分布式共识问题、理论、协议和算法
兰伯特有很多关于分布式的理论,这些理论都很经典(比如拜占庭将军 问题、Paxos),但也因为太早了,与实际场景结合的不多,所以后续的众多算法是在这个 基础之上做了大量的改进(比如Raft 等)
两种分布式共识问题
是否存在伪造或者篡改的恶意行为。
忠诚的将军,正常运行的计算机节点;
叛变的将军,出现故障并会发送误导信息的计算机节点;
信使被杀,通讯故障、信息丢失;
信使被间谍替换,通讯被中间人攻击,攻击者在恶意伪造信息和劫持通讯。
拜占庭问题 以及容错算法概述
分布式共识问题,是分布式系统领域最复杂的分区容错一致性模型的情景化描述
问题
- 它描述了如何在存在恶意行为(如消息篡改或伪造)的情况下使分布式系统达成一致
- 将军可能是叛徒,信使也可能是叛徒
口信消息型解决方案(A solution with oral message)
有m个将军叛变,要有3m+1个将军才可以实现最终达成一致。
从另外一个角度理解:n 位将军,最多能容忍 (n - 1) / 3 位叛将
原则:副官相互交流时候,少数服从多数
步骤:
- 1。第一轮指挥官先发送,不管指挥官是不是叛徒,不是的话副官收到的是一致的,是叛徒副官收到的消息是不一致的。
- 2。 第二轮副官之间相互交换信息,副官自己按照收到的消息类型少数服从多数,最后可以保证达成一致,
分析:如果是叛徒,给不同的人发送的消息肯定是不一样的,否则大家都一致了就不是叛徒了,但是其他人互相协商之后少数服从多数就可以实现最后的消息的一致性。
在二忠一叛的问题中,在存在 1 位叛将的情况下,必须增加 1 位将军,将 3 位将军协商共识,转换为 4 位将军协商共识,这样才能实现忠诚将军的一致性作战计划(三个副官肯定有两个收到的是一样的)
签名消息型解决方案(A solution with signed message)
- 如果忠诚将军先发送消息,叛徒收到修改消息,签名有误,发给其他人的时候就会被认为叛变失效。执行签名没变的消息
- 如果叛徒先发的消息,其余将军收到之后相互交流发现消息不一样,认为第一个发的已经叛变,可以先处理叛徒,之后重新制定协商计划。
非拜占庭问题 以及容错算法
问题
解决的是分布式的系统中存在故障,但不存在恶意节点的场景下 的共识问题。 也就是说,这个场景可能会丢失消息,或者有消息重复,但不存在错误消息,或者伪造消息的情况。
故障容错算法(Crash Fault Tolerance,CFT)
常见的算法有 Paxos 算法、Raft 算法、ZAB 协议。
对应的容错算法的比较选择
如果能确定该环境中各节点是可信赖的,不存在篡改消息或者伪造消息等恶意行为(例如 DevOps 环境中的分布式路由寻址系统),推荐使用非拜占庭容错算法;反之,推荐使用拜占庭容错算法,例如在区块链中使 用 PoW 算法。
CAP理论--分布式共识系统设计侧重点
借助CAP,根据业务场景的特点进行权衡,设计出适合的分区容错一致性模型。
- 一致性(Consistency) :读取不同节点,要么读出同一份最新的数据,要么读取失败。强调的不是数据完整而是数据一致。
- 可用性(Availability) :不管访问哪个节点,都能得到响应数据,但不保证是 同一份最新数据。强调数据可用,不保证数据一致。
- 分区容错性(Partition Tolerance):作为分布式系统,分区容错性是必须要实现的,不能因为节点间出现了分区故障,而出现整个系统不能用的情况。
只要有网络交互就一定会有延迟和数据丢失,必须接受,还必须保证系统不能挂掉,也就是说,分区容错性(P)是前提,是必须要保证的。
- CA 模型,在分布式系统中不存在。因为舍弃 P,意味着舍弃分布式系统,就比如单机版关系型数据库 MySQL,如果 MySQL 要考虑主备或集群部署时,它必须考虑 P。
- CP 模型,采用 CP 模型的分布式系统,一旦因为消息丢失、延迟过高发生了网络分区, 就影响用户的体验和业务的可用性。因为为了防止数据不一致,集群将拒绝新数据的写入,典型的应用是 ZooKeeper,Etcd 和 HBase。
- AP 模型,采用 AP 模型的分布式系统,实现了服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误,但当出现分区故障时,相同的读操作,访问不同的节点,得到响应数据可能不一样。典型应用就比如 Cassandra 和 DynamoDB。
并不是一定时时刻刻需要P,但是需要保证P!!!****在不存在网络分区的情况下,也就是分布式系统正常运行时(这也是系统在绝大部分时候所处的状态),就是说在不需要 P 时,C 和 A 能够 同时保证。只有当发生分区故障的时候,也就是说需要 P 时,才会在 C 和 A 之间做出选 择。
ACID理论:追求CAP中的一致性
事务的特性,单机里面比较容易实现
比如MySQL:
A : 原子性,undo log
C: 一致性,事务
I : 隔离性,隔离级别
D: 持久性,redo log
在分布式系统里面实现事务,需要使用分布式事务协议,比如二阶段提交协议(MYSQL实现的) 和 TCC(Try-Confirm-Cancel)(Seata等实现的)
多个系统组成一组分布式事务,要么都做要么都不做。
开发实现分布式系统,如果不是必须,尽量不要实现事务,可以 考虑采用强一致性或最终一致性。
二阶段提交协议
服务方实现(MySQL自己)
- 提交请求阶段(投票阶段):协调者向其他节点发起投票得到结果并且统计
- 提交执行阶段(完成阶段):根据统计结果再次向其他节点发送执行决策,结果是提交就执行提交,结果是不提交就不执行提交。
缺点是同步阻塞,而且万一协调者挂了就无法保证ACID,长期锁定资源
三阶段提交协议
2PC的第一步拆分成了2步,并且引入了超时机制,
- 第一步canCommit阶段,先向参与者发出一个信号,看看大家是否都能提交,如果可以就返回yes,否则返回no。
- 第二步 PreCommit 阶段,如果参与者可以完成 commit,就返回ack进确认,如果不能则放弃提交本次事务
- 第三步doCommit阶段,进行真正的事务提交
虽然针对二阶段提交协议的“协调者故障导致参与者长期锁定资源”的痛点,通过引入了询问阶段和超时机制,来减少资源被长时间锁定的情况,不过这会导致集群各节点在正常运行的情况下,使用更多的消息进行协商,增加系统负 载和响应延迟。也正是因为这些问题,三阶段提交协议很少被使用
TCC协议:补偿机制
业务逻辑实现(后端代码)
TCC 是 Try(预留)、Confirm(确认)、Cancel(撤销) 3 个操作的简称,它包含了预留、确认或撤销这 2 个阶段。(预留阶段所有的人都是确认才可以确认,否则向每个人发撤销请求)
TCC 本质上是补偿事务,它的核心思想是针对每个操作都要注册一个与其 对应的确认操作和补偿操作(也就是撤销操作)
TCC 不依赖于数据库的事务,而是在业务中实现了分布式事务,这样能减轻数据库 的压力,但对业务代码的入侵性也更强,实现的复杂度也更高。
BASE理论:追求CAP中的可用性
BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。
- 基本可用:在出现不可预知的故障时(挖断线路,并发请求量过载),允许损失部分功能的可用性,保障核心功能的可用性。也就是说,通过服务降级的方式,努力保障极端情况下的系统可用性。
- 措施:流量削峰、延迟响应、体验降级、过载 保护、分片多副本(将同一业务的数据先分片,然后再以 多份副本的形式分布在不同的节点上。)
- 最终一致:如果业务的某功能无法容忍一致性的延迟 (比如分布式锁对应的数据),需要实现的是强一致性;如果能容忍短暂的一致性的延迟 (比如 QQ 状态数据),就可以考虑最终一致性。
- 措施:读取数据时候不一致就修复,写入数据时候不一致就修复,定时任务后台对账扫描异步修复
- 软状态:系统数据的一种过渡状态,也就是说不同节点间,数据副本存在短暂的不一致
BASE 理论是对 CAP 中一致性和可用性权衡的结果,它来源于对大规模互联网分布式系统实践的总结,是基于 CAP 定理逐步演化而来的。它的核心思想是,如果不是必须的,不推荐实现事务或强一致性,鼓励可用性和性能优先,根据业务的场景特点,来实现非常弹性的基本可用,以及实现数据的最终一致性。
BASE 理论在 NoSQL 中应用广泛,是 NoSQL 系统设计的事实上的理论支撑。
ACID 理论 和 BASE 理论的区别
ACID 理论是传统数据库常用的设计理念,追求强一致性模型。
BASE 理论支持的是大型 分布式系统,通过牺牲强一致性获得高可用性。BASE 理论在很大程度上,解决了事务型 系统在性能、容错、可用性等方面痛点。
分布式共识算法:Paxos算法
兰伯特论文提出
分布式共识的代名词,为当前最常用的一批共识算法都是基于它改进的。比如,Fast Paxos 算法、 Cheap Paxos 算法、Raft 算法、ZAB 协议等等。
部分1:Basic Paxos 算法,描述的是多节点之间如何就某个值达成共 识;(实现2PC协议达成共识)
Basic Paxos 是通过二阶段提交的方式来达成共识的。
二阶段提交是达成共识的常用方式,如果你需要设计新的共识算法的时候,也可以考虑这个方式。
三个角色:提议者(Proposer)、接受者(Acceptor)、学习者(Learner)
- 提议者(可能有多个,提议值还不一样):接入和协调功能,收到客户端请求后,发起二阶段提交,进行共识协商;这样做的好处是,对业务代码没有入侵性
- 接受者(Acceptor):对每个提议的值进行投票,并存储接受的值,一般来说,集群中的所有节点都在扮演接受者的角色,参与共识协商,并接受 和存储数据。
- 一个节点(或进程)可以身兼多个角色。一个 3 节点的集 群,1 个节点收到了请求,那么该节点将作为提议者发起二阶段提交,然后这个节点和另外 2 个节点一起作为接受者进行共识协商
- 学习者:被告知投票的结果,接受达成共识的值,存储保存,不参与投票的过程。也就是slave节点。
实现流程
提案、提案编号、提议值:使用[n, v]表示一个提案,其中 n 为提案编号,v 为提议值。
- 准备阶段:客户端发送准备请求中是不需要指定提议的值的,只需要携带提案编号就可以了。
- 一个接收者没有通过任何提案的时候,先响应了大的提案编号之后,就会承诺不会理会后面来的提案编号小于等于的提案了。
- 如果接受者之前有通过提案,那么接受者将承诺,会在准备请求的响应中,包含已经通过的最大编号的提案信息
- 注意区分上面的响应和通过提案是不一样的,前者是回复【准备请求】,后者是回复【接收请求】
- 接收阶段:收到大多数节点的准备响应之后,会分别发送接受请求,携带的提案包含了提议值,接收者发现提案的提案编号小于自己承诺的可接受大小的最大值,就会拒绝接收提案。超最大值才会接收
- 如果集群中有学习者,当接受者通过了一个提案时,就通知给所有 的学习者。当学习者发现大多数的接受者都通过了某个提案,那么它也通过该提案,接受该 提案的值。
容错能力,源自“大多数”的约定
当少于一半的节点出现故障的时候,共识协商仍然在正常工作。
不像分布式事务算法那样,必须要所有节点都同意后才提交操作,因为“所有节点都 同意”这个原则,在出现节点故障的时候会导致整个集群不可用。
易错题
如果节点 A、B 已经通过了提案[5, 7],节点 C 未通过任何提案,那么当客户端 3 提案编号为 9 ,通过 Basic Paxos 执行“SET X = 6”, 最终节点值 都 应该是[9,7], 过程如下:
- 在准备阶段,节点C收到客户端3的准备请求[9,6], 因为节点C未收到任何提案,所以返回“尚无提案”。这时如果节点C收到了之前客户端的准备请求[5, 7], 根据提案编号5小于它之前响应的准备请求的提案编号9,会丢弃该准备请求。
- 客户端3发送准备请求[9,6]给节点A,B,这时因为节点A,B已经通过了提案[5,7], 根据“如果接受者之前有通过提案,那么接受者将承诺,会在准备请求的响应中,包含已经通过的最大编号的提案信息”,节点A,B会返回[5,7]给客户端3.
- 客户端3发送接受请求[9,7] 给节点A,B,C(注意这里使用的是准备阶段的最大提议编号和已经被通过的值),因为此时请求编号9不小于之前的请求编号,所以所有节点接受该请求[9,7].
- 所有学习者会接受该提案,学习该值。
部分2:Multi-Paxos 思想,描述的是执行多个 Basic Paxos 实例,就一系列值达成共识。
Multi-Paxos 算法是一个统称,它是指基于 Multi-Paxos 思想,通过多个 Basic Paxos 实例实现一系列值的共识的算法(比如 Chubby 的 Multi-Paxos 实现、Raft 算法等)。
兰伯特提到思想还缺少算法过程的细节和编程所必须的细节,比如如何选举领导者等。
直接通过多次执行 Basic Paxos 实例实现一系列值的共识 存在问题:
- 如果多个提议者同时提交提案,可能出现因为提案冲突,在准备阶段没有提议者接收到大多数准备响应,协商失败,需要重新协商。
- 2 轮 RPC 通讯(准备阶段和接受阶段)往返消息多、耗性能、延迟大。
解决多提议者没人收到大多响应的提案冲突:引入领导者,避免多次执行 Basic Paxos
领导者节点作为唯一提议者,这样就不存在多个提议者同时提交提案的情况,也就不存在提案冲突的情况了:
在论文中,兰伯特没有说如何选举领导者,需要我们在实现 MultiPaxos 算法的时候自己实现。
在 Chubby 中,主节点(也就是领导者节点)是通过执行 Basic Paxos 算法,进行投票选举产生的,主节点会通过不断续租的方式来延长租期(Lease)。在 Chubby 中,为了实现了强一致性,读操作也只能在主节点上执 行。 也就是说,只要数据写入成功,之后所有的客户端读到的数据都是一致的。所有的读请求和写请求都由主节点来处理。
解决准备和接收两次RPC消耗大:优化 Basic Paxos 执行
当领导者处于稳定状态时,省掉准备阶段,直接进入接受阶段。领导者节点上,序列中的命令是最新的,不再需 要通过准备请求来发现之前被大多数节点通过的提案,领导者可以独立指定提案中的值。这 时,领导者在提交命令时,可以省掉准备阶段,直接进入到接受阶段
减少了往返的消息数,提升了 性能,降低了延迟。
在 Chubby 中,当主节点从客户端接收到写请求后,作为提议者,执行 Basic Paxos 实例,将数据发送给所有的节点,并且在大多数的服务器接受了这个写请求之后,再响应给客户端成功。Chubby 省掉 Basic Paxos 的准备阶段,提升了数据的提交效率,但是所有写请求都在主节点处理,限制了 集群处理写请求的并发能力,约等于单机。
通过减少非必须的协商步骤来提升性能的。这种方法非常常用,也很有效。 比如,Google 设计的 QUIC 协议,是通过减少 TCP、TLS 的协商步骤,优化 HTTPS 性 能。我希望你能掌握这种性能优化思路,后续在需要时,可以通过减少非必须的步骤, 优化系统性能。
实现 Multi-Paxos 算法,最大的挑战是如何证明它是正确的。实际使用 时,不推荐你设计和实现新的 Multi-Paxos 算法,而是建议优先考虑 Raft 算法,因为 Raft 的正确性是经过证明的。当 Raft 算法不能满足需求时,你再考虑实现和优化 MultiPaxos 算法。
在 Chubby 的 Multi-Paxos 实现中,也约定了“大多数原则”,也就是说,只要大多数节点正常运行时,集群就能正常工作,所以 Chubby 能容错(n - 1)/2 个节点的 故障。
分布式共识算法:Raft算法
Raft 算法属于 Multi-Paxos 算法,它是在兰伯特 Multi-Paxos 思想的基础上,做了一些简化和限制,比如增加了日志必须是连续的,只支持领导者、跟随者和候选人三种状态,在理解和算法实现上都相对容易许多。
Raft 算法是现在分布式系统开发首选的共识算法。绝大多数选用 Paxos 算法的系统都是在 Raft 算法发布前开发的,当时没得选;而全新的系 统大多选择了 Raft 算法。
处理绝大部分场景的容错和一致性需求,轻松突破系统的单机限制。
从本质上说,Raft 算法是通过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。
raft算法包含的成员身份--服务器节点状态
首先Raft 算法:每个节点有三个状态,Leader,follower和candidate(候选人)。正常运行的情况下,集群中会有一个leader,其他都是follower,follower只响应Leader和Candidate的请求,而客户端的请求全部由Leader处理,即使有客户端请求到了一个follower,也会将请求转发到leader。
在任何时候,每一个服务器节点状态三选一:
- 跟随者:就相当于普通群众,默默地接收和处理来自领导者的消息,当等待领导者心跳 信息超时的时候,就主动站出来,推荐自己当候选人。
- 候选人(领导者超时的时候才会出现):候选人将向其他节点发送请求投票(RequestVote)RPC 消息,通知其他节点来投票,如果赢得了大多数选票,就晋升当领导者。
- 领导者: 一切以我为准,处理写请求、管理日志复制和不断地发送心跳信息,通知其他节点“我是领导者,我还活 着,你们现在不要发起新的选举,找个新领导者来替代我。”
- 只能有一个领导者
领导者选举
选举流程
-
首先,在初始状态下,集群中所有的节点都是 follower 的状态。初始的任期编号都是0,初始每个节点的获得的选票都是0,初始的超时时间是随机值,所以随机超时时间小的会率先超时。超时之后会增加自己的任期编号+1,之后推选自己为候选人,之后先给自己投票自己获得的票数+1,之后向向其他的节点发送RPC消息,请求选举自己作为leader。
-
如果其他节点接收到候选人 A 的请求投票 RPC 消息,在编号为 1 的这届任期内,也还没有 进行过投票,那么它将把选票投给节点 A,并增加自己的任期编号。
- 如果收到了来自leader或者candidate的心跳RPC请求,那他就会保持follower状态,避免争抢成为 candidate。
-
如果候选人在选举超时时间内赢得了大多数的选票,那么它就会成为本届任期内新的领导 者。节点 A 当选领导者后,他将周期性地发送心跳消息,通知其他服务器我是领导者,阻止跟 随者发起新的选举,篡权。(注意投票选举等待超时时间每一个节点自己是随机的)
-
如果follower一段时间(两 个timeout信号)内没有收到Leader的心跳信号,他就会认为leader挂了,发起新一轮选举。 选举开始后,每个follower会增加自己当前的term,并将自己转为candidate。然后向其他节点 发起投票请求,请求时会带上自己的编号和term + 1,也就是说都会默认投自己一票。之后 candidate状态可能会发生以下三种变化:
- 赢得选举,成为leader: 如果它在一个term内收到了大多数的选票,将会在接下的剩余term时间内称为leader,然后就可以通过发送心跳确立自己的地位。
- 其他节点成为leader: 在等待投票时,可能会收到其他server发出心跳信号,说明其他 leader已经产生了。这时通过比较自己的term编号和RPC过来的term编号,如果比对方大, 说明leader的term过期了,就会拒绝该RPC,并继续保持候选人身份; 如果对方编号不比自己 小,则承认对方的地位,转为follower。
- 选票被瓜分,选举失败: 如果没有candidate获取大多数选票, 则没有leader产生, candidate们 等待超时后发起另一轮选举. 为了防止下一次选票还被瓜分,必须采取一些额外的措施, raft采 用随机election timeout(随机休眠时间)的机制防止选票被持续瓜分。通过将timeout随机设 为一段区间上的某个值, 因此很大概率会有某个candidate率先超时然后赢得大部分选票。
服务器节点间的沟通联络采用的是两类远程过程调用(RPC):
- 请求投票(RequestVote)RPC,是由候选人在选举期间发起,通知各节点进行投票;
- 日志复制(AppendEntries)RPC,是由领导者发起,用来复制日志和提供心跳消息。
- 日志复制 RPC 只能由领导者发起
什么是任期?
用于决定是否有被投票的资格:
- 任期大的直接丢弃任期小的投票请求
- 任期小(例如从分区错误恢过来复)的收到任期大的投票请求之后,不管原来身份,现在变成跟随者
- 发起投票的时候HiU将自己的任期+1
在Raft协议中,会将时间分为一些任意时间长度的时间片段,叫做 term 任期。任期term会使用一个全局唯一,连续递增的编号作为标识,也就是起到了一个逻辑时钟的作用。在每一个 term 任期时间片里,都会进行新的选举,每一个Candidate都会努力争取成为leader。获得票数最多的节点就会被选举为Leader。
被选为Leader的这个节点,在一个term时间片里就会保持leader状态。这样,就会保证在同一时间段内,集群中只会有一个Leader。
在某些情况下,选票可能会被各个节点瓜分,形成不了多数派,那这个term可能直到结束都没有leader,直到下一个term再重新发起选举,这也就没有了Zookeeper中的脑裂问题。而在每次重新选举的过程中, leader也有可能会退化成为follower。也就是说,在这个集群中, leader节点是会不断变化的。
然后,每次选举的过程中,每个节点都会存储当前任期 term 编号,并在节点之间进行交流时,都会带上自己的term编号。如果一个节点发现他的编号比另外一个小,那么他就会将自己的编号更新为较大的那一个。而如果leader或者candidate发现自己的编号不是最新的,他就会自动转成 follower。如果接收到的请求term编号小于自己的编号,term将会拒绝执行。
选举有哪些规则
- 周期发心跳,组织Follower发起选举
- Follower自己超时时间内没有收到领导者的心跳就认为没有领导者,推举自己为候选人,发起选举
- 大多数原则,赢得大多数选票的candidate,将晋升为leader。(n - 1)/2
- 是为了保证在一个给定的任期内最多只有一个领导者。
- 在一个任期内,领导者一直都会是领导者,直到它自身出现问题(比如宕机),或者因 为网络延迟,其他节点发起一轮新的选举。
- 如果收到了来自leader或者candidate的心跳RPC请求,那他就会保持follower状态,避免争抢成为 candidate。
- 一个节点只能对不同节点同一个任期号投一张票,只有一个机器会被投票,FIFO的策略,
- 当任期编号相同时,日志完整性高的跟随者(也就是最后一条日志项对应的任期编号值 更大,索引号更大),拒绝投票给日志完整性低的候选人。
- 也就是不会投票给一个比自己数据旧的人,在 Raft 中,日志不仅是数据的载体,日志的完整性还影响领导者选举的结果。也就是说, 日志完整性最高的节点才能当选领导者。
- 兰伯特的 Multi-Paxos 不要求日志是连续的,但在 Raft 中日志必须是连续的。
2个随机超时时间
随机选举超时时间的方法,把超时时间都分散开来,导致在大多数情况下只有一个服务器节点先发起选举,而不是同时发起选举,这样就能减少因选票瓜分导致选举失败的情况。
- 心跳超时随机:跟随者等待领导者心跳信息超时的时间间隔,是随机的;
- 选举无效随机等待时间:当没有候选人赢得过半票数,选举无效了,这时需要再等待一个随机时间间隔(有点类似于滑动窗口发请求,拥塞控制发现拥堵的时候随机等待时间)
日志复制
日志是什么?
在 Raft 中,副本数据是以日志的形式存在的,其中日志项中的指令表示用户指定的数据。
Raft 除了能实现一系列值的共识之外,还能实现各节点日志的一致。
- 日志:在 Raft 算法中,副本数据是以日志(log)的形式存在的
- 日志项:它主要包含用户指定的数据
- 索引值:日志项对应的整数索引值。
- 任期编号:创建这条日志项的领导者的任期编号。
- 指令:一条由客户端请求指定的、状态机需要执行的指令。
- 日志项:它主要包含用户指定的数据
领导者接收到来自客户端写请求后,处理写请求的过程就是一个复制和提交日志项(Log Entry)的过程。一届领导者任期,往往有多条日志项。而且日志项的索引值是连续的
如何复制日志?
一个优化后的二阶段提交,
Dledger还会采用Raft协议进行多副本的消息同步:
- 简单来说,数据同步会通过两个阶段,一个是uncommitted阶段,一个是commited阶段。
- Leader Broker上的Dledger收到一条数据后,会标记为uncommitted状态,然后他通过自己的 DledgerServer组件把这个uncommitted数据发给Follower Broker的DledgerServer组件。
- 接着Follower Broker的DledgerServer收到uncommitted消息之后,必须返回一个ack给 Leader Broker的Dledger。然后如果Leader Broker收到超过半数的Follower Broker返回的ack 之后,就会把消息标记为committed状态。
- 再接下来,
Leader Broker上的DledgerServer就会发送committed消息给Follower Broker 上的DledgerServer,让他们把消息也标记为committed状态。这样,就基于Raft协议完成了两阶 段的数据同步。(心跳的时候或者uncommitted的时候才会,后面的来了说明前面的一定commit了)
- leader 进入第一阶段,通过日志复制(AppendEntries)RPC 消息,将日志项复制到集群其他节点上。
- 如果 leader 接收到大多数的“复制成功”响应后,它将日志项提交到它的状态机,并返回成功给客户端。如果leader 没有接收到大多数的“复制成功”响应,那么就返回错误给客户端。
- leader 不直接发送消息通知其他节点提交指定日志项(也就是最后的第二阶段通知 follower 没有立刻执行)。这是 Raft 中的一个优化,因为 leader 的日志复制 RPC 消息或心跳消息,包含了当前最大的将会被提交的日志项索引值。所以通过日志复制 RPC 消息或心跳消息,follower 就可以知道 leader 的日志提交位置信息,降低了处理客户端请求的延迟
如何实现日志的一致?
在 Raft 算法中,以领导者的日志为准,来实现各节点日志的一致的。Leader 通过强制 Follower 直接复制自己的日志项,处理不一致日志。
- 首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的 日志是不一致的了。
- 日志复制 RPC 的一致性检查:不断递减发送要复制的日志项的索引值,Follower会检测上一个日志项和自己结尾的日志项是不是一致的,一直就不会拒绝接收了,直到Follower不再拒绝接收此日志项,说明当前日志项的上一个日志项和Follower是一致的,就可以从这里开始复制了。
- PrevLogEntry:表示当前要复制的日志项的前面一条日志项的索引值。
- PrevLogTerm:表示当前要复制的日志项的前面一条日志项的任期编号,
- 日志复制 RPC 的一致性检查:不断递减发送要复制的日志项的索引值,Follower会检测上一个日志项和自己结尾的日志项是不是一致的,一直就不会拒绝接收了,直到Follower不再拒绝接收此日志项,说明当前日志项的上一个日志项和Follower是一致的,就可以从这里开始复制了。
- 然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。
成员变更(重点)
配置:它就是在说集群是哪些 节点组成的,是集群各节点地址信息的集合。比如节点 A、B、C 组成的集群,那么集群的配置就是[A, B, C]集合。
出现 2 个领导者
值的共识和日志的一致都是由领导者决定的,领导者的唯一性很重要
变更时候,节点 A、B 和 C 之间发生了分区错误,可能存在新旧配置的 2 个“大多数”,导致集群中同时出现两个领导者,破坏了 Raft 的领导者的唯一性原则,影响了集群的稳定运行。
- 节点 A、B 组成旧配置中的“大多数”,也就是变更前的 3 节点集群中的“大多数”,那么这时的领导者(节点 A)依旧是领导者。
- 节点 C 和新节点 D、E 组成了新配置的“大多数”,也就是变更后的 5 节点集 群中的“大多数”,它们可能会选举出新的领导者(比如节点 C)。那么这时,就出现了同时存在 2 个领导者的情况。
因为我们在启动集群时,配置是固定的,不存在成员变更,在这种情况下,Raft 的领导者选举能保证只有一个领导者。但是重启集群,意味着在集群变更期间服务不可用。
单节点变更
最初实现成员变更的是联合共识(Joint Consensus),但是较难实现,改进后的方法,单节点变更 (single-server changes)是通过一次变更一个节点实现成员变更。如果需要变更多个节点,那你需要 执行多次单节点变更。
假设节点 A 是领导者: 目前的集群配置为[A, B, C],我们先向集群中加入节点 D,这意味着新配置为[A, B, C, D]。 成员变更,是通过这么两步实现的:
- 第一步,领导者(节点 A)向新节点(节点 D)同步数据;
- 第二步,领导者(节点 A)将新配置[A, B, C, D]作为一个日志项,复制到新配置中所有节点(节点 A、B、C、D)上,然后将新配置的日志项提交到本地状态机,完成单节点变更。 在变更完成后,现在的集群配置就是[A, B, C, D],我们再向集群中加入节点 E,也就是说, 新配置为[A, B, C, D, E]。成员变更的步骤和上面类似
这样一来,我们就通过一次变更一个节点的方式,完成了成员变更,保证了集群中始终只有 一个领导者,而且集群也在稳定运行,持续提供服务。
在正常情况下,不管旧的集群配置是怎么组成的,旧配置的“大多数”和新配置的“大多数”都会有一个节点是重叠的。 (这里的大多数是leader之外的)也就是说,不会同时存在旧配置和新配置 2 个“大多数”,不管集群是偶数节点,还是奇数节点,不管是增加节点,还是移除节点,新旧配置的“大多数”都会存在重叠。
在分区错误、节点故障等情况下,如果我们并发执行单节点变更,那么就可能出现一次单节点变更尚未完成,新的单节点变更又在执行,导致集群出现 2 个领导者 的情况。
“联合共识”因为它难以实现,很少被 Raft 实现采 用。
误区
- Raft 不是一致性算法而是共识算法,是一个 Multi-Paxos 算法,实现的是如何就一系列值达成共识。并且,Raft 能容忍少数节点的故障。
http://thesecretlivesofdata.com/raft/
raft常见宕机问题解决:
https://blog.csdn.net/chdhust/article/details/67654512
负载均衡算法
轮询和随机算法
- 轮询算法:累加取余,实现简单,没有考虑开销导致的不一致问题。不适合有状态请求。
- 加权轮询算法:设置了优先级
- 随机算法:实现简单,没有考虑开销导致的不一致问题。不适合有状态请求。
哈希算法与一致哈希算法
- 相同key可以在同一台机器,可以实现有状态请求
Raft 算法领导者模型简化了 Multi-Paxos 算法实现和共识协商,但写请求只能限制在领导者节点上处理,导致 了集群的接入性能约等于单机,那么随着业务发展,集群的性能可能就扛不住了,会造成系统过载和服务不可用
要通过分集群,突破单集群的性能限制。 加个 Proxy 层,由 Proxy 层处理来自客户端的读写请求,接收到读写请求后,通过对 Key 做哈希找到对应的集群就可以了啊。但是简单的hash在扩缩容时候的数据迁移的时候成本太大。
具体参考下面的一致性hash
一致哈希本质上是一种路由寻址算法,适合简单的路由寻址场景。比如在 KV 存储系统内部,它的特点是简单,不需要维护路由信息。
Gossip协议:最终一致性
监控主机和业务运行的告警系统在可用性上比较敏感。希望自己的系统能在极端情况下(比如集群中只有一个节点在运行)也能运行。
二阶段提交协议和 Raft 算法都需要全部节点或者大多数节点正常运行,才能稳定运行
Gossip 协议,顾名思义,就像流言蜚语一样,利用一种随机、带有传染性的方式,将信息传播到整个网络中,并在一定时间内,使得系统内的所有节点数据一致。
Gossip 的三种方式:直接邮寄(Direct Mail)、反熵(Anti-entropy)和谣言传播 (Rumor mongering)
-
直接邮寄:就是直接发送更新数据,当数据发送失败时,将数据缓存下来,然后重传。可能会因为缓存队列满了而丢数据。也就是说,只采用直接邮寄是无法实现最终一致性的
-
反熵。本质上,反熵是一种通过异步修复实现最终一致性的方法。集群中的节点,每隔段时间就随机选择某个其他节点,然后通过互相交换自己的 所有数据来消除两者之间的差异,实现数据的最终一致性
- 在实现反熵的时候,主要有推、拉和推拉三种方式
- 推方式,就是将自己的所有副本数据,推给对方,修复对方副本中的熵:
- 拉方式,就是拉取对方的所有副本数据,修复自己副本中的熵
- 推拉,就是同时修复自己副本和对方副本中的熵:
- 熵越大越乱,反熵就是降低不一致。
- 因为反熵需要节点两两交换和比对自己所有的数据,执行反熵时通讯 成本会很高,可以通过引入校验和 (Checksum)等机制,降低需要对比的数据量和通讯消息等。如果 是一个动态变化或节点数比较多的分布式环境(比如在 DevOps 环境中检测节点故障,并动态维护集群节点状态),这时反熵就不适用了。
- 在实现反熵的时候,主要有推、拉和推拉三种方式
-
谣言传播:当一个节点有了新数据后,这个节点变成活跃状态, 并周期性地联系其他节点向其发送新数据,直到所有的节点都存储了该新数据:节点 A 向节点 B、D 发送新数据,节点 B 收到新数据后,变成活跃节 点,然后节点 B 向节点 C、D 发送新数据。谣言传播非常具有传染性,它适合动态变化的分布式系统。
三种方式的选择--简单--节点个数不变--节点个数变化
- 直接邮寄的方式是一定要实现 的,因为不需要做一致性对比,只是通过发送更新数据或缓存重传,来修复数据的不一致, 性能损耗低。
- 在存储组件中,节点都是已知的,一般采用反熵修复数据副本的一致性。
- 当集 群节点是变化的,或者集群节点数比较多时,这时要采用谣言传播的方式,同步更新数据, 实现最终一致
如何使用反熵 Anti-entropy 实现最终一致
一份数据副本是由多个分片组成的,也就是实现了数据分片,三节点 三副本的集群
不同节点上,同一分片组中的分片都没有差异。比如说,节点 A 要拥有分片 Shard1 和 Shard2,而且,节点 A 的 Shard1 和 Shard2,与节点 B、C 中的 Shard1 和 Shard2,是一样的。
-
缺失分片:在某个节点上整个分片都丢失了。
-
节点之间的分片不一致:节点上分片都存在,但里面的数据不一样,有数据 丢失的情况发生。
-
第一种情况修复起来不复杂,我们只需要将分片数据,通过 RPC 通讯,从其他节点上拷贝过来就可以了。
-
第二种情况,是按照一定顺序来修复节点的数据差异,先随机选择一个节点,然后循环修复,每个节点生成自己节点有、下一个节点没有的差异数据,发送给下一个节点,进行修复。最后节点 A 又对节点 B 的数据执行了一次数据修复操作,因为只有 这样,节点 C 有、节点 B 缺失的差异数据,才会同步到节点 B 上。
ZAB协议:如何实现操作的顺序性?
ZooKeeper一个开源的分布式协调服务,
ZooKeeper 中,能用兰伯特的 Multi-Paxos 实现各节点数据的共识和一致吗?
不行。因为兰伯特的 Multi-Paxos,虽然能保证达成共识后的值不再改变,对于指定序号的位置,最多只有一个指令(Command)会被选定,但它不管关心达成共识的值是什么,也无法保证各值(也就是操作)的顺序性。兰伯特的 Multi-Paxos 只考虑了如何实现共识,也就是,如何就一系列 值达成共识,未考虑如何实现各值(也就是操作)的顺序性。
ZAB 协议和 ZooKeeper 代码耦合在一 起,无法单独使用 ZAB 协议,先使用其实和Raft是类似的,
为什么 ZooKeeper 不用 Raft 呢?也可以一致性啊,这个问题其实比较简单,因为 Raft 出来的比较晚,直到 2013 年才正式提出,在 2007 年开发 ZooKeeper 的时候,还没有 Raft 呢。
ZAB 是如何保证操作的顺序性的?
与兰伯特的 Multi-Paxos 不同,ZAB 不是共识算法,不基于状态机,而是基于主备模式的原子广播协议,最终实现了操作的顺序性。
- Master-Slave 模型:一个主节点和多个备份节点,所有副本的数据 都以主节点为准,主节点采用二阶段提交,向备份节点同步数据,
- 如果主节点发生故障,数据最完备的节点将当选主节点。
- 为了实现分区容错能力,将数据复制到大多数 节点后(也就是如果大多数节点准备好了),领导者就会进入提交执行阶段,通知备份节点执行提交操作。Raft 和 ZAB 是类似的
- 原子广播协议:广播一组消息,消息的顺序是固定的。
- ZAB 实现了 FIFO 队列,保证消息处理的顺序性。
- ZAB 还实现了当主节点崩溃后,只有日志最完备的节点才能当选主节点
这些特性好像和 Raft 很像。在 Raft 中:
- 所有日志“一切以领导者为准”的强领导者模型
- 领导者接收到客户端请求后,会基于请求中的指令,创建日志项,并将日志项缓存在本地,然后按照顺序,复制到其他节点和提交 ;
- 在 Raft 中,也是日志最完备的节点才能当选领导者。
什么是状态机?
也就是状态的持久化,事务结束了,就持久化这个数据。一个里程碑状态持久化了。
本质上来说,状态机指的是有限状态机,它是一个数学模型。状态机是一 个功能模块,用来处理一系列请求,最大的特点就是确定性,也就是说,对于相同的输入,不管重复运行多少次,最终的内部状态和输出都是相同的。
在共识算法中,通过提议新的指令,达成共识后,提交给状态机执行,来达到修改指定内容的效果,比如修改 KV 存储中指定 key 对 应的值。
为什共识算法需要状态机?
因为:持久化,并且可以修改
在 Multi-Paxos、Raft 中需要状态机,Multi-Paxos、Raft 都是共识算法,而共识算法是就一系列值达成共识的,达成共识后,这个值就不能改了。但有时候我们是需要更改数据的值的,比如 KV 存储,我们肯定需要更改指定 key(比如 X)对应的值,这时我们就可以通过状态机来解决这个问题。
比如,如果你想把 X 的值改为 7,那你可以提议一个新的指令“SET X = 7”,当这个指令 被达成共识并提交到状态机后,你查询到的值就是 7 了,也就成功修改了 X 的值。
Soa和微服务架构有哪些区别?
微服务是在Soa的基础上发展而来,从粒度上来说,微服务的粒度要比SOA更细.
微服务由于粒度更细,所以微服务架构的耦合度相对于SOA架构的耦合度更低.
微服务的服务规模相较于SOA一般要更大,所能承载的并发量也更高.
参考
极客时间:分布式协议与算法实战
posted on 2025-10-13 01:10 chuchengzhi 阅读(5) 评论(0) 收藏 举报