一、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 服务器(写任意:对数据的修改可提交给任意的节点)。然后再执行如下的具体过程:
- Leader 接收到事务请求后,为事务赋予一个全局唯一的 64 位自增 id,即 zxid,通过 zxid 的大小比较即可实现事务的有序性管理,然后将事务封装为一个 Proposal。
- Leader 根据 Follower 列表获取到所有 Follower,然后再将 Proposal 通过这些 Follower 的 队列将提案发送给各个 Follower。
- 当 Follower 接收到提案后,会先将提案的 zxid 与本地记录的事务日志中的最大的 zxid 进行比较。若当前提案的 zxid 大于最大 zxid,则将当前提案记录到本地事务日志中,并 向 Leader 返回一个 ACK。
- 当 Leader 接收到过半的 ACKs 后,Leader 就会向所有 Follower 的队列发送 COMMIT 消息,向所有 Observer 的队列发送 Proposal。
- 当 Follower 收到 COMMIT 消息后,就会将日志中的事务正式更新到本地。当 Observer 收到 Proposal 后,会直接将事务更新到本地。
- 无论是 Follower 还是 Observer,在同步完成后都需要向 Leader 发送成功 ACK。
- ZAB的选主流程
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:
- 选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
- 选举线程首先向所有Server发起一次询问(包括自己);
- 选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
- 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
- 线程将当前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就进入状态初始同步过程:
- leader等待server连接
- Follower连接leader,将最大的zxid发送给leader;
- Leader根据follower的zxid确定同步点(leader的zxid是最大的,从而可以将需要同步的内容发送给follower);
- 完成同步后通知follower 已经成为uptodate状态;
- Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
初始同步的两个原则:
- 已被处理过的消息不能丢
当 Leader 收到超过半数 Follower 的 ACKs 后,就向各个 Follower 广播 COMMIT 消息, 批准各个 Server 执行该写操作事务。当各个 Server 在接收到 Leader 的 COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。
但是如果在非全部 Follower 收到 COMMIT 消息之前 Leader 就挂了,这将导致一种后 果:部分 Server 已经执行了该事务,而部分 Server 尚未收到 COMMIT 消息,所以其并没有 执行该事务。当新的 Leader 被选举出,集群经过恢复模式后需要保证所有 Server 上都执行 了那些已经被部分 Server 执行过的事务。 - 被丢弃的消息不能再现
当在 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.youkuaiyun.com/lingbo229/article/details/81052078
https://blog.youkuaiyun.com/qq_24313635/article/details/113941996
https://blog.youkuaiyun.com/wangxuelei036/category_10152721.html