概况
ZooKeeper
是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于 Paxos
算法的 ZAB
协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
ZooKeeper是一个开源的分布式协调服务,设计目标是将那些复杂且容器出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
另外,ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
特点
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
应用场景
数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
三个典型的应用场景:
- 分布式锁 : 通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
- 命名服务 :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
- 数据发布/订阅 :通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
https://snailclimb.gitee.io/javaguide/#/docs/system-design/distributed-system/zookeeper/zookeeper-plus
zookeeper更适合以查询为主的场景
横向拓展 只能提高非事务请求
如何选择节点:随机、轮询、算法
zk适合读请求多的需求
非事务反馈比较快
客户端选择一个服务端提供服务
如果客户端做负载均衡,他会获得zk集群所有节点的使用情况,这样任意一个客户端访问server,发生改变,就会通知所有节点,去通知client修改负载的状况
树形结构组织数据,每个节点只能存储少量数据。
每个几点在zookeeper叫做znode,并且有一个唯一的路径标识,只能包含少量数据,可以创建子节点
znode
znode有两种类型,短暂的,持久的
有四种类型的znode:
-
PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
-
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
-
EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
-
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
临时节点后面不能创建子节点
持久化节点只有客户端手动删除,才会删除
角色
没有传统的Master/Slave概念,而是引入了Leader、Follower和Observer三种角色。
Leader:为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
Follower:为客户端提供读服务,如果是写服务则转发给Leader。在选举过程中参与投票。
Observer:为客户端提供读服务,如果是写服务则转发给Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
这个过程大致是这样的:
1、Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超过半数节点的票数,它就可以当选准leader。
2、Discovery(发现阶段) :在这个阶段,followers跟准leader进行通信,同步followers最近接受的事务提议。
3、Synchronization(同步阶段) :同步阶段主要利用leader前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后,准leader才会成为真正的leader。
4、Broadcast(广播阶段) :到这个阶段,ZooKeeper集群才能正式对外提供事务服务,并且leader可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
服务器状态
- LOOKING :寻找 Leader。
- LEADING :Leader 状态,对应的节点为 Leader。
- FOLLOWING :Follower 状态,对应的节点为 Follower。
- OBSERVING :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
奇数台
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。 比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。 假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。
综上,何必增加那一个不必要的 ZooKeeper 呢?
读写机制
- zookeeper是一个由多个server组成的集群
- 一个leader,多个follower
- 每个server保存一份数据副本
- 全局数据一致
- 分布式读写
- 更新请求转发,由leader实施
保证
- 更新请求顺序进行(有一个队列,存放请求),来自同一个client的更新请求按其发送顺序依次执行
- 数据更新原子性,要么成功,要么失败
- 全局唯一数据视图,无论client连接哪个server,数据视图都是一致的
- 实时性,在一定事件范围内,client能读到最新数据
Watcher
是zookeeper一个核心功能,Watcher可以监控目录节点的数据变化以及目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点的Watcher,从而每个客户端都很快知道所关注的目录节点的状态发生变化,从而做出相应的反应
Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
2PC
两阶段提交
2-phase-commit,即阶段提交。
抽象出的协调者,也就是zk的leader的由来
阶段一:提交事务请求
当要执行一个分布式事务的时候,事务发起者首先向协调者发起事务请求,然后协调者会给所有参与者发送 prepare
请求(其中包括事务内容)告诉参与者你们需要执行事务了,如果能执行我发的事务内容那么就先执行但不提交,执行后请给我回复。然后参与者收到 prepare
消息后,他们会开始执行事务(但不提交),并将 Undo
和 Redo
信息记入事务日志中,之后参与者就向协调者反馈是否准备好了。
1、事务询问
协调者向所有参与者发送事务请求,询问是否可以执行事务请求操作,并等待各参与者的相应
2、执行事务
各参与者节点执行事务操作,并将Undo和Redo信息写入事务日志(便于回滚)
3、各参与者向协调者反馈事务询问的请求
如果参与者成功执行事务操作,就反馈给协调者Yes相应,表示事务可以执行里如果参与者没有成功执行事务操作,就反馈给协调者No相应,表示事务不可以执行
阶段二:执行事务的提交
协调者根据各参与者的反馈情况来决定是否可以进行事务提交操作
会有两种可能
执行事务提交
如果协调者从所有参与者获得的反馈都是Yes相应,就执行事务提交
-
发送提交请求
协调者向所有参与者节点发出Commit请求
-
事务提交
参与者收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的食物资源
-
反馈事务提交结果
参与者在完成事务提交之后,向协调者发送ACK消息
-
完成事务
协调者收到所有参与者反馈的ACK消息后,完成事务
回滚事务
而如果在第一阶段并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送 回滚事务的 rollback
请求,参与者收到之后将会 回滚它在第一阶段所做的事务处理 ,然后再将处理情况返回给协调者,最终协调者收到响应后便给事务发起者返回处理失败的结果。
优点:原理简单,实现方便
缺点:同步阻塞、单点问题、脑裂、太过保守
- 单点故障问题,如果协调者挂了那么整个系统都处于不可用的状态了。
- 阻塞问题,即当协调者发送
prepare
请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。 - 数据不一致问题,比如当第二阶段,协调者只发送了一部分的
commit
请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
两阶段提交在MySQL的应用
3PC
因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 3PC(三阶段提交) 。那么这三阶段又分别是什么呢?
千万不要吧PC理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
- CanCommit阶段:协调者向所有参与者发送
CanCommit
请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。 - PreCommit阶段:协调者根据参与者返回的响应来决定是否可以进行下面的
PreCommit
操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送PreCommit
预提交请求,参与者收到预提交请求后,会进行事务的执行操作,并将Undo
和Redo
信息写入事务日志中 ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 任何一个 NO 的信息,或者 在一定时间内 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。 - DoCommit阶段:这个阶段其实和
2PC
的第二阶段差不多,如果协调者收到了所有参与者在PreCommit
阶段的 YES 响应,那么协调者将会给所有参与者发送DoCommit
请求,参与者收到DoCommit
请求后则会进行事务的提交工作,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在PreCommit
阶段 收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应 ,那么就会进行中断请求的发送,参与者收到中断请求后则会 通过上面记录的回滚日志 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
这里是
3PC
在成功的环境下的流程图,你可以看到3PC
在很多地方进行了超时中断的处理,比如协调者在指定时间内为收到全部的确认消息则进行事务中断的处理,这样能 减少同步阻塞的时间 。还有需要注意的是,3PC
在DoCommit
阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交。为什么这么做呢?是因为这个时候我们肯定保证了在第一阶段所有的协调者全部返回了可以执行事务的响应,这个时候我们有理由相信其他系统都能进行事务的执行和提交,所以不管协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
总之,3PC
通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 PreCommit
阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
paxos
拜占庭将军问题
Paxos
算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一,其解决的问题就是在分布式系统中如何就某个值(决议)达成一致 。
paxos解决的问题是如何在一个可能发生宕机或者网络异常的分布式系统中,快速且正确的在集群内部对某个数据的值达成一致。
在 Paxos
中主要有三个角色,分别为 Proposer提案者
、Acceptor表决者
、Learner学习者
。Paxos
算法和 2PC
一样,也有两个阶段,分别为 Prepare
和 accept
阶段。
Prepare
Proposer提案者
:负责提出proposal
,每个提案者在提出提案时都会首先获取到一个 具有全局唯一性的、递增的提案编号N,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在第一阶段是只将提案编号发送给所有的表决者。Acceptor表决者
:每个表决者在accept
某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个编号最大的提案,其编号假设为maxN
。每个表决者仅会accept
编号大于自己本地maxN
的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给Proposer
。
accept
当一个提案被 Proposer
提出后,如果 Proposer
收到了超过半数的 Acceptor
的批准(Proposer
本身同意),那么此时 Proposer
会给所有的 Acceptor
发送真正的提案(你可以理解为第一阶段为试探),这个时候 Proposer
就会发送提案的内容和提案编号。
表决者收到提案请求后会再次比较本身已经批准过的最大提案编号和该提案编号,如果该提案编号 大于等于 已经批准过的最大提案编号,那么就 accept
该提案(此时执行提案内容但不提交),随后将情况返回给 Proposer
。如果不满足则不回应或者返回 NO 。
当 Proposer
收到超过半数的 accept
,那么它这个时候会向所有的 acceptor
发送提案的提交请求。需要注意的是,因为上述仅仅是超过半数的 acceptor
批准执行了该提案内容,其他没有批准的并没有执行该提案内容,所以这个时候需要向未批准的 acceptor
发送提案内容和提案编号并让它无条件执行和提交,而对于前面已经批准过该提案的 acceptor
来说 仅仅需要发送该提案的编号 ,让 acceptor
执行提交就行了。
而如果 Proposer
如果没有收到超过半数的 accept
那么它将会将 递增 该 Proposal
的编号,然后 重新进入 Prepare
阶段
两阶段提交需要所有参与者反馈
在zookeeper是超过一半节点反馈,就认为事务成功执行
所以ZAB协议是借鉴的2pc和paxos算法
paxos算法用来干什么: 基于消息传递,解决多个提案分布式一致性的一个协议
**问题:**假设有一组可以提出提案的进程集合,对于一致性的算法需要满足
被提出的提案中只能有一个提案产生
假设一个提案被选定后,进程可以获取被选定的提案信息
**两个步骤:**1、提案生成 2、批准提案
为什么超过一半通过提案,就是成功的?
一个集合S 有n个元素,有一个子集合s1里面存在超过集合一半数据的时候,还有一个子集合s2里面存在超过集合一半数据的时候,子集合s1和子集合s2肯定有一个公共的交集,最终一致性
Zab协议
作为一个优秀高效且可靠的分布式协调框架,ZooKeeper
在解决分布式数据一致性问题时并没有直接使用 Paxos
,而是专门定制了一致性协议叫做 ZAB(ZooKeeper Automic Broadcast)
原子广播协议,该协议能够很好地支持 崩溃恢复 。
ZAB的协议核心是在整个zookeeper集群中只有一个节点即Leader将客户端的写操作转化为事物(或提议proposal)。Leader节点再数据写完之后,将向所有的follower节点发送数据广播请求(或数据复制),等待所有的follower节点反馈。在ZAB协议中,只要超过半数follower节点反馈OK,Leader节点就会向所有的follower服务器发送commit消息。即将leader节点上的数据同步到follower节点之上。
zxid
zxid是一个64位的数字,高32位是epoch用来标记leader关系是否改变,每次一个leader被选出来,他都会有一个新的epoch。
换一次leader,epoch就加一
当重新选出leader后,计数器要清零
如果zxid最大代表epoch和计数器最大
epoch最大,代表是刚刚宕掉的leader周期内部的一个节点
计数器是当前选举周期内,事务执行操作的一个编号
计数器最大,就是选举周期拥有最新事务的一个节点
ZAB由崩溃恢复和消息广播组成
崩溃恢复就是leader宕掉之后,重新选举,关于数据的同步
消息广播是在正常运行状况下,leader接收到一个事务请求,将事务请求发给各个follower
消息广播
当集群中已经有过半的follower服务器完成了和leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。
当一台同样遵守ZAB协议的服务器启动后加入到集群中是,如果此时集群中已经存在一个leader服务器在进行消息广播,那么新加入的服务器就会自觉进入数据恢复模式:找到leader所在的服务器,并与其数据同步,然后参与到消息广播流程中去。
leader可以接受客户端新的事物proposal请求,将新的proposal请求广播给所有的follower。
- 在zookeeper集群中数据副本的传递策略就是采用消息广播模式。zookeeper中数据副本的同步方式与二阶段提交相似但是却又不同。二阶段提交的要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题。
- ZAB协议中Leader等待follower的ACK反馈是指”只要半数以上的follower成功反馈即可,不需要收到全部follower反馈”
具体步骤
1、客户端发起一个写操作请求
2、leader服务器将客户端的请求转化为事物的proposal提案,同时为每个proposal分配一个全局的ID,即zxid。
3、leader服务器为每个follower服务器分配一个独立的队列,然后将需要广播的proposal依次放到队列中,并且根据先进先出(FIFP)策略进行消息发送
4、follower接收到proposal后,会首先将其以事物日志的方式写入本地磁盘中,写入成功后向leader反馈一个Ack响应信息
5、leader接收到超过半数以上follower的Ack相应信息后,即认为消息发送成功,可以发送commit消息。
6、leader向所有follower广播commit消息,同时自身也会完成事务提交。follower接收到commit消息后,会将上一条事务提交
Leader 服务器与每一个 Follower 服务器之间都维护了一个单独的 FIFO 消息队列进行收发消息,使用队列消息可以做到异步解耦。 Leader 和 Follower 之间只需要往队列中发消息即可。如果使用同步的方式会引起阻塞,性能要下降很多。
zookeeper 采用 Zab 协议的核心,就是只要有一台服务器提交了 Proposal,就要确保所有的服务器最终都能正确提交 Proposal。这也是 CAP/BASE 实现最终一致性的一个体现。
崩溃恢复
当整个服务框架在启动过程中,或是当leader服务器出现网络中断、崩溃退出和重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的leader服务器。当选举产生了新的leader服务器,同时集群已经有过半的机器与该leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。
所谓状态同步就是指数据同步,用来保证集群中存在过半的机器能够和leader服务器的数据状态保证一致。
崩溃恢复主要包括两部分:leader选举和数据恢复
选举和数据同步
1、选举
选出ZXID最大的事务的leader
2、数据同步
每个follower准备一个数据队列,队列存放这些事务发送给follower进行同步的事务。
在zookeeper集群中新的leader选举成功之后,leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。follower节点会根据leader的消息进行回退或者是数据同步操作。最终目的要保证集群中所有节点的数据副本保持一致。
保证消息有序
在整个消息广播中,Leader会将每一个事务请求转换成对应的 proposal 来进行广播,并且在广播 事务Proposal 之前,Leader服务器会首先为这个事务Proposal分配一个全局单递增的唯一ID,称之为事务ID(即zxid),由于Zab协议需要保证每一个消息的严格的顺序关系,因此必须将每一个proposal按照其zxid的先后顺序进行排序和处理。
应用场景
- 选主
- 分布式锁
- 命名服务
- 集群管理和注册中心:
zookeeper
天然支持的watcher
和 临时节点能很好的实现这些需求