zookeeper典型应用场景

Posted by CHuiL on July 13, 2021

zookeeper依靠他对znode操作事务的原子性和顺序性,可以用来帮助实现

  • 数据订阅和发布
  • 负载均衡
  • 命名服务
  • 集群管理
  • Master选取
  • 全局唯一ID
  • 分布式锁
  • 分布式队列

其中大部分都是依靠znode的特性,如唯一性,临时节点和watcher通知机制。
数据订阅和发布就1是将配置数据写入到znode上,并且关系该值的客户端可以watch该节点,一旦该节点发生变动,就会发出通知,客户端就可以依靠该通知来触发获取znode上的最新数据。
image

命名服务*常应用于RPC,服务提供者在启动的时候会在zk上以自己的名字创建节点,并且将自己的信息,如ip地址和端口等写入到该节点。然后服务消费者可以直接根据名字从对应的znode上获取需要的信息,并且可以设置对该znode的watch以监听该节点的变化。

全局唯一ID则是利用znode的顺序节点,创建顺序节点时会一个自增的id作为该节点的后缀。并且该id是全局唯一。可以利用该特性来生成分布式环境下的全局唯一节点。

Master选取则利用节点的唯一性,对于每个可能成为master的机器,一起竞争创建一个节点,创建成功的自然就成为master了。

集群管理要做到对集群的管理,控制和监控。我们肯定需要一个监控中心,并且有一个对应的znode节点,/machines ,监控中心watch该节点。当有新的机器加入时,该机器会主动在该节点下创建一个临时节点/machines/[M1],监控中心会收到通知,并且该机器会主动将自己的状态写入到zk上的对应节点,监控中心同样通过watch这些节点来获取机器的状态。当机器下线后,客户端与zk断开连接,临时节点也就被删除了,监控中心同样会收到通知。

image

分布式队列 使用顺序临时节点,入队操作就是创建节点,然后获取子节点列表,如果自己是序号最小的节点,则等待并且watch比自己小的最后一个节点。出队就是队首的客户端完成操作断开连接自动删除临时节点。

分布式锁

分布式锁主要通过znode唯一性和严格的顺序性来实现的。

排他锁

排他锁很好理解,在获取锁的过程就是创建临时znode的过程,创建成功就成功获得锁,创建失败则watch该锁,等收到的通知(会发出通知只有该节点被删除时),则再次尝试创建节点,重复直到成功获得锁。

释放锁就是删除该节点的过程,而客户端在以下两种情况下会释放锁

  • 正常业务逻辑执行结束,客户端主动断开连接,临时节点被删除
  • 客户端宕机,连接断开,临时节点删除。

问题

  • 如果客户端阻塞,迟迟不释放锁怎么办???,没有redis类似的超时机制怎么办?

image

共享锁

共享锁的实现很有意思,根据顺序节点来创建。共享锁分为读锁和写锁,读锁之间可以共享,写锁是排他的。这里在创建锁的时候,根据类型可以创建对应的顺序临时节点,如读锁就是/share_lock/R-00000001, 写锁就是/share_lock/W-00000001;
获取锁的流程

  1. 根据所需要的锁创建对应的临时节点
  2. 获取/share_lock的所有子节点列表
  3. 对于读请求,如果没有比自己序号小的子节点 或者 所有比自己小的子节点都是读节点,则获取读锁成功。如果有比自己序号小的写节点,则需要进入等待。
  4. 对于写请求,如果自己不是序号最小的节点,则获取写锁失败,进入等待,如果是则获取锁成功。
  5. 如果需要进入等待,则watch /share_lock节点。接受到该节点子节点变化通知后,重复步骤2.

释放锁的流程 与排他锁一致,都是与连接断开之后,临时节点被删除。

羊群效应 image 这里第一个子节点删除之后,余下4台机器都会收到通知,然后重新获取子节点列表,但是只有对192.168.0.2这台机器有意义,因为他判断到自己就是序号最小的写节点了,他成功获得写锁,而余下的机器被唤醒但是又要重新进入阻塞,没有意义。观察可以发现,往后的操作都会唤醒所有,但是并不是对所有的机器都有意义。
如果集群规模比较大,会对zk服务器造成巨大的性能影响和网络冲击,更严重的情况下,如果有多个客户端同时完成事务释放了连接使得节点被删除,那么zk服务器就会在短时间内向其余的客户端发送大量的事件通知,这就是所谓的羊群效应。

这里我们发现,

  • 对于读请求,他其实只要关心比自己序号小的最后一个写请求节点即可,
  • 写请求则关心序号比自己的小的最后一个节点即可。

这样通知就会更有针对性,只会通知那些真正有意义的节点。