基础
znode
znode共4种类型:持久的、临时的、持久有序的、临时有序的。
每个znode都有一个版本号,每次数据变化时自增。setData/delete只有当传入参数的版本号与服务器上的版本号一致时才会修改成功。
会话过期时间
服务端若t时间未收到会话的任何消息,会话过期;
客户端若t/3时间未收到任何消息,将发送心跳消息;2t/3时间后尝试连接其他服务器,所连服务器的最近更新时间zxid≥客户端所见的zxid。
监视点
监视点是一个单次触发的操作。
为了接收多个通知,客户端在接收通知时要重读节点状态并设置新监视点。重读节点状态是因为,在设置新监视点前节点状态可能变更。
每个监视点都与会话关联,会话过期时监视点将被删除。
监视点有两种:数据监视点和子节点监视点,注意不能直接监视孙子。
实现Watcher接口只要实现一个函数:void process(WatchedEvent event);
n个客户端抢夺锁
-
都创建/lock节点,如果节点已存在就监听这个节点的删除事件(导致一个节点上大量监视点)
LOCK acquire 1 if create("f", ephem=True) return 2 if exists("f", watch=True) wait 3 goto line 1 -
创建/lock/lock-有序节点,通过getChildren方法获取/lock下的所有子节点,判断自己创建的节点是不是序号最小的。如果不是,就根据序号确定顺序,在前一个节点上设置监视点。
LOCK w/o HERD (SCABLABLE LOCK) acquire 1 create ephem seq 'f' 2 list f* 3 if no lower #file, return 4 if exists(next lower #, watch=True) 5 wait 6 goto line 2
客户端重连时重建监视点
客户端在连接新的服务器时,会发送监视点列表和最后已知zxid。服务端检查列表中znode节点的修改时间,如果>zxid就触发这个监视点。
特殊情况:客户端用exists操作在不存在的节点上设置监视点,然后在客户端失联又重连的这段时间,有其他客户端先创建再删除这个被监视节点,则重连后的客户端会错过这两个事件。
fencing令牌
将新znode主节点的czxid(创建该节点时的zxid)作为递增的fencing令牌,写外部存储时附带该令牌,外部存储拒绝更小fencing令牌的写入。
内部原理
对znode的只读请求在各节点本地处理,变更请求被转发到主节点处理。
变更请求的处理过程:
- 处理变更请求生成一个事务,事务是一些原子的幂等的状态更新
- 将事务数据追加到事务日志
- 实际修改zookeeper数据树
leader选举
事务标识符zxid是一个64位整数,分为epoch、counter两部分,epoch在每次新leader选举时加1
选Lamport时间戳(最新事务标识zxid,服务器标识sid)最大的做leader
- 每个节点进入LOOKING状态,向其他节点广播(zxid, sid)
- 延迟200ms,收到其他节点广播的(zxid, sid),若收到增大的时间戳则再广播出去
- 若收到过半数同样的时间戳,则选出了leader
Zab
ZooKeeper原子广播(Zookeeper Atomic Broadcast),类似raft
leader向follower发送提议日志,收到过半数响应后通知follower提交
快照
ZooKeeper在进行快照时还会继续处理请求(数据树还在变),所以快照不能反映出给定时间点的准确状态。这不是问题,因为服务器会重放快照开始时间之后的所有事务(事务是幂等的)。
参考
- 《ZooKeeper——分布式过程协同技术详解》