深入浅出Zookeeper(二) 基于Zookeeper的分布式锁与领导选举

原创文章,转载请务必将下面这段话置于文章开头处。 本文转发自技术世界原文链接 http://www.jasongj.com/zookeeper/distributedlock/

Zookeeper特点

Zookeeper节点类型

如上文《Zookeeper架构及FastLeaderElection机制》所述,Zookeeper 提供了一个类似于 Linux 文件系统的树形结构。该树形结构中每个节点被称为 znode ,可按如下两个维度分类

  • Persist vs. Ephemeral
    • Persist节点,一旦被创建,便不会意外丢失,即使服务器全部重启也依然存在。每个 Persist 节点即可包含数据,也可包含子节点
    • Ephemeral节点,在创建它的客户端与服务器间的 Session 结束时自动被删除。服务器重启会导致 Session 结束,因此 Ephemeral 类型的 znode 此时也会自动删除
  • Sequence vs. Non-sequence
    • Non-sequence节点,多个客户端同时创建同一 Non-sequence 节点时,只有一个可创建成功,其它匀失败。并且创建出的节点名称与创建时指定的节点名完全一样
    • Sequence节点,创建出的节点名在指定的名称之后带有10位10进制数的序号。多个客户端创建同一名称的节点时,都能创建成功,只是序号不同

Zookeeper语义保证

Zookeeper 简单高效,同时提供如下语义保证,从而使得我们可以利用这些特性提供复杂的服务。

  • 顺序性 客户端发起的更新会按发送顺序被应用到 Zookeeper 上
  • 原子性 更新操作要么成功要么失败,不会出现中间状态
  • 单一系统镜像 一个客户端无论连接到哪一个服务器都能看到完全一样的系统镜像(即完全一样的树形结构)。注:根据上文《Zookeeper架构及FastLeaderElection机制》介绍的 ZAB 协议,写操作并不保证更新被所有的 Follower 立即确认,因此通过部分 Follower 读取数据并不能保证读到最新的数据,而部分 Follwer 及 Leader 可读到最新数据。如果一定要保证单一系统镜像,可在读操作前使用 sync 方法。
  • 可靠性 一个更新操作一旦被接受即不会意外丢失,除非被其它更新操作覆盖
  • 最终一致性 写操作最终(而非立即)会对客户端可见

Zookeeper Watch机制

所有对 Zookeeper 的读操作,都可附带一个 Watch 。一旦相应的数据有变化,该 Watch 即被触发。Watch 有如下特点

  • 主动推送 Watch被触发时,由 Zookeeper 服务器主动将更新推送给客户端,而不需要客户端轮询。
  • 一次性 数据变化时,Watch 只会被触发一次。如果客户端想得到后续更新的通知,必须要在 Watch 被触发后重新注册一个 Watch。
  • 可见性 如果一个客户端在读请求中附带 Watch,Watch 被触发的同时再次读取数据,客户端在得到 Watch 消息之前肯定不可能看到更新后的数据。换句话说,更新通知先于更新结果。
  • 顺序性 如果多个更新触发了多个 Watch ,那 Watch 被触发的顺序与更新顺序一致。

分布式锁与领导选举关键点

最多一个获取锁 / 成为Leader

对于分布式锁(这里特指排它锁)而言,任意时刻,最多只有一个进程(对于单进程内的锁而言是单线程)可以获得锁。

对于领导选举而言,任意时间,最多只有一个成功当选为Leader。否则即出现脑裂(Split brain)

锁重入 / 确认自己是Leader

对于分布式锁,需要保证获得锁的进程在释放锁之前可再次获得锁,即锁的可重入性。

对于领导选举,Leader需要能够确认自己已经获得领导权,即确认自己是Leader。

释放锁 / 放弃领导权

锁的获得者应该能够正确释放已经获得的锁,并且当获得锁的进程宕机时,锁应该自动释放,从而使得其它竞争方可以获得该锁,从而避免出现死锁的状态。

领导应该可以主动放弃领导权,并且当领导所在进程宕机时,领导权应该自动释放,从而使得其它参与者可重新竞争领导而避免进入无主状态。

感知锁释放 / 领导权的放弃

当获得锁的一方释放锁时,其它对于锁的竞争方需要能够感知到锁的释放,并再次尝试获取锁。

原来的Leader放弃领导权时,其它参与方应该能够感知该事件,并重新发起选举流程。

非公平领导选举

从上面几个方面可见,分布式锁与领导选举的技术要点非常相似,实际上其实现机制也相近。本章就以领导选举为例来说明二者的实现原理,分布式锁的实现原理也几乎一致。

选主过程

假设有三个Zookeeper的客户端,如下图所示,同时竞争Leader。这三个客户端同时向Zookeeper集群注册EphemeralNon-sequence类型的节点,路径都为/zkroot/leader(工程实践中,路径名可自定义)。

如上图所示,由于是Non-sequence节点,这三个客户端只会有一个创建成功,其它节点均创建失败。此时,创建成功的客户端(即上图中的Client 1)即成功竞选为 Leader 。其它客户端(即上图中的Client 2Client 3)此时匀为 Follower。

放弃领导权

如果 Leader 打算主动放弃领导权,直接删除/zkroot/leader节点即可。

如果 Leader 进程意外宕机,其与 Zookeeper 间的 Session 也结束,该节点由于是Ephemeral类型的节点,因此也会自动被删除。

此时/zkroot/leader节点不复存在,对于其它参与竞选的客户端而言,之前的 Leader 已经放弃了领导权。

感知领导权的放弃

由上图可见,创建节点失败的节点,除了成为 Follower 以外,还会向/zkroot/leader注册一个 Watch ,一旦 Leader 放弃领导权,也即该节点被删除,所有的 Follower 会收到通知。

重新选举

感知到旧 Leader 放弃领导权后,所有的 Follower 可以再次发起新一轮的领导选举,如下图所示。

从上图中可见

  • 新一轮的领导选举方法与最初的领导选举方法完全一样,都是发起节点创建请求,创建成功即为 Leader,否则为 Follower ,且 Follower 会 Watch 该节点
  • 新一轮的选举结果,无法预测,与它们在第一轮选举中的顺序无关。这也是该方案被称为非公平模式的原因

非公平模式总结

  • 非公平模式实现简单,每一轮选举方法都完全一样
  • 竞争参与方不多的情况下,效率高。每个 Follower 通过 Watch 感知到节点被删除的时间不完全一样,只要有一个 Follower 得到通知即发起竞选,即可保证当时有新的 Leader 被选出
  • 给Zookeeper 集群造成的负载大,因此扩展性差。如果有上万个客户端都参与竞选,意味着同时会有上万个写请求发送给 Zookeper。如《Zookeeper架构》一文所述,Zookeeper 存在单点写的问题,写性能不高。同时一旦 Leader 放弃领导权,Zookeeper 需要同时通知上万个 Follower,负载较大。

公平领导选举

选主过程

如下图所示,公平领导选举中,各客户端均创建/zkroot/leader节点,且其类型为EphemeralSequence

由于是Sequence类型节点,故上图中三个客户端均创建成功,只是序号不一样。此时,每个客户端都会判断自己创建成功的节点的序号是不是当前最小的。如果是,则该客户端为 Leader,否则即为 Follower。

在上图中,Client 1创建的节点序号为 1 ,Client 2创建的节点序号为 2,Client 3创建的节点序号为3。由于最小序号为 1 ,且该节点由Client 1创建,故Client 1为 Leader 。

放弃领导权

Leader 如果主动放弃领导权,直接删除其创建的节点即可。

如果 Leader 所在进程意外宕机,其与 Zookeeper 间的 Session 结束,由于其创建的节点为Ephemeral类型,故该节点自动被删除。

感知领导权的放弃

与非公平模式不同,每个 Follower 并非都 Watch 由 Leader 创建出来的节点,而是 Watch 序号刚好比自己序号小的节点。

在上图中,总共有 1、2、3 共三个节点,因此Client 2 Watch /zkroot/leader1Client 3 Watch /zkroot/leader2。(注:序号应该是10位数字,而非一位数字,这里为了方便,以一位数字代替)

一旦 Leader 宕机,/zkroot/leader1被删除,Client 2可得到通知。此时Client 3由于 Watch 的是/zkroot/leader2,故不会得到通知。

重新选举

Client 2得到/zkroot/leader1被删除的通知后,不会立即成为新的 Leader 。而是先判断自己的序号 2 是不是当前最小的序号。在该场景下,其序号确为最小。因此Client 2成为新的 Leader 。

这里要注意,如果在Client 1放弃领导权之前,Client 2就宕机了,Client 3会收到通知。此时Client 3不会立即成为Leader,而是要先判断自己的序号 3 是否为当前最小序号。很显然,由于Client 1创建的/zkroot/leader1还在,因此Client 3不会成为新的 Leader ,并向Client 2序号 2 前面的序号,也即 1 创建 Watch。该过程如下图所示。

公平模式总结

  • 实现相对复杂
  • 扩展性好,每个客户端都只 Watch 一个节点且每次节点被删除只须通知一个客户端
  • 旧 Leader 放弃领导权时,其它客户端根据竞选的先后顺序(也即节点序号)成为新 Leader,这也是公平模式的由来
  • 延迟相对非公平模式要高,因为它必须等待特定节点得到通知才能选出新的 Leader

总结

基于 Zookeeper 的领导选举或者分布式锁的实现均基于 Zookeeper 节点的特性及通知机制。充分利用这些特性,还可以开发出适用于其它场景的分布式应用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

3 条评论
登录 后参与评论

相关文章

来自专栏腾讯Bugly的专栏

《ios爆内存问题解决方案-OOMDetector组件》

组件介绍 在iOS App中,有两种闪退是让人深恶痛绝的,一种是异常退出,另外一种是爆内存杀进程。前者已经有完备的工具协助定位分析,而后者却一直是业界的难以治愈...

4894
来自专栏架构师之旅

Oracle的常见问题汇总(3) ——​数据库死锁原因及解决办法

数据库死锁原因及解决办法 所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时...

1615
来自专栏后端技术探索

基于Redis实现分布式消息队列(一)

1、为什么需要消息队列? 当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消息队列,作为抽象层,弥合双方的差异。

1043
来自专栏运维技术迷

Redis单线程架构

redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。 引出单线程模型 开启三个redis-cli客户端同时执行命令 客户端1设置一个字...

3348
来自专栏数据之美

Zookeeper 原理与实践

1、Zookeeper 的由来 在Hadoop生态系统中,许多项目的Logo都采用了动物,比如 Hadoop 和 Hive 采用了大象的形象,HBase 采用了...

3898
来自专栏架构师之路

消息总线能否实现消息必达?

一、缘起 上周讨论了两期环形队列的业务应用: 《高效定时任务的触发》 《延迟消息的快速实现》 两期的均有大量读者提问: 任务、延迟消息都放在内存里,万一重启了怎...

3906
来自专栏郝阳的专栏

关于分布式“缓存”的思考

本文从缓存的分类、同步和空查询三个问题分享下对分布式缓存的一些想法,抛砖引玉。

6230
来自专栏用户画像

3.1数据链路层的功能

数据链路层在物理层提供服务的基础上向网络层提供服务,其主要作用是加强物理层传输原始流的功能,将物理层提供的可能出错的物理连接改造成为逻辑上无差错的数据链路,使之...

531
来自专栏即时通讯技术

网络编程懒人入门(三):快速理解TCP协议一篇就够1、前言2、系列文章3、参考资料4、TCP 协议的作用5、TCP 数据包的大小6、TCP 数据包的编号(SEQ) 7、TCP 数据包的组装 8、慢启动

本系列文章的前两篇《网络编程懒人入门(一):快速理解网络通信协议(上篇)》、《网络编程懒人入门(二):快速理解网络通信协议(下篇)》快速介绍了网络基本通信协议及...

933
来自专栏即时通讯技术

网络编程懒人入门(四):快速理解TCP和UDP的差异

对于即时通讯开者新手来说,在开始着手编写IM或消息推送系统的代码前,最头疼的问题莫过于到底该选TCP还是UDP作为传输层协议。本文延续《网络编程懒人入门》系列文...

622

扫码关注云+社区