基础

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的只读请求在各节点本地处理,变更请求被转发到主节点处理。

变更请求的处理过程:

  1. 处理变更请求生成一个事务,事务是一些原子的幂等的状态更新
  2. 将事务数据追加到事务日志
  3. 实际修改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——分布式过程协同技术详解》