此文是阅读link zookeeper recipes的阅读笔记
说明
exists, getData,getChildren,delete,create 均为zk客户端的对某个znode的操作。含义同名。
Zookeeper可以用来实现以下分布式功能
分布式程序栅栏
当集群某些节点需要等待其他进程时,可以使用栅栏功能阻塞自身,并且在栅栏被删除的时候接受到通知。
步骤如下 :
1)客户端调用在栅栏znode上调用exist,并设置监听器watcher
2)如果exist返回false,说明栅栏不存在,程序继续执行
3)如果exist返回true,说明栅栏存在,需要等待
4)当监听器watcher被触发的时候,回到1)
双重栅栏
以上描述了单个栅栏的实现,利用zk还可以实现双重栅栏。
双重栅栏似的分布式程序可以同步某个计算的开始和结束,当足够多的进程可以执行计算的时候,开始计算,然后等待所有的进程完成计算,一起退出计算流程。
伪代码变量说明:
b 代表栅栏znode
p 代表客户端进程,所有的客户端进程在开始计算的时候注册自己,然后准备结束计算的时候注销的自己
x 代表最少需要开始计算流程的进程数量
开始计算
create a name n=b+"/"+p
set watch: exist(b+"/ready", true)
create child: create(n, EPHEMERAL)
L = getChildren(b, false)
if fewer children in L than x, wait for watch event
else create(b+"/"+ready)
计算结束
L = getChildren(b, false)
if no children , exit
if p is only process node in L, delete(n) and exit
if p is the lowest process node in L, wait on highest process node in P
else delete(n) if still exist and wait on lowest process node in L
goto 1
在准备开始计算的时候,所有的进程在栅栏节点下创建子节点表示准备计算了,并监听ready znode 的创建,前x-1个节点等待ready znode,第x个进程创建ready znode后大家一起开始计算;
//以下没理解,纯翻译文档 X_X
在退出计算的时候,不能和准备开始计算的时候创建一个ready标记来同步,因为使用的是临时性znode,如果在计算过程中有进程失败退出的话,就会出现问题。
进程在栅栏节点没有子节点的时候退出,为了效率,你也可以使用编号最低的节点作为标记节点,所有的可以退出的进程监听编号最低节点的消失,最小编号的节点等待任一进程的节点消失,这意味着每个节点的时候只有一个进程被唤醒。
分布式队列
这个比较简单了。
首先呢,定义一个znode来hold队列(hold不会翻),叫做队列节点,取名为queue。然后在入队的时候,创建叫做queue-X的序列临时性节点,这样就可以了(主要依赖了zk自身提供的序列节点的特性),在出对的时候,getChildren()就好了。
优先级分布式队列
基本同普通队列一样,znode 的前缀用queue-YY,YY是权重,然后入队或者出队的时候通知客户端重新获取队列节点。
分布式锁
创建序列znode,然后调用getChidren,如果自己创建的节点编号是最小的,那么获取锁成功,否则监听所有编号比自己小的节点中编号最大的那个,也就是刚好编号比自己小的那个节点。
释放锁的时候,删除自己拥有的节点就好了,此时刚好比自己大的节点会接受到通知。
共享锁(读写 锁)
获取读锁
1. 创建一个名为 guid-/read- 临时序列节点
2. 调用getChildren(),同时不能设置watcher(为了避免羊群效应)
3. 如果没有子节点叫做 write- , 而且还有比自己刚创建的节点的编号还小的节点,说明获取读锁成功。
4. 否则的话,调用exists()监听锁路径下以write开头的编号第二小的节点
5. 如果exists()返回false,回到step2
6. 否则等待前述设置的监听器的通知,然后回到step2
获取写锁
1. 调用create创建一个名为 write-的临时序列节点
2. 调用getChildren(),同时不能设置watcher(为了避免羊群效应)
3. 如果不存在比自己创建的节点编号更小的节点,说明获取读锁成功。
4. 调用exists()监听所有节点编号比自己小的所有节点中编号最大的那个节点
5. 如果exists()返回false,回到step2,否则等待通知后回到step2
实现2PC和Leader Election
基本思路都差不多,就不翻译了。
总结
zk实现分布式协调主要使用的特性是
- 对于节点的读写原子性
- 可以创建会话节点
- 可以创建序列节点,并且有zk来管理节点的序列号
- 可以设置监听器:这样就可以在某件事发生的时候确保客户端收到通知
这几点可以说是zk的核心特性,灵活的组合利用可以实现多种多样的分布式协调功能,另外有一点需要注意的是,如果有多个watcher同时监听某个节点,这样在节点事件发生的时候,多个监听器同时接收到通知,此时产生羊群效应,造成系统负载不合理的增加。在编写zk应用程序的时候需要注意到这一点。