Watcher 节点事件监听
zookeeper 提供了分布式数据的发布/订阅功能, zookeeper允许客户端向服务端注册一个 watcher 监听。
当服务端的一些指定事件(比如可以监听节点数据变更、节点删除、子节点状态变更等 )触发了 watcher,那么服务端就会向客户端发送 一个事件通知。
值得注意的是, Watcher 通知是一次性的,即一旦触发一次通知后,该 Watcher 就失效了,因此如果要实现永久监听,可以通过循环注册来实现
通过这个事件机制,可以基于 zookeeper 实现分布式锁、集群管理等功能
实现分布式锁
利用 zookeeper 节点的特性来实现独占锁,就是同级节点的唯一性,多个进程往 zookeeper 的指定节点下创建一个相同名称的节点,只有一个能成功,另外一个是创建失败;
创建失败的节点全部通过 zookeeper 的 watcher 机制来监听 zookeeper 这个子节点的变化,一旦监听到子节点的删除事件,则再次触发所有进程去写锁;
存在的问题
实现方式很简单,但是会产生“惊群效应”,简单来说就是如果存在许多的客户端在等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待状态的客户端都会被唤醒。
这个时候 zookeeper 在短时间内发送大量子节点变更事件给所有待获取锁的客户端,然后实际情况是只会有一个客户端获得锁。
如果在集群规模比较大的情况下,会对 zookeeper 服务器的性能产生比较的影响。
利用有序节点来实现分布式锁
通过有序节点来实现分布式锁,每个客户端都往指定的节点下注册一个临时有序节点,越早创建的节点,节点的顺序编号就越小,那么我们可以判断子节点中最小的节点设置为获得锁。
每个节点只需要监听比自己小的节点,当比自己小的节点删除以后,客户端会收到 watcher 事件,此时再次判断自己的节点是不是所有子节点中最小的,如果是则获得锁,否则就不断重复这个过程,这样就不会导致惊群效应。
Zookeeper 的 Java 访问框架
针对 zookeeper,比较常用的 Java 客户端有 zkclient、curator。由于 Curator 对于 zookeeper 的抽象层次比较高,简化了zookeeper 客户端的开发量。使得 curator 逐步被广泛应用。
ACL 权限控制
为了保证 zookeeper 中的数据的安全性,避免误操作带来的影响。 Zookeeper 提供了一套 ACL 权限控制机制来保证数据的安全。
(1)ZooKeeper 提供了如下几种验证模式:
1)IP
通 过 ip 地 址 粒 度 来 进 行 权 限 控 制 , 例 如 配 置[ip:192.168.0.1], 或者按照网段 ip:192.168.0.1/24 ;
2)digest
digest模式是最常用的一种模式,形如"username:password"的方式。
3)World
World模式其实是一种开放的模式,即对所以用户开放,设置格式是"world:anyone",事实上这种模式没什么效果,因为设置了也对所有人开放
4)Super
Super模式是超级管理员模式,超级管理员可以对任何节点进行操作
(2)权限类型
指通过权限检查后可以被允许的操作 , create /delete/read/write/admin
Create 允许对子节点 Create 操作
Read 允许对本节点 GetChildren 和 GetData 操作
Write 允许对本节点 SetData 操作
Delete 允许对子节点 Delete 操作
Admin 允许对本节点 setAcl 操作
Leader选举
实际使用ZooKeeper开发中,我们最常用的是Apache Curator。 它由Netflix公司贡献给Apache。Curator 有两种选举 recipe(Leader Latch 和 Leader Election)
Leader Latch
参与选举的所有节点,会创建一个顺序节点,其中最小的节点会设置为 master 节点, 没抢到 Leader 的节点都监听前一个节点的删除事件。
在前一个节点删除后进行重新抢主,当 master 节点手动调用 close()方法或者 master节点挂了之后, 后续的子节点会抢占 master。其中 spark 使用的就是这种方法
LeaderSelector
LeaderSelector 和 Leader Latch 最大的差别在于, leader可以释放领导权以后,还可以继续参与竞争
Zookeeper 数据同步
见下方ZAB(Zookeeper Atomic Broadcast) 协议
ZAB协议
ZAB(Zookeeper Atomic Broadcast) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。
ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性 。
ZAB 协议包含两种基本模式,分别是 :崩溃恢复 和 原子广播
恢复模式介绍
当整个集群在启动时,或者当 leader 节点出现网络中断、崩溃等情况时, ZAB 协议就会进入恢复模式并选举产生新的 Leader,当 leader 服务器选举出来后,并且集群中有过半的机器和该 leader 节点完成数据同步后, ZAB 协议就会退出恢复模式。
在 Leader 节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入恢复模式
广播模式介绍
当集群中已经有过半的 Follower 节点完成了和 Leader 状态同步以后,那么整个集群就进入了消息广播模式。
leader 节点可以处理事务请求和非事务请求, follower 节点只能处理非事务请求,如果 follower 节点接收到非事务请求,会把这个请求转发给 Leader 服务器
消息广播原理
消息广播的过程实际上是一个简化版本的二阶段提交过程
- leader 接收到消息请求后,将消息赋予一个全局唯一的64 位自增 id,叫: zxid,通过 zxid 的大小比较既可以实现因果有序这个特征
- leader 为每个 follower 准备了一个 FIFO 队列(通过 TCP协议来实现,以实现了全局有序这一个特点)将带有 zxid的消息作为一个提案(proposal)分发给所有的 follower
- 当 follower 接收到 proposal,先把 proposal 写到磁盘,写入成功以后再向 leader 回复一个 ack
- 当 leader 接收到合法数量(超过半数节点)的 ACK 后,leader 就会向这些 follower 发送 commit 命令,同时会在本地执行该消息
- 当 follower 收到消息的 commit 命令以后,会提交该消息
和完整的 2pc 事务不一样的地方在于, zab 协议不能终止事务
崩溃恢复的实现原理
一旦 Leader 节点崩溃,或者由于网络问题导致 Leader 服务器失去了过半的Follower 节点的联系那么就会进入到崩溃恢复模式。
崩溃恢复状态下 zab 协议需要做两件事:
-1. 选举出新的 leader
-2. 数据同步
需要解决两个问题:
1.已经被处理的消息不能丢
这种是服务器leader已经发广播commit出去了,并向连接的客户端返回「成功」,然后崩溃或失联,为了最终能够在所有的服务器上执行,这种消息不能丢了。
2.被丢弃的消息不能再次出现
场景:当 leader 接收到消息请求生成 proposal 后挂了,其他 follower 并没有收到此 proposal,很久之后挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的proposal 状态,与整个系统的状态是不一致的,需要将其删除。
实现办法:
1.保证新选举出来的 Leader 服务器拥所有机器最高编号(消息ZXID 最大)的事务Proposal,那么就可以保证这个新选举出来的 Leader 一定具有已经提交的提案。
因为所有提案被 COMMIT 之前必须有超过半数的 follower ACK,即必须有超过半数节点的服务器的事务日志上有该提案的 proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保存了所有被COMMIT 消息的 proposal 状态
2.另外, zxid(消息id) 是 64 位:
高 32 位是 epoch 编号,每经过一次 Leader 选举产生一个新的 leader,新的 leader会将 epoch 号+1,
低 32 位是消息计数器,每接收到一条消息这个值+1,新 leader 选举后这个值重置为 0.
这样设计在于老 leader 挂了后重启,不会选举为 leader,因它的 zxid 肯定小于当前新leader。老 leader 作为 follower 接入新 leader后,新 leader 会让它拥有旧的 epoch 号未被 COMMIT 的 proposal 清除
ZXID
zxid,也就是事务 id ,为了保证事务的顺序一致性, zookeeper 采用了递增的事务 id 号(zxid)来标识事务 。所有的提议(proposal)都在被提出的时候加上了 zxid。
zxid 是一个 64 位的数字,它高 32 位是 epoch,用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的epoch=(原来的 epoch+1,低 32 位用于递增计数。
epoch:可以理解为当前集群所处的年代或者周期,每个leader 就像皇帝,都有自己的年号,所以每次改朝换代, leader 变更之后,都会在前一个年代的基础上加1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。