二、Zookeeper客户端使用与集群特性

1,820 阅读14分钟

一、客户端API常规应用


知识点:

  1. 初始连接
  2. 创建、查看节点
  3. 监听节点
  4. 设置节点权限
  5. 第三方客户端ZkClient

zookeeper提供了java与C两种语言的客户端。我们要学习的就是java客户端。引入最新的maven依赖:

<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.5.5</version>
</dependency>

1. 初始连接

常规的客户端类是 org.apache.zookeeper.ZooKeeper,实例化该类之后将会自动与集群建立连接。构造参数说明如下:

参数名称 类型 说明
connectString String 连接串,包括ip+端口 ,集群模式下用逗号隔开 192.168.0.149:2181,192.168.0.150:2181
sessionTimeout int 会话超时时间,该值不能超过服务端所设置的 minSessionTimeout 和maxSessionTimeout
watcher Watcher 会话监听器,服务端事件将会触该监听
sessionId long 自定义会话ID
sessionPasswd byte[] 会话密码
canBeReadOnly boolean 该连接是否为只读的
hostProvider HostProvider 服务端地址提供者,指示客户端如何选择某个服务来调用,默认采用StaticHostProvider实现
String conn = "106.13.125.89:2181";
ZooKeeper zooKeeper = new ZooKeeper(conn, 60000, new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
        System.out.println(watchedEvent.getPath());
        System.out.println(watchedEvent);
    }
});

2. 创建、查看节点

创建节点

通过org.apache.zookeeper.ZooKeeper#create()即可创建节点,其参数说明如下:

参数名称 类型 说明
path String
data byte[]
acl List
createMode CreateMode
cb StringCallback
ctx Object
List<ACL> list = new ArrayList<>();
int perm = ZooDefs.Perms.ADMIN | ZooDefs.Perms.READ;
ACL acl1 = new ACL(perm,new Id("world","anyone"));
ACL acl2 = new ACL(perm,new Id("ip","192.168.0.149"));
ACL acl3 = new ACL(perm,new Id("ip","127.0.0.1"));
list.add(acl1);
list.add(acl2);
list.add(acl3);
zooKeeper.create("/cyan/cyan004","cyan004".getBytes(),list,CreateMode.PERSISTENT);

查看节点:

通过org.apache.zookeeper.ZooKeeper#getData()即可查看节点,其参数说明如下:

参数名称 类型 说明
path String
watch boolean
watcher Watcher
cb DataCallback
ctx Object
// 获取节点
byte[] data = zooKeeper.getData("/cyan",false,null);

查看子节点:

通过org.apache.zookeeper.ZooKeeper#getChildren()即可查看子节点,其参数说明如下:

参数名称 类型 说明
path String
watch boolean
watcher Watcher
cb Children2Callback
ctx Object
// 查看子节点(无监听)
List<String> children = zooKeeper.getChildren("/cyan",false);
        children.stream().forEach(System.out::println);

3. 监听节点

在getData()与getChildren()两个方法中可分别设置监听数据变化和子节点变化。通过设置watch为true,当前事件触发时会调用zookeeper()构建函数中Watcher.process()方法。也可以添加watcher参数来实现自定义监听。一般采用后者。

// 获取节点(有一次监听)
byte[] data = zooKeeper.getData("/cyan",true,null);
Thread.sleep(Long.MAX_VALUE);

// 获取节点(有持久监听)
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/cyan", new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
        try {
            zooKeeper.getData(watchedEvent.getPath(),this,null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(watchedEvent.getPath());
        System.out.println(watchedEvent);
    }
}, stat);
Thread.sleep(Long.MAX_VALUE);

// 查看子节点(有一次监听)        
List<String> children = zooKeeper.getChildren("/cyan",event -> {
    try {
        zooKeeper.getChildren("/cyan",false);
    } catch (Exception e) {
        e.printStackTrace();
    }
});
children.stream().forEach(System.out::println);
Thread.sleep(Long.MAX_VALUE);   

注:所有的监听都是一次性的,如果要持续监听需要触发后在添加一次监听。

4. 设置节点ACL权限

ACL包括结构为scheme:id:permission

客户端中由org.apache.zookeeper.data.ACL 类表示,类结构如下:

  • ACL
    • Id
      • scheme // 对应权限模式scheme
      • id // 对应模式中的id值
    • perms // 对应权限位permission

关于权限位的表示方式:

每个权限位都是一个唯一数字,将其通过或运算生成一个全新的数字即可

@InterfaceAudience.Public
public interface Perms {
    int READ = 1 << 0;
    int WRITE = 1 << 1;
    int CREATE = 1 << 2;
    int DELETE = 1 << 3;
    int ADMIN = 1 << 4;

    int ALL = READ | WRITE | CREATE | DELETE | ADMIN;
}
List<ACL> list = new ArrayList<>();
int perm = ZooDefs.Perms.ADMIN | ZooDefs.Perms.READ;
ACL acl1 = new ACL(perm,new Id("world","anyone"));
ACL acl2 = new ACL(perm,new Id("ip","192.168.0.149"));
ACL acl3 = new ACL(perm,new Id("ip","127.0.0.1"));

5. 第三方客户端ZkClient

zkClient是在zookeeper客户端基础之上封装的,使用上更加友好。主要变化如下:

  • 可以设置持久监听,或删除某个监听
  • 可以插入JAVA对象,自动进行序列化和反序列化
  • 简化了基本的增删改查操作。

二、Zookeeper集群


知识点:

  1. 集群三种角色说明
  2. 注册中心常见三种模式
  3. zookeeper选举时的状态
  4. 选举发生的时机及选举算法
  5. 数据同步机制
  6. 集群部署
  7. 四字运维命令

zookeeper集群的目的是为了保证系统的性能承载更多的客户端连接而专门提供的机制。通过集群可以实现以下功能:

  • 读写分离:提高承载,为更多的客户端提供连接,并保障性能。
  • 主从自动切换:提高服务容错性,部分节点故障不会影响整个服务集群。

半数以上运行机制说明:

集群至少需要三台服务器,并且强烈建议使用奇数个服务器。因为zookeeper通过判断大多数节点的存活来判断整个服务是否可用。比如3个节点,挂掉了2个表示整个集群挂掉,而用偶数4个,挂掉了2个也表示其并不是大部分存活,因此也会挂掉。

1. 集群三种角色说明

zookeeper集群中总共有三种角色,分别是leader(主节点)、follower(子节点)、 observer(次级子节点)

角色 描述
leader 主节点,又名领导者。作为整个zk集群写请求的唯一处理者,并负责进行投票的发起与决议,通过选举产生,如果宕机将会选举新的主节点。
follower 子节点,又名追随者。接收客户端请求,处理读请求,并向客户端返回结果,将写请求转给leader。同时他也是主节点的备选节点,并用拥有投票权。
observer 次级子节点,又名观察者。可以理解为无选举投票权的follower,不能选为主节点,并且在计算集群可用状态时不会将observer计算入内。其主要是为了协助follower处理更多的读请求,如果zk集群的读请求负载很高,或者客户端非常非常多,多到跨机房,则可以设置一些Observer服务器,以提高读取的吞吐量。

observer配置: 只要在集群配置中加上observer后缀即可,示例如下:

server.3=127.0.0.1:2889:3889:observer

2. 注册中心常见三种模式

zookeeper的核心是广播机制,该机制保证了各个zk之间数据同步(数据一致性)。zk实现的机制为ZAB(Zookeeper Atomic Broadcast:zk原子广播)协议,通过ZAB协议来保证分布式事务的最终一致性

  1. 恢复模式:如果leader崩溃,这个时候就会进入恢复模式,使整个zk集群恢复到正常的工作状态
  2. 同步模式:新的leader选举出来后,就乎进入同步模式(各个follower会去同步新的leader上的数据),当大多数zkServer完成了与leader的状态同步之后,恢复模式就结束
  3. 广播模式:客户端想写入数据,这个时候leader发起提议,当leader的提议被大多数的zkServer统一之后,leader就会去修改自身的数据,并将修改后的数据广播给其他的follower

3. zookeeper选举时的状态

  • myid:这是zk集群中服务器的唯一标识,称为myid。例如,有三个zk服务器,那么分别编号1,2,3

  • zxid:为long类型,长度为64位的数字,其中高32位表示epoch(周期id),每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的周期id,进行加1,再将低32位的全部设置为0。这样就保证了每次新选举出的leader的ZXID的唯一性;低32位表示xid(事务id),按照数字递增,任何数据的变更都会导致低32位的数字简单加1;即zxid由两部分构成:epoch与xid。

每个leader都会具有一个不同的epoch值,表示一个时期、时代。新的leader产生,则会更新所有zkServer的zxid中的epoch值。而xid则为zk的事务id,每一个写操作都是一个事务,都会有一个xid。每一个写操作都需要leader发起一个提议,由所有follower表决是否同意本次写操作。

zk的选举状态:

  1. LOOKING:选举状态
  2. LEADING:领导者状态,处于该状态的服务器称为leader
  3. FOLLOWING:随从状态,同步leader状态,处于该状态的服务器称为Follower
  4. OBSERVING:观察状态,同步leader状态,处于该状态的服务器称为Observer

4. 选举发生的时机及选举算法

  • 发生时机:整个集群群龙无首的时候(服务启动、leader宕机之后)
  • 选举机制:集群中,半数zkServer同意,则产生新的leader(搭建集群时,一般都是奇数个,三台服务器,最多允许一台宕机,四台服务器,也是最多允许一台宕机)
  • 选举算法:对比(myid,zxid),先对比zxid大者胜出(大表示数据越新),称为leader,如果zxid一致,则myid大者胜出

当节点初始启动时会在集群中寻找Leader节点,如果找到则与Leader建立连接,其自身状态变化为followerobserver。如果没有找到Leader,当前节点状态将变化LOOKING,进入选举流程。

在集群运行其间如果有follower或observer节点宕机只要不超过半数并不会影响整个集群服务的正常运行。但如果leader宕机,将暂停对外服务,所有follower将进入LOOKING 状态,进入选举流程

通过 ./bin/zkServer.sh status <zoo配置文件> 命令可以查看到节点状态

./bin/zkServer.sh status conf/zoo.cfg
Mode: follower

可以发现中间的2182是leader状态.其选举机制如下图:

图片

投票机制说明:

  • 第一轮投票:全部投给自己
  • 变更投票:每台机器发出投票后,也会收到其他机器的投票,每台机器会根据一定规则来处理收到的其他机器的投票,并以此来决定是否需要变更自己的投票,这个规则也是整个leader选举算法的核心所在,具体规则如下:
规则一:如果other_zxid大于my_zxid,则认可当前收到的投票,并在此将该票发送出去
规则二:如果other_zxid小于my_zxid,则坚持自己的投票,不做任何变更
规则三:如果other_zxid等于my_zxid,那么比较other_myid与my_myid,如果other_myid大于my_myid,则认可当前收到的投票,并在此将该票发送出去
规则四:如果other_zxid等于my_zxid,那么比较other_myid与my_myid,如果other_myid小于my_myid,则坚持自己的投票,不做任何变更
  • 确定leader:经过第二轮投票后,集群中的每台机器都会收到其他机器的投票,然后开始统计投票,如果一台机器收到超过半数的相同投票,那么这个投票对应的myid即为leader,选举结束

5. 数据同步机制

zookeeper的数据同步是为了保证各节点中数据的一至性,同步时涉及两个流程,一个是正常的客户端数据提交,另一个是集群某个节点宕机在恢复后的数据同步。

客户端写入请求:

写入请求的大至流程是,leader接收客户端写请求,并同步给各个子节点。如下图:

图片

但实际情况要复杂的多,比如client并不知道哪个节点是leader,有可能写的请求会发给follower ,由follower在转发给leader进行同步处理

图片

客户端写入流程说明:

  1. client向zk中的server发送写请求,如果该server不是leader,则会将该写请求转发给leader server,leader将请求事务以proposal形式分发给follower;
  2. 当follower收到leader的proposal时,根据接收的先后顺序处理proposal;
  3. 当Leader收到follower针对某个proposal过半的ack后,则发起事务提交,重新发起一个commit的proposal
  4. Follower收到commit的proposal后,记录事务提交,并把数据更新到内存数据库;
  5. 当写成功后,反馈给client。

服务节点初始化同步:

在集群运行过程当中,如果有一个follower节点宕机,由于宕机节点没过半,集群仍然能正常服务。当leader收到新的客户端请求,此时无法同步给宕机的节点。造成数据不一至。为了解决这个问题,当节点启动时,第一件事情就是找当前的Leader,比对数据是否一至。不一至则开始同步,同步完成之后在进行对外提供服务。

如何比对Leader的数据版本呢,这里通过ZXID(事物ID)来确认

思考题: 如果leader节点宕机,在恢复后它还能被选为leader吗?

6. 集群部署

配置语法:server.<节点ID>=:<数据同步端口>:<选举端口>

  • 节点ID:服务id手动指定1至125之间的数字,并写到对应服务节点的{dataDir}/myid 文件中。
  • IP地址:节点的远程IP地址,可以相同。但生产环境就不能这么做了,因为在同一台机器就无法达到容错的目的。所以这种称作为伪集群。
  • 数据同步端口:主从同时数据复制端口,(做伪集群时端口号不能重复)。
  • 选举端口:主从节点选举端口,(做伪集群时端口号不能重复)。

集群配置流程:

  1. 三台服务器均在zk目录中创建data目录用于存储节点数据,并在data目录里创建一个myid文件,文件内容是一个数字,对应server.1=192.168.1.11:2888:3888中的server后面的数字
mkdir data
echo 1 > data/myid
  1. 编写配置文件

conf/zoo.cfg

tickTime=2000
initLimit=10
syncLimit=5
dataDir=data
clientPort=2181
# 集群配置
server.1=192.168.1.11:2887:3887
server.2=192.168.1.11:2887:3887
server.3=192.168.1.11:2887:3887
  1. 分别启动
./bin/zkServer.sh start conf/zoo.cfg
  1. 分别查看状态
./bin/zkServer.sh status conf/zoo.cfg

检查集群复制情况:

分别连接指定节点:zkCli.sh 后加参数-server 表示连接指定IP与端口。

./bin/zkCli.sh -server 192.168.1.11:2181
./bin/zkCli.sh -server 192.168.1.12:2181
./bin/zkCli.sh -server 192.168.1.13:2181

[ ] 任意节点中创建数据,查看其它节点已经同步成功。

注意:-server参数后同时连接多个服务节点,并用逗号隔开 192.168.1.11:2181,192.168.1.12:2182

7. 四字运维命令

ZooKeeper响应少量命令。每个命令由四个字母组成。可通过telnet或nc向ZooKeeper发出命令。

这些命令默认是关闭的,需要配置4lw.commands.whitelist来打开,可打开部分或全部示例如下:

# 打开指定命令
4lw.commands.whitelist=stat, ruok, conf, isro
# 打开全部
4lw.commands.whitelist=*

安装Netcat工具,以使用nc命令

# 安装Netcat 工具
yum install -y nc
# 查看服务器及客户端连接状态
echo stat | nc localhost 2181

命令列表

  1. conf:3.3.0中的新增功能:打印有关服务配置的详细信息。
  2. 缺点:3.3.0中的新增功能:列出了连接到该服务器的所有客户端的完整连接/会话详细信息。包括有关已接收/已发送的数据包数量,会话ID,操作等待时间,最后执行的操作等信息。
  3. crst:3.3.0中的新增功能:重置所有连接的连接/会话统计信息。
  4. dump:列出未完成的会话和临时节点。这仅适用于领导者。
  5. envi:打印有关服务环境的详细信息
  6. ruok:测试服务器是否以非错误状态运行。如果服务器正在运行,它将以imok响应。否则,它将完全不响应。响应“ imok”不一定表示服务器已加入仲裁,只是服务器进程处于活动状态并绑定到指定的客户端端口。使用“ stat”获取有关状态仲裁和客户端连接信息的详细信息。
  7. srst:重置服务器统计信息。
  8. srvr:3.3.0中的新功能:列出服务器的完整详细信息。
  9. stat:列出服务器和连接的客户端的简要详细信息。
  10. wchs:3.3.0中的新增功能:列出有关服务器监视的简要信息。
  11. wchc:3.3.0中的新增功能:按会话列出有关服务器监视的详细信息。这将输出具有相关监视(路径)的会话(连接)列表。请注意,根据手表的数量,此操作可能会很昂贵(即影响服务器性能),请小心使用。
  12. dirs:3.5.1中的新增功能:以字节为单位显示快照和日志文件的总大小
  13. wchp:3.3.0中的新增功能:按路径列出有关服务器监视的详细信息。这将输出具有关联会话的路径(znode)列表。请注意,根据手表的数量,此操作可能会很昂贵(即影响服务器性能),请小心使用。
  14. mntr:3.4.0中的新增功能:输出可用于监视集群运行状况的变量列表。