https://zhuanlan.zhihu.com/p/130332285
一致性的由来
- 数据不能存在单个节点(主机)上,否则可能出现单点故障。
- 多个节点(主机)需要保证具有相同的数据。
为了解决以上两个问题,就出现了一致性算法。
一致性的分类
- 强一致性算法,保证系统改变提交后立即改变集群的状态。主要模型有:Paxos,Raft(muti-paxos),ZAB(muti-paxos)协议。
- 弱一致性算法,也叫最终一致性,系统不保证改变提交以后立即改变集群中的状态,但能保证最终状态是一致的。主要模型有:DNS系统,Gossip协议。
一致性算法实现案例
- Google的Chubby分布式锁服务,采用Paxos算法。
- etcd分布式键值数据库,采用Raft算法。
- Zookeeper分布式应用协议服务,Chubby的开源实现,采用ZAB算法。
Paxos算法
Paxos算法中的三种角色,包括Proposer,Acceptor,Learners。
- Proposer
只要Proposer发起的提案被半数以上Aceptor接受,Proposer就认为该提案里的value被选定了。 - Acceptor
只要Acceptor接受了某个提案,Acceptor就认为该提案里的value被选定了。 - Learner
Acceptor 告诉Learner 哪个value 被选定,Learner 就认为那个value 被选定。
Paxos算法的两阶段
阶段1(准leader确定)
- Proposer 选择一个提案编号N,然后向半数以上的Acceptor 发送编号为N的Prepare 请求。
- 如果一个Acceptor 收到一个编号为N 的Prepare 请求,且N 大于该Acceptor 已经响应过的所有Prepare 请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor 承诺不再接受任何编号小于N 的提案。
阶段2(leader确认)
- 如果 Proposer 收到半数以上Acceptor 对其发出的编号为N 的Prepare 请求的响应,那么它就会发送一个针对[N,V]提案的Accept 请求给半数以上的Acceptor。注意:V 就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V 就由Proposer 自己决定。
- 如果Acceptor收到一个针对编号为 N 的提案的Accept 请求,只要该Acceptor 没有对编号大于N 的 Prepare 请求做出过响应,它就接受该提案。
ZAB 协议
事务编号 Zxid(事务请求计数器+ epoch)
在 ZAB ( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议) 协议的事务编号 Zxid设计中, Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器, 针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号, 每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的 ZXID,并从中读取epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。Zxid(Transaction id) 类似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal(提议)
ID。为了保证顺序性,该 zkid 必须单调递增。
epoch
epoch:可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代, leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。
Zab 协议有两种模式-恢复模式(选主)、广播模式(同步)
Zab 协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步) 。当整个服务框架在启动过程中或者在领导者崩溃后, Zab 就进入了恢复模式选举产生新的Leader服务器。当领导者被选举出来,且过半的 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。
当有新的server加入到集群时,此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器会自动进入数据恢复模式,找到Leader服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
ZAB 协议 4 阶段
Leader election(选举阶段-选出准 Leader)
- Leader election(选举阶段) : 节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。只有到达 广播阶段(broadcast) 准 leader 才会成为真正的 leader。这一阶段的目的是就是为了选出一个准 leader,然后进入下一个阶段。
Discovery(发现阶段-接受提议、生成 epoch、接受 epoch)
Discovery(发现阶段) : 在这个阶段, followers 跟准 leader 进行通信,同步 followers最近接收的事务提议。这个阶段的主要目的是发现当前大多数节点接收的最新提议,并且准 leader生成新的 epoch,让 followers 接受,更新它们的 accepted Epoch。
一个 follower 只会连接一个 leader, 如果有一个节点 f 认为另一个 follower p 是 leader, f在尝试连接 p 时会被拒绝, f 被拒绝之后,就会进入重新选举阶段。
Synchronization(同步阶段-同步 follower 副本)
Synchronization(同步阶段) : 同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。 只有当 大多数节点都同步完成,准 leader 才会成为真正的 leader。follower 只会接收 zxid 比自己的 lastZxid 大的提议。
Broadcast(广播阶段-leader 消息广播)
Broadcast(广播阶段) : 到了这个阶段, Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
ZAB 提交事务并不像 2PC 一样需要全部 follower 都 ACK, 只需要得到超过半数的节点的 ACK 就可以了。
ZAB 协议 JAVA 实现(FLE-发现阶段和同步合并为 Recovery Phase(恢复阶段) )
协议的 Java 版本实现跟上面的定义有些不同,选举阶段使用的是 Fast Leader Election(FLE),
它包含了 选举的发现职责。因为 FLE 会选举拥有最新提议历史的节点作为 leader,这样就省去了
发现最新提议的步骤。实际的实现将 发现阶段 和 同步合并为 Recovery Phase(恢复阶段)。所
以, ZAB 的实现只有三个阶段: Fast Leader Election; Recovery Phase; Broadcast Phase。
投票机制
每个 sever 首先给自己投票, 然后用自己的选票和其他 sever 选票对比, 权重大的胜出,使用权重较大的更新自身选票箱。 具体选举过程如下:
- 每个 Server 启动以后都询问其它的 Server 它要投票给谁。对于其他 server 的询问,server 每次根据自己的状态都回复自己推荐的 leader 的 id 和上一次处理事务的 zxid(系统启动时每个 server 都会推荐自己)
- 收到所有 Server 回复以后,就计算出 zxid 最大的哪个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server。
- 计算这过程中获得票数最多的的 sever 为获胜者,如果获胜者的票数超过半数,则改server 被选为 leader。否则,继续这个过程,直到 leader 被选举出来
- leader 就会开始等待 server 连接
- Follower 连接 leader,将最大的 zxid 发送给 leader
- Leader 根据 follower 的 zxid 确定同步点,至此选举阶段完成。
- 选举阶段完成 Leader 同步后通知 follower 已经成为 uptodate 状态
- Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了
目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
9. 服务器 1 启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器 1 的状态一直属于 Looking。
10. 服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由于服务器 2 的编号大所以服务器 2 胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
11. 服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换信息,由于服务器 3 的编号最大所以服务器 3 胜出,此时投票数正好大于半数,所以服务器 3 成为领导者,服务器1,2 成为小弟。
12. 服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息,尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能成为小弟。
13. 服务器 5 启动,后面的逻辑同服务器 4 成为小弟
Zookeeper 工作原理(原子广播)
- Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。 Zab 协议有两种模式,它们分别是恢复模式和广播模式。
- 当服务启动或者在领导者崩溃后, Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了。
- 状态同步保证了 leader 和 server 具有相同的系统状态
- 一旦 leader 已经和多数的 follower 进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 zookeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步束,它也参与消息广播。 Zookeeper服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的followers 支持。
- 广播模式需要保证 proposal 被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了 zxid。
- 实现中 zxid 是一个 64 为的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch。低 32 位是个递增计数。
- 当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 server 都恢复到一个正确的状态。
Znode 有四种形式的目录节点
- PERSISTENT:持久的节点。
- EPHEMERAL: 暂时的节点。
- PERSISTENT_SEQUENTIAL:持久化顺序编号目录节点。
- EPHEMERAL_SEQUENTIAL:暂时化顺序编号目录节点
Raft协议
与Paxos 不同Raft 强调的是易懂(Understandability),Raft 和Paxos 一样只要保证n/2+1 节点正常就能够提供服务;raft 把算法流程分为三个子问题:选举(Leader election)、日志复制(Log replication)、安全性(Safety)三个子问题。
角色
Raft把集群中的节点分为三种状态,Leader、Follower、Candidate,理所当然每种状态负责的任务也是不一样的,Raft运行时提供服务的时候只存在Leader与Follower两种状态。
Leader(领导者-日志管理)
负责日志的同步管理,处理来自客户端的请求,与Follower保持这HeartBeat的联系。
Follower(追随者-日志同步)
刚启动时所有节点为Follower 状态,响应Leader 的日志同步请求,响应Candidate 的请求,把请求到Follower的事务转发给Leader。
Candidate(候选者-负责选票)
负责选票投票,Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出Leader后从Candidate转为Leader状态。
任期
在Raft 中使用了一个可以理解为周期(第几届、任期)的概念,用Term 作为一个周期,每个Term 都是一个连续递增的编号,每一轮选举都是一个Term 周期,在一个Term 中只能产生一个Leader;当某节点收到的请求中Term 比当前Term 小时则拒绝该请求。
选举
Raft 的选举由定时器来触发,每个节点的选举定时器时间都是不一样的,开始时状态都为Follower 某个节点定时器触发选举后Term 递增,状态由Follower 转为Candidate,向其他节点发起RequestVote RPC 请求,这时候有三种可能的情况发生:
- 该RequestVote 请求接收到n/2+1(过半数)个节点的投票,从Candidate 转为Leader,向其他节点发送heartBeat 以保持Leader 的正常运转。
- 在此期间如果收到其他节点发送过来的AppendEntries RPC 请求,如该节点的Term 大则当前节点转为Follower,否则保持Candidate 拒绝该请求。
- Election timeout 发生则Term 递增,重新发起选举。
在一个Term 期间每个节点只能投票一次,所以当有多个Candidate 存在时就会出现每个Candidate 发起的选举都存在接收到的投票数都不过半的问题,这时每个Candidate 都将Term递增、重启定时器并重新发起选举,由于每个节点中定时器的时间都是随机的,所以就不会多次存在有多个Candidate 同时发起投票的问题。
在Raft 中当接收到客户端的日志(事务请求)后先把该日志追加到本地的Log 中,然后通过heartbeat 把该Entry 同步给其他Follower,Follower 接收到日志后记录日志然后向Leader 发送ACK,当Leader 收到大多数(n/2+1)Follower 的ACK 信息后将该日志设置为已提交并追加到本地磁盘中,通知客户端并在下个heartbeat 中Leader 将通知所有的Follower 将该日志存储在自己的本地磁盘中。
分布式ID
https://zhuanlan.zhihu.com/p/107939861