背景
咱们都知道 Zookeeper 是基于 ZAB 协议实现的,在介绍 ZAB 协议以前,先回顾一下 Zookeeper 的起源与发展。java
Zookeeper 到底是在什么样的时代背景下被提出?为了解决了哪些棘手的问题?面试
Zookeeper 最先起源于雅虎研究院的一个研究小组。当时,研究人员发现,在雅虎的不少大型系统基本都须要依赖一个相似的系统来进行分布式协调,可是这些系统都存在分布式单点问题,因此雅虎的开发人员就试图开发出一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。算法
因而,Zookeeper 就诞生了!数据库
Zookeeper 的出现不只解决了分布式系统下数据一致性的问题,并且经历过线上验证,不管是从性能、易用性、稳定性上来讲,都是工业级产品的标准。能够说在分布式系统中具备不可替代的核心地位,Hadoop、HBase、Storm 和 Solr 等大型分布式系统都已经将 Zookeeper 做为其核心组件,用于分布式协调。即使 Zookeeper 如此优秀,可是 Zookeeper 依然是免费且开源的,全世界成千上万的开发者都关注着它,陪同着一块儿成长和发展。缓存
做为一个开发者不管是为了应付面试、晋升仍是我的技术成长的须要,都须要对 Zookeeper 有所了解,而学习 Zookeeper 的关键就是理解其核心部分:服务器
ZAB 协议, 全称(Zookeeper Atomic Broadcast)Zookeeper 原子消息广播协议。微信
它与 Paxos 相似,也是一种数据一致性的算法。网络
Zookeeper应该具有的特性
在 ZAB 协议的开发设计人员在协议设计之初并无要求 ZAB 具备很好的扩展性,最初只是为了雅虎公司内部哪些高吞吐量、低延迟、健壮、简单的分布式系统场景设计的。基于 ZAB 协议,Zookeeper 实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性,简单架构图以下:架构
Zookeeper 用一个单一的主进程来接收并处理客户端的全部事物请求,并采用 ZAB 的原子广播协议将服务器数据状态以事物 Proposal 的形式广播到全部的副本进程中去。这样的模式就保证了,在同一时刻只有一个主进程来广播服务器的状态更变,所以可以很好地处理客户端大量的并发请求,这在 ZAB 协议中叫:消息广播。并发
除此以外,在分布式环境中事物的执行顺序也会存在必定的前后关系,好比:事务 C 的写入须要依赖事务 B 的写入,而事务 B 写入须要依赖事务 A 写入。这种先后依赖的顺序也对 ZAB 协议提出了一个要求:ZAB 协议须要保证若是一个状态的变动被处理了,那么全部其依赖的状态变动都已经被提早处理了。也就是须要顺序执行。
另外除了能正常广播消息、消息的顺序执行,主进程也可能随时会由于断电、机器宕机等异常状况没法提供服务,所以,ZAB 协议还须要作到在当前主进程出现上述异常状况的时候依然可以正常工做,这在 ZAB 协议中叫:崩溃恢复。
因此整个 ZAB 协议须要具有的核心特性已经被描述出来了,处理事务的请求的方式能够简单理解为:
> 全部的事务请求必须由一个全局惟一的服务器来协调处理,这样的服务器叫:Leader 服务器。其余的服务器被称为 Follower 服务器。Leader 服务器将客户端事务请求转化成一个事务 Prososal(提议),并将改 Proposal 分发给集群中全部的 Follower 服务器。以后 Leader 服务器接收了正确的反馈后,那么 Leader 就会再次向全部的 Follower 服务器分发 Commit 消息,要求将前一个 Proposal 提交。
这就简单阐述了ZAB 协议中消息广播模式的部份内容。
ZAB协议的两种模式
ZAB 协议的包括两种模式:崩溃恢复、消息广播。
既然有两种模式,那 Zookeeper 集群何时进入奔溃恢复模式?何时进入消息广播模式呢?
在进入奔溃恢复模式时 Zookeeper 集群会进行 Leader 选举,通常有两种状况会发生选举:
-
当服务器启动时期会进行 Leader 选举。
-
当服务器运行期 Leader 服务器的出现网络中断、奔溃退出、重启等异常状况,或者当集群中半数的服务器与该 Leader 服务器没法通讯时,进入崩溃恢复模式,开始 Leader 选举。
选举出 Leader 服务器后,会进入消息广播模式,开始接收处理客户端的请求,前文已经描述,这里再也不赘述。
相关名词概念
在深刻讲解 ZAB 协议的两个模式以前,先解释 Zookeeper 的几个相关概念,方便理解 ZAB 协议:
三种角色
在前面提到 Zookeeper 的集群中的服务器有 Leader 和 Follower ,但实际在 ZAB 协议中 Zookeeper 有三种角色,分别是 Leader、Folower、Observer,它们的分工各有不一样:
- Leader :负责整个Zookeeper 集群工做机制中的核心,主要工做有一下两个:
- 事务请求的惟一调度和处理者,保证集群事务处理的顺序性
- 集群内部各服务器的调度者
- Follower :它是 Leader 的追随者,其主要工做有三个:
- 处理客户端的非实物请求,转发事务请求给 Leader 服务器
- 参与事务请求 Proposal 的投票
- 参与 Leader 选举投票
- Observer :是 zookeeper 自 3.3.0 开始引入的一个角色,它不参与事务请求 Proposal 的投票,也不参与 Leader 选举投票,只提供非事务的服务(查询),一般在不影响集群事务处理能力的前提下提高集群的非事务处理能力。
三种状态
在知道了 Zookeeper 中有三种角色后,不经提问: Zookeeper 是如何知道本身目前是什么角色的呢?
在 ZAB 协议中定义:经过自身的状态来区分本身的角色的,在运行期间各个进程可能出现如下三种状态之一:
- LOOKING:处在这个状态时,会进入 Leader 选举状态
- FOLLOWER:Follower 服务器和 Leader 服务器保持同步时的状态
- LEADING:Leader 服务器做为主进程领导者的状态
在组成 ZAB 协议的全部进程启动的时候,初始化状态都是 LOOKING 状态,此时进程组中不存在 Leader,选举以后才有,在进行选举成功后,就进入消息广播模式,此时 Zookeeper 集群中的角色状态就再也不是 LOOKING 状态。
ZXID
前文咱们知道 zookeeper 消息有严格的因果关系,所以必须将每个事务请求按照前后顺序来进行排序与处理。那 Zookeeper 是如何保持请求处理的顺序的呢?其中很是关键的点就是 ZXID。
那 ZXID 到底是怎么发挥做用的呢?
Leader 服务器在接收到事务请求后,会为每一个事务请求生成对应的 Proposal 来进行广播,而且在广播事务 Proposal 以前,Leader 服务器会首先为这个事务 Proposal 分配一个全局单调递增的惟一 ID ,咱们称之为事务 ID(即 ZXID)。
ZXID 的设计也颇有特色,是一个全局有序的 64 位的数字,能够分为两个部分:
- 高 32 位是: epoch(纪元),表明着周期,每当选举产生一个新的 Leader 服务器时就会取出其本地日志中最大事务的 ZXID ,解析出 epoch(纪元)值操做加 1做为新的 epoch ,并将低 32 位置零。
- 低 32 位是: counter(计数器),它是一个简单的单调递增的计数器,针对客户端的每一个事务请求都会进行加 1 操做;
这里低 32 位 counter(计数器)单调递增还好理解,高 32 位 epoch(纪元)每次选举加 1 也许有些同窗就有疑问了,为何 epoch(纪元)每次选须要举加 1 ,它在整个 ZAB 协议中有什么做用?
咱们知道每当选举产生一个新的 Leader 服务器时生成一个新的 epoch(纪元)值,而在前文咱们知道,服务运行过程当中触发选举 Leader 的条件是:Leader 服务器的出现网络中断、奔溃退出、重启等异常状况,或者当集群中半数的服务器与该 Leader 服务器没法通讯时。
这说明整个 Zookeeper 集群此时处于一个异常的状况下,而在发生异常前,消息广播进行到哪一步骤咱们根本不知道,集群中的其余 Follower 节点从这种崩溃恢复状态从新选举出 Leader 后,若是老 Leader 又恢复了链接进入集群。此时老 Leader 的 epoch 确定会小于新 Leader 的 epoch,这时就将老 Leader 变成 Follower,对新的 Leader 进行数据同步。即使这时老 Leader 对其余的 Follower 节点发送了请求,Follower 节点也会比较 ZXID 的值,由于高 32 位加 1 了, Follower 的 epoch(纪元)大于老 Leader 的 epoch(纪元),因此 Follower 会忽略这个请求。
这像改朝换代同样,前朝的剑不能斩本朝的官。
消息广播模式
知道了这些名词,和上文提到的零散的知识点,其实崩溃恢复模式和消息广播模式的过程你们大体有所了解了。
先看看消息广播模式吧!
消息广播的模式的过程简图以下所示:
整个过程相似一个二阶段提交的过程,但却有所不一样,ZAB 协议简化了二阶段提交模型,在超过半数的 Follower 服务器已经反馈 ACK 以后就开始提交事务 Prososal 了,无需等待全部服务器响应。
结合上图,看看消息广播的具体细节:
- Leader 服务器接收到请求后在进行广播事务 Proposal 以前会为这个事务分配一个 ZXID,再进行广播。
- Leader 服务器会为每一个 Follower 服务器都各自分配一个单独的队列,而后将须要广播的事务 Proposal 依次放入这些队列中去,并根据 FIFO 策略进行消息的发送。
- 每一个Follower 服务器在接收到后都会将其以事务日志的形式写入到本地磁盘中,而且在成功写入后返回 Leader 服务器一个 ACK 响应。
- 当有超过半数的服务器 ACK 响应后,Leader 就会广播一个 Commit 消息给全部的 Follower 服务器,Follower 接收到后就完成对事务的提交操做。
在画一张详细点的流程图,更直观:
这就完成了整个消息广播了!
崩溃恢复模式
前文已经反复提过崩溃恢复模式了,其实就是从新选举出新的 Leader 服务器,选举完成后 Follower 服务器在再去同步 Leader 的数据。
运行中的服务再次进行从新选举,必定是出现某种异常,咱们知道在出现异常状况以前 Leader 的消息广播可能会处在任何一个阶段,有可能客户端的请求只是在 Leader 服务器上提出并未被提交,也可能请求已经被 Leader 服务器提交。
ZAB 协议对于不一样阶段的出现的数据不一致的状况作了兼容,保证:
- 已经在 Leader 服务器上提交的事务,最终会被全部服务器都提交
- 只在 Leader 服务器上提出的事务,要丢弃
针对以上的两个要求,在进行 Leader 选举时,之须要选举出集群中 ZXID 最大的事务 Proposal 便可,这样就能够省去 Leader 服务器检查 Proposal 的提交和丢弃工做了。由于 Leader 服务器的事务是最大的,一切以 Leader 服务器的数据为标准便可。
ZXID 在集群中其实并非惟一的,因此也有可能出现多 Follower 服务器 ZXID 相同的状况,这时候就须要比较 Zookeeper 的 SID 值。什么是 SID?SID 是一个数字,和 zookeeper 的 myid 一致,myid 就不要解释了,安装过 Zookeeper 的都知道,每台服务器都须要配置一个这样的文件,里面只有一个数字,用来标识这台服务器。由于每台机器的 myid 配置都不同,因此集群选举的时不会出现相等的状况。
选举时,比较大小的源码以下:
前面已经说过,出现选举 Leader 可能会出现两种状况:
- 服务启动时期,发起选举
- 服务运行期间,出现异常,发起选举
但不管是启动期仍是运行期进行 Leader 选举,其选举过程都差不太多,我简单画个流程图:
结合上图,奔溃恢复模式下 Leader 选举的过程细节以下:
- 检测节点处于 LOOKING 阶段,开发选举 Leader
- 发起投票时有两种状况:
- 在服务启动的初始阶段,每一个服务器都会投票给本身以(myid,zxid)的信息形式发送,那初始阶段没有 zxid 值,就会发送(myid,0)
- 在服务器运行期间,每一个服务器上的 zxid 都有值,且 zxid 都不相同,因此就正常发送(myid,zxid)
- 各节点收到信息后将收到的(myid,zxid)和本身的比较,比较的过程前面已经说过,这里再也不赘述
- 而后判断是否有半数的机器投票选出 Leader,若是否,在进入新一轮投票,直到选出
- 选出 Leader 后,其余节点就变成 Follower 角色,并向 Leader 发送本身服务器的最大 zxid ,Leader 服务器收到后会和本身本地的提议缓存队列进行比较,判断使用那种策略进行同步(后面详细说明同步的四种策略)
- 当同步完成,集群就能够正常的处理请求了,就进入消息广播模式了。
这就是崩溃恢复模式下选举 Leader 的过程了!
下面再简单介绍下数据同步的四种策略,这四种同步策略保证了Zookeeper 集群中的数据一致性,也解决了前文提出的两个问题,兼容了各类数据不一致的场景。
数据同步的四种策略
在数据同步以前,Leader 服务器会进行数据同步的初始化,首先会从 Zookeeper 的内存数据库中提取出事务前期对应的提议缓存队列,同时会初始化三个 ZXID 的值:
- peerLastZxid:这是 Follower 的最后处理 ZXID
- minCommittedLog:Leader 服务器的提议缓存队列中 最小的 ZXID
- maxCommittedLog:Leader 服务器的提议缓存队列中 最大的 ZXID
根据这三个参数,就能够肯定四种同步方式,分别为:
- 直接差别化同步
- 场景:当 minCommittedLog < peerLastZxid < maxCommittedLog 时
- 先回滚在差别化同步
- 场景:假如集群有 A、B、C 三台机器,此时 A 是 Leader 可是 A 挂了,在挂以前 A 生成了一个提议假设是:03,而后集群有从新选举 B 为新的 Leader,此时生成的的提议缓存队列为:01~02,B 和 C 进行同步以后,生成新的纪元,ZXID 从 10 开始计数,集群进入广播模式处理了部分请求,假设如今 ZXID 执行到 15 这个值,此时 A 恢复了加入集群,这时候就比较 A 最后提交的 ZXID:peerLastZxid 与 minCommittedLog、maxCommittedLog 的关系。此时虽然符合直接差别化同步:minCommittedLog < peerLastZxid < maxCommittedLog 这样的关系,可是提议缓存队列中却没有这个 ZXID ,这时候就须要先回滚,在进行同步。
- 仅回滚同步
- 场景:这里和先回滚在差别化同步相似,直接回滚就能够。
- 全量同步
- 场景:peerLastZxid < minCommittedLog,当远远落后 Leader 的数据时,直接全量同步。
这就是四种同步策略,这几种同步方式也解决了上文提出的问题:
- 只在 Leader 服务器上提出的事务,要丢弃(这个问题会在同步时,会进行回滚,使得只在 Leader 服务器上提出的事务丢弃)
这些就是整个 ZAB 协议中崩溃恢复的内容。
ZAB协议和Paxos算法的区别
ZAB协议看起来和Paxos有着相同之处,但它并非Paxos的典型实现,其实仍是有一些区别,ZAB协议中额外添加了一个同步的阶段,二者设计目标也不太同样,ZAB协议主要用于构建一个高可用的分布式数据主备系统,而Paxos算法则是用于构建一个分布式一致性的状态机。
总结
Zookeeper 做为出色的分布式协调服务,目前读 QPS 达到 12w,出色的性能也让开发者更加青睐,其 ZAB 协议的核心分为两个部分:崩溃恢复、消息广播。
典型的应用场景有:
- 数据发布/订阅、负载均衡
- 命名服务
- 分布式协调通知
- 集群管理
- Master选举
- 分布式锁
- 分布式队列
- 用 Zookeeper 避免脑裂
除此以外在大数据领域也有应用,例如:
- Hadoop
- HBase
- Kafka
在阿里巴巴集团内部实践的 Zookeeper 的产品也有不少,如:
- 消息中间件:Metamorphosis
- RPC 服务框架:Dubbo
- 基于 MySQL Binlog 的增量订阅和消费组件:Cancel
- 分布式数据库同步系统:Otter
- 实时计算引擎:JStorm
你知道的越多,你不知道的越多,我是帅帅,一个在互联网苟且偷生的工具人,欢迎关注个人技术微信公众号:java之旅