文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上如下珍贵的学习资源:html
高并发 必读 的精彩博文 | |
---|---|
nacos 实战(史上最全) | sentinel (史上最全+入门教程) |
Zookeeper 分布式锁 (图解+秒懂+史上最全) | Webflux(史上最全) |
SpringCloud gateway (史上最全) | TCP/IP(图解+秒懂+史上最全) |
10分钟看懂, Java NIO 底层原理 | Feign原理 (图解) |
更多精彩博文 ..... | 请参见【 疯狂创客圈 高并发 总目录 】 |
常见的一致性协议 有二阶段提交(2PC)、三阶段提交(3PC)、Paxos、Raft等算法,在本文将介绍他们中的一部分。java
2PC即Two-Phase Commit,二阶段提交。普遍应用在数据库领域,为了使得基于分布式架构的全部节点能够在进行事务处理时可以保持原子性和一致性。绝大部分关系型数据库,都是基于2PC完成分布式的事务处理。
顾名思义,2PC分为两个阶段处理,程序员
协调者在阶段二决定是否最终执行事务提交操做。这一阶段包含两种情形:面试
执行事务提交
全部参与者reply Yes,那么执行事务提交。算法
事情总会出现意外,当存在某一参与者向协调者发送No响应,或者等待超时。协调者只要没法收到全部参与者的Yes响应,就会中断事务。sql
优势主要体如今实现原理简单;
缺点比较多:数据库
针对2PC的缺点,研究者提出了3PC,即Three-Phase Commit。做为2PC的改进版,3PC将原有的两阶段过程,从新划分为CanCommit、PreCommit和do Commit三个阶段。
编程
在本阶段,协调者会根据上一阶段的反馈状况来决定是否能够执行事务的PreCommit操做。有如下两种可能:设计模式
执行事务预提交缓存
中断事务
加入任意一个参与者向协调者发送No响应,或者等待超时,协调者在没有获得全部参与者响应时,便可以中断事务:
在这个阶段,会真正的进行事务提交,一样存在两种可能。
执行提交
中断事务
在该阶段,假设正常状态的协调者接收到任一个参与者发送的No响应,或在超时时间内,仍旧没收到反馈消息,就会中断事务:
3PC有效下降了2PC带来的参与者阻塞范围,而且可以在出现单点故障后继续达成一致;
但3PC带来了新的问题,在参与者收到preCommit消息后,若是网络出现分区,协调者和参与者没法进行后续的通讯,这种状况下,参与者在等待超时后,依旧会执行事务提交,这样会致使数据的不一致。
像 2PC 和 3PC 都须要引入一个协调者的角色,当协调者 down 掉以后,整个事务都没法提交,参与者的资源都出于锁定的状态,对于系统的影响是灾难性的,并且出现网络分区的状况,颇有可能会出现数据不一致的状况。有没有不须要协调者角色,每一个参与者来协调事务呢,在网络分区的状况下,又能最大程度保证一致性的解决方案呢。此时 Paxos 出现了。
Paxos 算法是 Lamport 于 1990 年提出的一种基于消息传递的一致性算法。因为算法难以理解起初并无引发人们的重视,Lamport在八年后从新发表,即使如此Paxos算法仍是没有获得重视。2006 年 Google 的三篇论文石破天惊,其中的 chubby 锁服务使用Paxos 做为 chubbycell 中的一致性,后来才获得关注。
Paxos 协议是一个解决分布式系统中,多个节点之间就某个值(提案)达成一致(决议)的通讯协议。它可以处理在少数节点离线的状况下,剩余的多数节点仍然可以达成一致。即每一个节点,既是参与者,也是决策者
因为 Paxos 和下文提到的 zookeeper 使用的 ZAB 协议过于类似,详细讲解参照下文, ZAB 协议部分
分布式系统中的节点通讯存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。
基于消息传递通讯模型的分布式系统,不可避免的会发生如下错误:进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复,在基础Paxos场景中,先不考虑可能出现消息篡改,即拜占庭错误的状况。(网络环境通常为自建内网,消息安全相对高)
Paxos算法解决的问题是在一个可能发生上述异常的分布式系统中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性。
Paxos 协议的角色 主要有三类节点:
过程:
规定一个提议包含两个字段:[n, v],其中 n 为序号(具备惟一性),v 为提议值。
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每一个 Proposer 都会向全部 Acceptor 发送提议请求。
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],而且以前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],而且保证之后不会再接受序号小于 n1 的提议。
以下图,Acceptor X 在收到 [n=2, v=8] 的提议请求时,因为以前没有接收过提议,所以就发送一个 [no previous] 的提议响应,而且设置当前接收到的提议为 [n=2, v=8],而且保证之后不会再接受序号小于 2 的提议。其它的 Acceptor 相似。
若是 Acceptor 接受到一个提议请求,包含的提议为 [n2, v2],而且以前已经接收过提议 [n1, v1]。若是 n1 > n2,那么就丢弃该提议请求;不然,发送提议响应,该提议响应包含以前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],而且保证之后不会再接受序号小于 n2 的提议。
以下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,因为以前已经接收过 [n=4, v=5] 的提议,而且 n > 2,所以就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,由于以前接收到的提议为 [n=2, v=8],而且 2 <= 4,所以就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],而且保证之后不会再接受序号小于 4 的提议。Acceptor Y 相似。
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就能够发送接受请求。
Proposer A 接受到两个提议响应以后,就发送 [n=2, v=8] 接受请求。该接受请求会被全部 Acceptor 丢弃,由于此时全部 Acceptor 都保证不接受序号小于 4 的提议。
Proposer B 事后也收到了两个提议响应,所以也开始发送接受请求。须要注意的是,接受请求的 v 须要取它收到的最大 v 值,也就是 8。所以它发送 [n=4, v=8] 的接受请求。
Acceptor 接收到接受请求时,若是序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给全部的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
Paxos 是论证了一致性协议的可行性,可是论证的过程听说晦涩难懂,缺乏必要的实现细节,并且工程实现难度比较高广为人知实现只有 zk 的实现 zab 协议。
Paxos协议的出现为分布式强一致性提供了很好的理论基础,可是Paxos协议理解起来较为困难,实现比较复杂。
而后斯坦福大学RamCloud项目中提出了易实现,易理解的分布式一致性复制协议 Raft。Java,C++,Go 等都有其对应的实现
以后出现的Raft相对要简洁不少。
引入主节点,经过竞选。
节点类型:Follower、Candidate 和 Leader
Leader 会周期性的发送心跳包给 Follower。每一个 Follower 都设置了一个随机的竞选超时时间,通常为 150ms~300ms,若是在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
① 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间以后,没收到 Leader 发来的心跳包,所以进入竞选阶段。
② 此时 A 发送投票请求给其它全部节点。
③ 其它节点会对请求进行回复,若是超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
④ 以后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会从新开始计时。
① 若是有多个 Follower 成为 Candidate,而且所得到票数相同,那么就须要从新开始投票,例以下图中 Candidate B 和 Candidate D 都得到两票,所以须要从新开始投票。
② 当从新开始投票时,因为每一个节点设置的随机竞选超时时间不一样,所以能下一次再次出现多个 Candidate 并得到一样票数的几率很低。
① 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
② Leader 会把修改复制到全部 Follower。
③ Leader 会等待大多数的 Follower 也进行了修改,而后才将修改提交。
④ 此时 Leader 会通知的全部 Follower 让它们也提交修改,此时全部节点的值达成一致。
Google 的粗粒度锁服务 Chubby 的设计开发者 Burrows 曾经说过:“全部一致性协议本质上要么是 Paxos 要么是其变体”。Paxos 虽然解决了分布式系统中,多个节点就某个值达成一致性的通讯协议。可是仍是引入了其余的问题。因为其每一个节点,均可以提议提案,也能够批准提案。当有三个及以上的 proposer 在发送 prepare 请求后,很难有一个 proposer 收到半数以上的回复而不断地执行第一阶段的协议,在这种竞争下,会致使选举速度变慢。
因此 zookeeper 在 paxos 的基础上,提出了 ZAB 协议,本质上是,只有一台机器能提议提案(Proposer),而这台机器的名称称之为 Leader 角色。其余参与者扮演 Acceptor 角色。为了保证 Leader 的健壮性,引入了 Leader 选举机制。
ZAB协议还解决了这些问题
ZAB 协议相似于两阶段提交,客户端有一个写请求过来,例如设置 /my/test
值为 1,Leader 会生成对应的事务提议(proposal)(当前 zxid为 0x5000010 提议的 zxid 为Ox5000011),现将set /my/test 1
(此处为伪代码)写入本地事务日志,而后set /my/test 1
日志同步到全部的follower。follower收到事务 proposal ,将 proposal 写入到事务日志。若是收到半数以上 follower 的回应,那么广播发起 commit 请求。follower 收到 commit 请求后。会将文件中的 zxid ox5000011 应用到内存中。
上面说的是正常的状况。有两种状况。第一种 Leader 写入本地事务日志后,没有发送同步请求,就 down 了。即便选主以后又做为 follower 启动。此时这种仍是会日志会丢掉(缘由是选出的 leader 无此日志,没法进行同步)。第二种 Leader 发出同步请求,可是尚未 commit 就 down 了。此时这个日志不会丢掉,会同步提交到其余节点中。
如今 5 台 zk 机器依次编号 1~5
1.节点 1 发起投票,第一轮投票先投本身,而后进入 Looking 等待的状态 2.其余的节点(如节点 2 )收到对方的投票信息。节点 2 在 Looking 状态,则将本身的投票结果广播出去(此时走的是上图中左侧的 Looking 分支);若是不在 Looking 状态,则直接告诉节点 1 当前的 Leader 是谁,就不要瞎折腾选举了(此时走的是上图右侧的 Leading/following 分支) 3.此时节点 1,收到了节点 2 的选举结果。若是节点 2 的 zxid 更大,那么清空投票箱,创建新的投票箱,广播本身最新的投票结果。在同一次选举中,若是在收到全部节点的投票结果后,若是投票箱中有一半以上的节点选出了某个节点,那么证实 leader 已经选出来了,投票也就终止了。不然一直循环
zookeeper 的选举,优先比较大 zxid,zxid 最大的节点表明拥有最新的数据。若是没有 zxid,如系统刚刚启动的时候,则比较机器的编号,优先选择编号大的
在选出 Leader 以后,zk 就进入状态同步的过程。其实就是把最新的 zxid 对应的日志数据,应用到其余的节点中。此 zxid 包含 follower 中写入日志可是未提交的 zxid 。称之为服务器提议缓存队列 committedLog 中的 zxid。
同步会完成三个 zxid 值的初始化。
peerLastZxid
:该 learner 服务器最后处理的 zxid。 minCommittedLog
:leader服务器提议缓存队列 committedLog 中的最小 zxid。 maxCommittedLog
:leader服务器提议缓存队列 committedLog 中的最大 zxid。 系统会根据 learner 的peerLastZxid
和 leader 的minCommittedLog
,maxCommittedLog
作出比较后作出不一样的同步策略
场景:peerLastZxid
介于minCommittedLogZxid
和maxCommittedLogZxid
间
此种场景出如今,上文提到过的,Leader 发出了同步请求,可是尚未 commit 就 down 了。 leader 会发送 Proposal 数据包,以及 commit 指令数据包。新选出的 leader 继续完成上一任 leader 未完成的工做。
例如此刻Leader提议的缓存队列为 0x20001,0x20002,0x20003,0x20004,此处learn的peerLastZxid为0x20002,Leader会将0x20003和0x20004两个提议同步给learner
此种场景出如今,上文提到过的,Leader写入本地事务日志后,还没发出同步请求,就down了,而后在同步日志的时候做为learner出现。
例如即将要 down 掉的 leader 节点 1,已经处理了 0x20001,0x20002,在处理 0x20003 时还没发出提议就 down 了。后来节点 2 当选为新 leader,同步数据的时候,节点 1 又神奇复活。若是新 leader 尚未处理新事务,新 leader 的队列为,0x20001, 0x20002,那么仅让节点 1 回滚到 0x20002 节点处,0x20003 日志废弃,称之为仅回滚同步。若是新 leader 已经处理 0x30001 , 0x30002 事务,那么新 leader 此处队列为0x20001,0x20002,0x30001,0x30002,那么让节点 1 先回滚,到 0x20002 处,再差别化同步0x30001,0x30002。
peerLastZxid
小于minCommittedLogZxid
或者leader上面没有缓存队列。leader直接使用SNAP命令进行全量同步
http://www.javashuo.com/article/p-vjywczat-vh.html
https://blog.csdn.net/weixin_33725272/article/details/87947998