Zookeeper设计及工作原理

一、Zookeeper概述

Zookeeper 是一个分布式服务框架,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

1.应用场景
  • 统一命名服务:在zookeeper的文件系统里创建一个命名节点,各机器通过监听该节点获取统一命名
  • 配置管理:程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。
  • 集群管理:所谓集群管理无在乎两点:是否有机器退出和加入、选举master。
    • 对于第一点,所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入 也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了。
    • 对于第二点,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。
  • 分布式锁: 将zookeeper上的一个znode看作是一把锁,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,其他未获得锁的会去监听比它稍大的节点;当锁被释放后,就会通知次小的节点获取锁
  • 队列管理:队列按照 FIFO 方式进行入队和出队操作,入列有编号,出列按编号。
2. Zookeeper设计

简单的说,zookeeper=文件系统+通知机制

文件系统:Zookeeper维护一个类似文件系统的数据结构:
在这里插入图片描述

每个子目录项如 NameService 都被称作为 znode,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:

  • PERSISTENT-持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL-临时目录节点:客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

通知机制:客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

二、数据一致性与分布式协调

当 Zookeeper 为集群提供分布式协调服务时,为保证高可用性,一般不可能单个部署,Zookeeper通常也会集群部署。那么如何保证Zookeeper集群中的各个Zookeeper节点数据的一致,从而为外部集群提供统一的服务,是必须要考虑的问题。

1.分布式一致性协议

分布式一致性协议是一个研究了很久的问题,主要分为单主一致性算法和多主一致性算法,具体可以参照之前的博客:https://zhuanlan.zhihu.com/p/130974371

Zookeeper就是在单主一致性算法Paxos的基础上实现了ZAB协议,从而保证数据一致性

2.ZAB协议

ZAB协议,全称 Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。它是专门为分布式协调服务——Zookeeper,设计的一种支持崩溃恢复和原子广播的协议

  • ZAB协议的设计目标

    • 最终一致性!!!:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能
    • 可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
    • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口
    • 等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
    • 原子性:更新只能成功或者失败,没有中间状态。
    • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
  • ZAB协议的角色
    Zookeeper中的角色主要有以下三类:
    在这里插入图片描述
    整个ZAB协议一共定义了三个阶段:

  • 发现:要求zookeeper集群必须选举出一个 Leader ,同时 Leader 会维护一个 Follower 可用客户端列表。将来客户端可以和这些 Follower节点进行通信。

  • 同步:Leader 要负责将本身的数据与 Follower 完成同步,做到多副本存储。这样也是提现了CAP中的高可用和分区容错。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中

  • 广播:Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower。

三个阶段执行完为一个周期,在Zookeeper集群的整个生命周期中,这三个阶段会不断进行,如果Leader崩溃或因其它原因导致Leader缺失,ZAB协议会再次进入阶段一。

  • zxid
    为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上 了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个 新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数

  • ZAB的正常工作流程(二阶段提交,半数以上通过)
    如果集群中的 Learner 节点收到客户端的事务请求,那么这些 Learner 会将请求转发给 Leader 服务器(写任意:对数据的修改可提交给任意的节点)。然后再执行如下的具体过程:

  1. Leader 接收到事务请求后,为事务赋予一个全局唯一的 64 位自增 id,即 zxid,通过 zxid 的大小比较即可实现事务的有序性管理,然后将事务封装为一个 Proposal
  2. Leader 根据 Follower 列表获取到所有 Follower,然后再将 Proposal 通过这些 Follower 的 队列将提案发送给各个 Follower。
  3. 当 Follower 接收到提案后,会先将提案的 zxid 与本地记录的事务日志中的最大的 zxid 进行比较。若当前提案的 zxid 大于最大 zxid,则将当前提案记录到本地事务日志中,并 向 Leader 返回一个 ACK
  4. 当 Leader 接收到过半的 ACKs 后,Leader 就会向所有 Follower 的队列发送 COMMIT 消息,向所有 Observer 的队列发送 Proposal。
  5. 当 Follower 收到 COMMIT 消息后,就会将日志中的事务正式更新到本地。当 Observer 收到 Proposal 后,会直接将事务更新到本地。
  6. 无论是 Follower 还是 Observer,在同步完成后都需要向 Leader 发送成功 ACK。
  • ZAB的选主流程
    当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:
  1. 选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
  2. 选举线程首先向所有Server发起一次询问(包括自己);
  3. 选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
  4. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server
  5. 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。

通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.

fast paxos流程:在选举过程中,某Server首先向所有Server提议自己要成为leader(epoch+1),当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。其流程图如下所示:在这里插入图片描述

  • ZAB崩溃恢复

当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式。首先,集群会根据上述流程选出一个新的leader。选完leader以后,zk就进入状态初始同步过程

  1. leader等待server连接
  2. Follower连接leader,将最大的zxid发送给leader;
  3. Leader根据follower的zxid确定同步点(leader的zxid是最大的,从而可以将需要同步的内容发送给follower);
  4. 完成同步后通知follower 已经成为uptodate状态;
  5. Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

初始同步的两个原则:

  1. 已被处理过的消息不能丢
    当 Leader 收到超过半数 Follower 的 ACKs 后,就向各个 Follower 广播 COMMIT 消息, 批准各个 Server 执行该写操作事务。当各个 Server 在接收到 Leader 的 COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。
    但是如果在非全部 Follower 收到 COMMIT 消息之前 Leader 就挂了,这将导致一种后 果:部分 Server 已经执行了该事务,而部分 Server 尚未收到 COMMIT 消息,所以其并没有 执行该事务。当新的 Leader 被选举出,集群经过恢复模式后需要保证所有 Server 上都执行 了那些已经被部分 Server 执行过的事务
  2. 被丢弃的消息不能再现
    当在 Leader 新事务已经通过,其已经将该事务更新到了本地,但所有 Follower 还都没 有收到 COMMIT 之前,Leader 宕机了(比前面叙述的宕机更早),此时,所有 Follower 根本 就不知道该 Proposal 的存在。当新的 Leader 选举出来,整个集群进入正常服务状态后,之 前挂了的 Leader 主机重新启动并注册成为了 Follower。若那个别人根本不知道的 Proposal 还保留在那个主机,那么其数据就会比其它主机多出了内容,导致整个系统状态的不一致。 所以,该 Proposa 应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的, 应该被清除。
  • ZAB协议和Raft协议的区别

领导者选举

  • ZAB 采用的“见贤思齐、相互推荐”的快速领导者选举(Fast Leader Election),节点间通过PK竞争(资本是所持有的信息)看哪个节点更适合做Leader,一个节点PK后,会将选票信息广播出去,最终选举出了大多数节点中数据最完整的节点
  • Raft 采用的是“一张选票、先到先得”的自定义算法(注:里面包含了一个随机等待时间的概念,来保证最多几次选举就能完整选举过程。),这里简单说一下就是一个节点发现leader挂了,就选举自己为leader,然后通知其他节点,其他节点把选票投给第一个通知它的节点。(注:这里其实也会涉及到PK,根据数据的完整性以及任期等信息,如果通知它的节点 没有当前节点的数据完整等那么 当前节点是不会将选票投给该节点)

日志复制

  • Raft 和 ZAB 相同,都是以领导者的日志为准来实现日志一致,而且日志必须是连续的,也必须按照顺序提交。
  • ZAB通过TCP来保证操作的顺序性。
  • Raft协议通过Log Entry 加自己的校验来实现日志的连续性。

读操作和一致性

  • ZAB 的设计目标是操作的顺序性,在 ZooKeeper 中默认实现的是最终一致性,读操作可以在任何节点上执行;(注:很多地方说ZK是CP这没有毛病,但是并不是指ZK中的读写时强一致性,是指在发生P的时候,ZK是C)
  • 而 Raft 的设计目标是强一致性(也就是线性一致性),所以 Raft 更灵活(可以自己配置),Raft 系统既可以提供强一致性,也可以提供最终一致性,但是一般为了保证性能,默认提供的也是最终一致性

其他

  • 相比 ZAB,Raft 的设计更为简洁,比如 Raft 没有引入类似 ZAB 的成员发现和数据同步阶段,而是当节点发起选举时,递增任期编号,在选举结束后,广播心跳,直接建立领导者关系,然后向各节点同步日志,来实现数据副本的一致性。
  • ZAB协议的数据同步的阶段,ZAB集群式无法对外提供服务。
  • ZAB 和 ZooKeeper 强耦合,你无法在实际系统中独立使用;而 Raft 的实现(比如 Hashicorp Raft)是可以独立使用的,编程友好。
  • raft 协议的心跳是从 leader 到 follower, 而 zab 协议则相反

参考文献:
https://blog.csdn.net/lingbo229/article/details/81052078
https://blog.csdn.net/qq_24313635/article/details/113941996
https://blog.csdn.net/wangxuelei036/category_10152721.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值