Zookeeper
ZooKeeper
- 前身是Google的Chubby
- ZooKeeper 是一个开源的分布式协调服务器,为分布式提供一致性服务。其一致性是通过基于Paxos算法的ZAB协议完成的,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置维护,域名服务、分布式同步、分布式锁和分布式队列等功能。
- 配置维护
- 域名服务
- 分布式同步
- 集群管理
一致性的要求
- 顺序一致性
- 原子性
- 单一视图
- 可靠性
- 实时性
重要概念
Session
- 客户端会话,客户端启动,首先会与zk服务器建立一个TCP长连接,通过该长连接,客户端能够通过心跳检测保持与服务端的有效会话,也能够向zookeeper服务端发送请求并接收响应。同时还能通过该连接的接收来自服务端的Watcher时间通知。
- Session的SessionTimeOut值用来设置一个客户端会话的超时时间。当服务端由于压力、网络故障或者客户端主动断开连接等各种原因导致客户端连接断开时,只要在SessionTimeout的规定的时间内重新连接上服务端中的任意一台服务器,那么之前创建的会话仍然有效。
ZNode
- zookeeper的文件系统采用的是树形层次化的目录结构,每个目录在zookeeper中叫做一个ZNode,每个ZNode拥有一个唯一的路径标识,即名称。ZNode可以包含数据和子ZNode(临时节点不能有子ZNode).ZNnde中的数据可以有多个版本,所以查询某路径下的数据需要带上版本号。客户端可以在ZNnode上设置监视器(Watcher)
- 可以分为临时节点和持久节点,持久节点一旦被创建,除非主动移除,否则将一直保存在ZooKeeper上。临时节点的生命周期和Session保持一致。还有就是临时顺序节点和持久顺序节点,除了基本的特性之外,子节点的名称还具有有序性。
stat
- 每个Znode都会维护一个Stat的数据结构。该数据结构记录着ZNode的三个版本号
- version:当前Znode的版本
- cversion:当前ZNode子节点的版本
- aversion:当前ZNode的ACL版本
Watcher(事件监听器)
客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程。
zk通过Watcher机制实现了发布/订阅模式。zk提供了分布式数据的发布订阅功能,一个发布者能够让多个订阅者同时监听某一个主题对象,当这个对象的状态发生变化时,会通知所有的订阅者,使他们能够做出相应的处理。zk引入了Watcher机制来实现这种模式的通知功能。zk允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发这个Watcher,那么就会向指定客户端发送一个通知。这个时间通知是通过TCP长连接的Session完成的
watcher的特性
- 一次性 :Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册
- 客户端顺序回调 :Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行
- 轻量级: WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容;
- 时效性: Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;
ACL
- (Access Control List)访问控制列表 用于控制资源的访问权限,是zk数据的安全保障,zk利用ACL策略控制znode的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等
- 在传统的文件系统中,ACL分为两个维度:组和权限,一个组可以包含多种权限,一个文件或目录拥有了某个组的权限即拥有了组里所有的权限。子目录和文件会默认继承其父目录的ACl。而在zk中,znode的ACL是没有继承关系的,每个znode的权限都是独立控制的,只有客户端满足Znode设置的权限要求时,才能完成相应的操作。zookeeper的ACL分为三个维度:授权策略 scheme、用户 id 、用户权限 permisson
- 五种权限
- CREATE:创建子节点的权限
- DELETE:删除子节点的权限
- READ:获取节点数据和子节点列表的权限
- WRITE:更新更点数据的权限
- ADMIN:设置节点ACl的权限
监听器
- 监听Znode节点的数据变化
- 监听子节点的增减变化
分布式锁
- 创建顺序临时节点,如果是最小的节点就返回加锁成功如果不是就监听了自己的前一个节点,当自己的前一个节点删除时,自己开始执行
缺点
- Zk性能上没有redis分布式锁好,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上
- 网络抖动并发问题
- 由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。
就可能产生并发问题了,这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。
多次重试之后还不行的话才会删除临时节点。
- 由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。
ZAB协议
使用一个单一进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更为以事务Proposal的形式广播到所有的副本进程上去。
ZAB协议必须能够保证一个全局的变更序列被顺序应用。
ZAB协议还需要在当前主线程出现上述异常情况的时候可以正常工作。
三类角色
- Leader
- zk写请求的唯一处理者,并负责进行投票的发起和决议,更新系统状态;Leander的写请求需要经过半数以上zkServer同意
- Follower
- 接收客户端请求,处理读请求,并向客户端返回结果;将写请求转给Leader,在选Leader的过程中参与投票
- Observer
- 无选主权和写操作投票权的Flollower,其不属于法定人数范围,主要是为了协助Follower处理更多的读请求
两种模式
- 崩溃恢复
- 恢复模式
- 在服务重启过程中,或者Leader崩溃后,就进入了恢复模式并选举出新的Leader,来恢复到zk集群正常的工作状态
- 同步模式
- 当所有的zkServer启动完毕,或者Leader崩溃后又被选举出来,就进入到同步模式,各个Follower需要马上将Leader中的数据同步到自己的主机中。当大多数(超过半数)zkServer完成了与Leader的状态同步后,崩溃恢复就结束了。
- 恢复模式
- 消息广播
- 当Leader的提议被大多数zkServer同意后,Leader会修改自身数据然后会将修改后的数据广播给其他Follower或新加入的Follower
zxid
- zxid为事务编号。是一个64位长度的Long类型,由两部分组成,其中高32位表示纪元epoch,低32位表示事务标识xid,是一个单调递增的计数器。
- 每个Leader都会具有一个不同的epoch值,表示一个时期、时代。每一个新的选举开启时都会生成一个新的epoch,新的Leader产生,则会更新所有zkServer的zxid中epoch。
- xid为zk事务的id,每一个写操作都是一个事务。xid为一个依次递增的流水号。每一个写操作都需要由Leader发起一个提案,由所有Follower表决是否同意本次写操作,而每个提案都具有一个zxid
消息广播算法
恢复模式的两个原则
- 已被处理过的消息不能丢
- 被丢弃的消息不能再现
Leader选举
时机
- 集群启动的Leader选举
- 一个是服务启动的时候当整个集群都没有leader节点会进入选举状态,如果leader已经存在就会告诉该节点leader的信息,自己连接上leader,整个集群不用进入选举状态。
- 断连后的Leader选举
- 在服务运行中,可能会出现各种情况,服务宕机、断电、网络延迟很高的时候leader都不能再对外提供服务了,所有当其他几点通过心跳检测到leader失联之后,集群也会进入选举状态。
- 集群启动的Leader选举
规则
- 当其他节点的纪元比自身高选纪元高的那个,如果纪元相同比较自身的zxid的大小,选举zxid大的节点,这里的zxid代表节点所提交事务最大的id,zxid越大代表该节点的数据越完整。
最后如果epoch和zxid都相等,则比较服务的serverId,这个Id是配置zookeeper集群所配置的,
- 当其他节点的纪元比自身高选纪元高的那个,如果纪元相同比较自身的zxid的大小,选举zxid大的节点,这里的zxid代表节点所提交事务最大的id,zxid越大代表该节点的数据越完整。
流程
所有节点第一票先选举自己当leader,将投票信息广播出去;
从队列中接受投票信息;
按照规则判断是否需要更改投票信息,将更改后的投票信息再次广播出去;
判断是否有超过一半的投票选举同一个节点,如果是选举结束根据投票结果设置自己的服务状态,选举结束,否则继续进入投票流程。
数据一致性
流程
- Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个全局唯一递增的事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower
- Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader
- Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了
选举后的数据同步
- 数据同步包含3个主要值和4种形式。
- PeerLastZxid:Learner服务器最后处理的ZXID
- minCommittedLog:Leader提议缓存队列中最小ZXID
- maxCommittedLog:Leader提议缓存队列中最大ZXID
直接差异化同步 DIFF同步
- 表现
- 如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。
- 首先Leader向Learner发送DIFF指令,代表开始差异化同步,然后把差异数据(从PeerLastZxid到maxCommittedLog之间的数据)提议proposal发送给Learner
- 发送完成之后发送一个NEWLEADER命令给Learner,同时Learner返回ACK表示已经完成了同步
- 接着等待集群中过半的Learner响应了ACK之后,就发送一个UPTODATE命令,Learner返回ACK,同步流程结束
先回滚再差异化同步 TRUNC+DIFF同步
- 针对的是一个异常的场景
- 举例
- 假设现在的Leader是A,minCommittedLog=1,maxCommittedLog=3,刚好生成的一个proposal的ZXID=4,然后挂了。
- 重新选举出来的Leader是B,B之后又处理了2个提议,然后minCommittedLog=1,maxCommittedLog=5。这时候A的PeerLastZxid=4,在(1,5)之间。这一条只存在于A的提议如何处理
- A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。
流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。
仅回滚同步 TRUNC同步
- 针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。
这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。
所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。
全量同步 SNAP同步
- 适用于两个场景:
- PeerLastZxid小于minCommittedLog
- Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID
这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。
数据不一致的情况
查询不一致
- 因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。
解决方案可以在读取前使用sync命令。
leader未发送proposal宕机
- leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。
这种场景下的日志将会被丢弃。
leader发送proposal成功,发送commit前宕机
- 如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。
zkui的部署
- nohup java -jar ./zkui-2.0-SNAPSHOT-jar-with-dependencies.jar &
使用
- 配置环境变量
- 配置文件说明
- tickTime: 心跳频率
- initLimit: 初始化心跳时间
- syncLimit: 同步时间
- maxClientCnxns: 允许的客户端最大连接数
- 集群配置
- server.1=ip:2888:3888 通过server.x的格式来配置所有的zk主机 , 3888接口用来通信投票选举leader,2888用来leader与各个salves通信
- 在linux中data目录下,创建myid文件给当前zk设置id 选举leader靠id决定(最大的当选leader),id要与配置文件一致
- observer的配置
- peerType=observer
- server.1:localhost:2181:3181**:observer** 追加这一行
- 基本命令
- create path value 创建节点,例如 create /ooxx “” -e 创建临时节点 -s 多个节点同时对同一个节点修改,使用序列来避免覆盖数据的问题
- get set 获取和修改节点数据
ZK的配置
集群模式
- zoo.cfg
- 集群上所有机器上zoo.cfg都应该是一致的
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# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/home/zk/zookeeper-3.4.14/data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
# 集群模式下配置:server.id=host:port:port
- 集群上所有机器上zoo.cfg都应该是一致的
- myid
- 位于dataDir中的.pid文件,是给
Zookeeper
https://x-leonidas.github.io/2022/02/01/11技术栈/注册中心/Zookeeper/