Zookeeper分布式协调服务

2023/11/30 Install基础

# 安装与核心概念

Zookeeper是一个分布式的开源协调服务,主要用于解决分布式系统中的一些数据管理、配置管理、命名服务、集群管理、分布式锁等问题。Zookeeper提供了一个简单的树形结构的命名空间,可以存储任意类型的数据,而且可以通过Watcher机制来实现分布式系统中的协调和同步。

# 1、主要特点

  1. 分布式协调:Zookeeper可以作为分布式系统的协调服务,管理分布式系统中各个节点的状态和配置信息,实现分布式锁等机制。
  2. 高可用性:Zookeeper支持多机部署,可以实现高可用性的服务,通过主备节点之间的选举机制保证服务的可用性。
  3. 数据一致性:Zookeeper提供了强一致性的数据访问机制,保证不同节点之间数据的一致性。
  4. Watcher机制:Zookeeper提供了Watcher机制,可以实现分布式系统中的事件通知和监控机制,可以实现分布式系统中节点状态的实时监控和更新。

使用Docker进行安装:

docker run -d --name xk857_zookeeper -p 2181:2181 -t wurstmeister/zookeeper
1

# 2、客户端操作Zookeeper

客户端操作Zookeeper其实不是很重要,实际操作大多在Java客户端操作,客户端操作仅为演示Zookeeper的功能和特性,所以命令写的不是很详细,如果感兴趣可自行搜索。

# 进入docker容器内
docker exec -it xk857_zookeeper /bin/bash

# 查看目录,进入bin目录
ls
cd bin

# 运行Zookeeper自带的客户端
./zkCli.sh 
1
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会+1
    • set /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]
  • 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 # 提示没有权限
1
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
1
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第一次是创建用户,后续则是登录
1
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
1
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>
1
2
3
4
5

# 1、初始化ZClient

就两个参数,ip地址加端口号以及连接超时时间。

ZkClient zkClient = new ZkClient("192.168.31.76:2181", 50000);
1

# 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");
    }
}
1
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:以上三种节点类型的组合。即同时具有PERSISTENTEPHEMERALEPHEMERAL_SEQUENTIAL节点的特性

# 3、删除节点

deleteRecursive可以递归删除节点下所有的子节点

public static void deleteNode(){
    // 删除节点c,以及节点c下面所有的子节点
    zkClient.deleteRecursive("/c");
}
1
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");
    }
});
1
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、其他操作

  1. 返回PATH下的所有子节点:zkClient.getChildren(PATH);
  2. 写数据:zkClient.writeData(PATH, "456");
  3. 检查节点是否存在:zkClient.exists(PATH);