Zookeeper分布式协调服务
# 安装与核心概念
Zookeeper是一个分布式的开源协调服务,主要用于解决分布式系统中的一些数据管理、配置管理、命名服务、集群管理、分布式锁等问题。Zookeeper提供了一个简单的树形结构的命名空间,可以存储任意类型的数据,而且可以通过Watcher机制来实现分布式系统中的协调和同步。
# 1、主要特点
- 分布式协调:Zookeeper可以作为分布式系统的协调服务,管理分布式系统中各个节点的状态和配置信息,实现分布式锁等机制。
- 高可用性:Zookeeper支持多机部署,可以实现高可用性的服务,通过主备节点之间的选举机制保证服务的可用性。
- 数据一致性:Zookeeper提供了强一致性的数据访问机制,保证不同节点之间数据的一致性。
- Watcher机制:Zookeeper提供了Watcher机制,可以实现分布式系统中的事件通知和监控机制,可以实现分布式系统中节点状态的实时监控和更新。
使用Docker进行安装:
docker run -d --name xk857_zookeeper -p 2181:2181 -t wurstmeister/zookeeper
# 2、客户端操作Zookeeper
客户端操作Zookeeper其实不是很重要,实际操作大多在Java客户端操作,客户端操作仅为演示Zookeeper的功能和特性,所以命令写的不是很详细,如果感兴趣可自行搜索。
# 进入docker容器内
docker exec -it xk857_zookeeper /bin/bash
# 查看目录,进入bin目录
ls
cd bin
# 运行Zookeeper自带的客户端
./zkCli.sh
2
3
4
5
6
7
8
9
# 3、常用命令
ls:例如ls /进入根目录,Zookeeper中目录就是节点的意思,跟目录下展示的第一层就是跟节点get:获取节点数据set:设置节点数据create:创建节点create /xk857 one-data:创建xk857节点,数据为new-data,此时dataVersion会+1
delete:删除节点delete /xk857:删除xk857节点
create -e:创建临时节点delete -e:删除临时节点set:修改节点内容set /xk857 new-data:修改xk857节点的数据为new-data,此时dataVersion会+1set /xk857 new-data 2:如果当前最新版本不是2,则修改失败
- delete和set命令最后可以带上版本号,只有版本号相同操作才会生效,否则会报错。
# 4、ZK中的Session
- 客户端与服务端之间的连接存在会话,每个会话都会可以设置一个超时时间,心跳结束则session过期。
- Session过期,则临时节点znode会被抛弃。
# watcher机制
针对每个节点的操作都会有一个监督者wathcer,当监控的某个对象(节点)发生了变化,则触发watcher事件;
- zk中的watcher是一次性的,触发后立即销毁;
- java的客户端API提供了永久性的功能,但ZK本身的watcher机制只支持一次性操作。
- 父节点、 子节点增删改都能够触发其watcher
# 1、watcher相关命令
- 在get、ls或stat命令最后添加watch字段,则对当前查看的节点进行监督;比如
get /xk857 watch - 添加watcher后,父子节点的增删改查操作会触发不同的事件
- 创建父节点触发:NodeCreated
- 修改父节点数据触发: NodeDataChanged
- 删除父节点触发: NodeDeleted
- 为父节点设置watcher,创建子节点触发:NodeChildrenChanged
- 删除子节点触发:NodeChildrenChanged
- 为父节点设置watcher,修改子节点不触发事件
# 2、watcher使用场景
统一资源配置:当某个节点的数据或状态发生变化时,通过watcher监听到变化,然后去更新其他节点或连接到当前ZK客户端的配置。
# ACL权限控制详解
针对节点可以设置相关读写等权限,目的为了保障数据安全性;权限permissions可以指定不同的权限范围以及角色。
- getAcl:获取某个节点的acl权限信息
- setAcl:设置某个节点的ac|权限信息
setAcl world:anyone:crwd:表示任何人都有创建、读取、写入、删除该节点的权限
- addauth:输入认证授权信息,注册时输入明文密码(登录),但是在zk的系统里,密码是以加密的形式存在的
# 1、权限的构成
zk的ACL通过[scheme:id:permissions]来构成权限列表
- scheme:代表采用的某种权限机制
- world:world下只有一个id,即只有一个用户,也就是anyone,那么组合的写法就是
world:anyone:[permissions] - auth:代表认证登录,需要注册用户有权限就可以,形式为
auth:user:password:[permissions] - digest:需要对密码加密才能访问,组合形式为
digest: username:BASE64(SHA1(password)):[permissions] - ip:限制制定ip才可访问,比如
ip:192.168.1.1:[permissions]
- world:world下只有一个id,即只有一个用户,也就是anyone,那么组合的写法就是
- id:代表允许访问的用户
- permissions:权限组合字符串,权限字符串缩写crdwa
- create:创建子节点
- read:获取当前节点/子节点
- write:设置节点数据
- delete:删除子节点
- admin:设置当前节点的权限
# 2、实战演练
# 1.scheme之world功能演示
[zk: localhost:2181(CONNECTED) 18] create /xk857/abc abc # 创建/xk857/abc节点,并设置值为abc
Created /xk857/abc
[zk: localhost:2181(CONNECTED) 19] getAcl /xk857/abc # 查看/xk857/abc节点的权限
'world,'anyone # 所有人
: cdrwa #可进行增删改查及设置权限
[zk: localhost:2181(CONNECTED) 20] setAcl /xk857/abc world:anyone:crwa # 设置该节点下的子节点不可删除
cZxid = 0x3 # ……输出的数据不重要这里省略
[zk: localhost:2181(CONNECTED) 21] getAcl /xk857/abc # 获取该节点,检查权限发现d没有了
'world,'anyone
: crwa
[zk: localhost:2181(CONNECTED) 22] creat /xk857/abc/bcd bcd #在 /xk857/abc 节点下创建子节点
ZooKeeper -server host:port cmd args # ……
[zk: localhost:2181(CONNECTED) 23] delete /xk857/abc/bcd # 尝试删除子节点
Node does not exist: /xk857/abc/bcd # 提示没有权限
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.scheme之auth功能演示
[zk: localhost:2181(CONNECTED) 25] addauth digest xk857:xk857 #创建用户,账号密码都是xk857
[zk: localhost:2181(CONNECTED) 26] setAcl /xk857/abc auth:xk857:xk857:cdrwa # 设置节点 /xk857/abc 对xk857账号开放所有权限
cZxid = 0x3 # ……
[zk: localhost:2181(CONNECTED) 27] getAcl /xk857/abc #获取节点权限数据
'digest,'xk857:+3c13f0N7Xwl04rR6Bnc+HcJuLQ= # 密码是加密存储的,不明文显示
: cdrwa
2
3
4
5
6
7
# 3.scheme之digest功能演示
auth和digest命令功能类似,只不过使用digest命令输入密码是密文。
[zk: localhost:2181(CONNECTED) 33] setAcl /xk857/abc digest:xk857:+3c13f0N7Xwl04rR6Bnc+HcJuLQ=:cdra # 密码使用加密方式
[zk: localhost:2181(CONNECTED) 34] addauth digest xk857:xk857 #但是登录时仍使用明文,addauth第一次是创建用户,后续则是登录
2
执行 addauth digest username:password 命令后,当客户端连接到Zookeeper服务器时,会将用户名和密码用MD5哈希算法加密后发送给服务器进行身份验证。如果身份验证成功,则客户端可以访问受保护的znode节点。
# 4.id设置
[zk: localhost:2181(CONNECTED) 0] create /xk857/ip ipadd
Created /xk857/ip
[zk: localhost:2181(CONNECTED) 2] setAcl /xk857/ip ip:192.168.31.11:cdrwa
2
3
4
# 使用ZClient操作Zookeeper
Zookeeper管阀提供了java客户端操作Zookeeper的API,但是并不好用,我们使用大神封装好的ZKClient客户端操作Zookeeper,一篇文章即可上手。
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
2
3
4
5
# 1、初始化ZClient
就两个参数,ip地址加端口号以及连接超时时间。
ZkClient zkClient = new ZkClient("192.168.31.76:2181", 50000);
# 2、创建节点
createParents用于创建节点,支持直接写路径递归创建子节点,且节点内容可以传任意类型数据,可以自定义内容的序列化和反序列化。在没指定zkSerializer时,默认使用java自动的序列化和反序列化
package cn.lxm.attendance.controller;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkConnection;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkConnection;
import org.apache.zookeeper.CreateMode;
import java.util.List;
public class ZkClientTest {
private static final String PATH = "/xk857/test";
static ZkClient zkClient = new ZkClient("192.168.31.76:2181",50000);
public static void main(String[] args) throws InterruptedException {
createNode();
List<String> children = zkClient.getChildren("/");
System.out.println(children);
}
// 创建node
public static void createNode(){
// 创建一个永久的node节点a
zkClient.createPersistent("/a");
// 创建一个永久性node节点b,和原生API相似直接传递数据进去,CreateMode有四种取值
zkClient.create("/b","这里是内容", CreateMode.PERSISTENT);
// 创建一个永久性node节点c/cc,true代表可以递归创建
zkClient.createPersistent("/c/cc",true);
// 创建永久性node节点d,里面内容为‘内容d’
zkClient.createPersistent("/d","内容d");
// 创建一个临时节点f
zkClient.createEphemeral("/f");
// 创建一个临时节点g,内容为 ‘内容g’
zkClient.createEphemeral("/g","内容g");
// 创建一个 递增序号 临时节点h
zkClient.createEphemeralSequential("/h","内容h");
// 创建一个 递增序号 的永久节点i
zkClient.createPersistentSequential("/i","内容i");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# CreateMode:
PERSISTENT:持久节点。如果节点不存在,则创建该节点;如果节点已经存在,则抛出异常。EPHEMERAL:临时节点。如果节点不存在,则创建该节点;如果节点已经存在,则删除该节点和其所有子节点。EPHEMERAL_SEQUENTIAL:顺序临时节点。与EPHEMERAL类似,但如果节点已经存在,则在当前节点之后创建一个新节点。EPHEMERAL_ALLOFTHEABOVE:以上三种节点类型的组合。即同时具有PERSISTENT、EPHEMERAL和EPHEMERAL_SEQUENTIAL节点的特性
# 3、删除节点
deleteRecursive可以递归删除节点下所有的子节点
public static void deleteNode(){
// 删除节点c,以及节点c下面所有的子节点
zkClient.deleteRecursive("/c");
}
2
3
4
# 4、ZKClient注册监听
subscribeChildChanges:在节点上加上一个listen监听事件,用来监听子节点的变化subscribeDataChanges:监听当前节点上数据的变化;handleDataChange数据变化, handleDataDeleted节点数据被删除subscribeStateChanges:监听zookeeper的状态
// 注册一个监听事件,subscribeChildChanges,通过使用listen方式来监听来达到消息广播效果,监听子节点变化
zkClient.subscribeChildChanges("/zz", new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println(">>>进入了handleChildChange");
System.out.println(">>>s = " + s);
System.out.println(">>>list = " + list);
}
});
// 注册一个监听事件,subscribeDataChanges 监听节点上数据的变化
zkClient.subscribeDataChanges("/zz", new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println(">>> 进入了handleDataChange");
System.out.println(">>> dataPath = " + dataPath);
System.out.println(">>> data = " + data);
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println(">>> 进入了handleDataDeleted");
System.out.println(">>> dataPath = " + dataPath);
}
});
// 监听zookeeper状态变化
zkClient.subscribeStateChanges(new IZkStateListener() {
@Override
public void handleStateChanged(Watcher.Event.KeeperState state) throws Exception {
System.out.println(">>> 进入了subscribeStateChanges");
System.out.println(">>> state = " + state);
}
@Override
public void handleNewSession() throws Exception {
System.out.println(">>> 进入了handleNewSession");
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 5、其他操作
- 返回PATH下的所有子节点:zkClient.getChildren(PATH);
- 写数据:zkClient.writeData(PATH, "456");
- 检查节点是否存在:zkClient.exists(PATH);