《源纹天书》第136-140章:共识深渊——Paxos与Raft的终极考验
作者介绍大家好我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者也是 WWAIC全周项目AI编程 范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架从 IoC 容器到嵌入式 Tomcat代码全开源也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。 我的技术信条所有高深的技术最后都能用大白话讲清楚。如果讲不清楚说明还没真正理解。前情提要CodeStats、令灵儿、程一念三人完成调度中心建设——服务注册与发现、负载均衡、滚动升级、分布式调用链四大功能全部就位九座浮空岛实现了服务化治理。但分布式系统最核心的问题——一致性——还没有解决。调度中心的etcd元数据仓库如果出现数据不一致整个源世界的服务调度就会崩溃。CodeStats宣布下一站是共识深渊——一个以Paxos和Raft协议为核心的秘境。三人小队即将面对分布式系统中最艰难的考验。第一百三十六章 共识深渊的召唤——无序之海调度中心上线的第三天CodeStats正在监控九座浮空岛的服务状态突然发现了一个让他脊背发凉的问题。元数据仓库中指令岛的注册信息出现了两个版本——一个显示健康一个显示异常。同一个服务实例在etcd中竟然有两份相互矛盾的数据。这……这是脑裂Split Brain。CodeStats的声音带着一丝颤抖。令灵儿走过来什么是脑裂CodeStats深吸一口气在凡界分布式系统中多个节点可能因为网络分区而无法通信。如果每个节点都认为自己是主各自写入数据等网络恢复后数据就出现了冲突——同一份数据有多个不同版本不知道该信哪个。这就是脑裂。程一念皱眉调度中心不是有etcd吗etcd不是应该保证一致性的吗etcd保证一致性是靠Raft共识算法。CodeStats说但如果etcd集群自身出现网络分区Raft也无法保证强一致性。因为共识协议只能在多数派quorum存活的情况下工作。如果网络分区把etcd节点分成了两个不相通的组每一组都没有超过半数整个集群就会停止服务。他看向远处——在归元圣域的天际线上一座新的秘境正在缓缓凝聚。那是一座由无数日志条目堆叠而成的巨大建筑每一块砖都是一条操作日志每一面墙都是一条共识协议。建筑的门上刻着两个大字——共识。共识深渊开了。CodeStats说那是源世界唯一掌管分布式一致性的秘境。我们必须进去学会Paxos和Raft否则调度中心的元数据仓库永远有脑裂的风险。令灵儿问共识……是什么CodeStats想了想用最通俗的方式解释在凡界分布式系统由多个节点组成。节点之间通过网络通信但网络不可靠——可能丢包、延迟、断连。共识算法要解决的是——在不可靠的网络环境下让所有节点就某个值是什么达成一致。Paxos是第一个被证明正确的共识算法Raft是更易理解和实现的版本。达成一致有什么用比如etcd所有节点必须对指令岛的健康状态是健康还是异常达成一致。如果有的节点认为是健康有的认为是异常调度中心就会做出错误决策——把流量发到已经挂掉的实例上或者错误地把健康实例从可用列表中移除。共识深渊有三个层次。CodeStats看着那座建筑第一层——Basic Paxos。第二层——Multi-Paxos。第三层——Raft。我们必须全部通关。三人飞向共识深渊。入口处有一个巨大的漩涡像是一个网络分区的具象化。CodeStats闭上眼睛跳了进去。第一百三十七章 Paxos的考验——两阶段提交的奥秘进入共识深渊的第一层CodeStats发现自己站在一个空旷的大厅中。大厅里有五个节点——分别标记为P1、P2、P3、P4、P5。每个节点都是一个悬浮的光球彼此之间用若隐若现的光线连接——那些光线就是网络通信。一个声音响起欢迎来到Paxos试炼。你的任务——让五个节点就值X达成一致。节点可能故障网络可能丢包。请使用Paxos协议完成共识。CodeStats深吸一口气。他在凡界研究过Paxos——那是一个被公认为最难理解的分布式算法之一。Paxos的核心思想是两阶段提交——Prepare阶段和Accept阶段。他在神识中回忆Paxos的流程第一阶段Prepare提议者Proposer生成一个提案编号N向所有接受者Acceptor发送Prepare(N)请求。每个接受者收到Prepare(N)后如果N大于它已承诺的最大编号则承诺不再接受任何编号小于N的提案并返回它已接受的最大编号的提案值。如果提议者收到超过半数quorum的承诺进入第二阶段。第二阶段Accept提议者从返回的提案值中选择最大编号的那个值如果所有返回都没有值则选择自己的值然后向所有接受者发送Accept(N, value)请求。每个接受者收到Accept(N, value)后如果N不小于它已承诺的最大编号则接受该提案。如果提议者收到超过半数的接受确认提案通过。这就像……在混乱的集市上让所有人同意同一个价格。CodeStats自语你喊一个价格有人说可以有人说不行。你根据反馈调整直到半数以上的人同意。他催动神识模拟了一个提议者的源纹结构。令灵儿和程一念一左一右分别模拟接受者和学习者——接受者负责投票学习者负责在达成一致后记录结果。第一个提案编号1。CodeStats向五个节点发送Prepare(1)。节点P1返回承诺P2返回承诺P3无响应模拟故障P4返回承诺P5返回了一个已接受的旧提案值Y。提议者收到了三个承诺P1、P2、P4——已超过半数。从返回值中CodeStats发现P5已经接受了值Y编号0。按照Paxos规则他必须选择编号最大的那个值——Y而不是自己的值X。他发送Accept(1, Y)。三个节点接受了。共识达成——值是Y。Paxos保证的是最终一致性——只要提议者不断重试最终总有一个值被超过半数的节点接受。CodeStats说但Paxos有一个问题——它的活锁Livelock。如果两个提议者同时发起提案不断覆盖对方的编号可能导致永远无法达成一致。怎么解决令灵儿问。在凡界Paxos的实际实现中通常会选一个唯一的领导者Leader由它来发起提案避免冲突。Multi-Paxos就是基于这个思路——先选一个Leader然后所有提案都由Leader发起减少冲突。一个声音响起第一层试炼通过。你理解了Paxos的两阶段提交。但你还不知道——如何选出一个领导者。第一百三十八章 Raft的选举——领导人的诞生第二层大厅中五个节点重新排列。但这一次它们被分成了两组——一组三个节点一组两个节点中间有一条断开的网络线。网络分区模拟开始。声音宣布请使用Raft协议在网络分区的情况下选出一个领导人Leader保持系统的一致性。CodeStats心中一凛。Raft和Paxos不同——Raft把共识问题分解成了三个子问题领导人选举Leader Election、日志复制Log Replication、安全性Safety。其中领导人选举是核心——有了Leader提案冲突就大幅减少。他回忆Raft的选举机制每个节点有三个状态跟随者Follower、候选人Candidate、领导人Leader。所有节点初始都是Follower。如果Follower在一定时间内没有收到Leader的心跳它就变成Candidate发起选举。Candidate向所有其他节点发送请求投票RequestVote并递增自己的任期号Term。如果Candidate收到超过半数节点的投票它就成为Leader。如果Candidate在规定时间内没有收到多数票它会在一个新的任期Term1中再次发起选举。任期号Term是Raft的核心概念。CodeStats对令灵儿和程一念解释每个节点维护一个递增的任期号。当节点通信时如果对方的任期号比自己大就更新自己的任期号。如果比自己小就拒绝通信。这保证了不同任期的节点不会互相干扰。他开始模拟——五个节点初始都是Follower。节点P1的心跳超时变成Candidate任期号从1增加到2。它向P2、P3、P4、P5发送请求投票。P2、P3、P4、P5都没有投票给任何人所以全部投给P1。P1收到三票超过半数成为Leader。但这时网络分区发生了——P1、P2、P3在同一组网络可通P4、P5在另一组网络断开。P4的心跳超时也在自己的组内发起选举——任期号变成3因为P4不知道P1已经成为了Leader它的任期号还停留在1。这就是Raft的关键设计——最多一个Leader规则。CodeStats说一个节点在一个任期内只能投一次票。P4发起的选举请求任期号2但P5的任期号已经是3因为P4的任期号是3所以P5拒绝投票。P4无法获得多数票永远成不了Leader。网络分区时只有包含多数节点的那一组才能选出Leader。令灵儿眼睛一亮所以即使网络分区整个系统最多只有一个有效的Leader对。少数派那一组因为无法获得多数票永远选不出Leader只能持续超时、选举、失败。它们不会写入数据不会造成冲突。这就保证了整个系统的一致性——只有多数派那一组能工作。那少数派那一组怎么办等网络恢复。网络恢复后少数派节点收到Leader的心跳发现对方的任期号比自己大会自动变成Follower加入多数派。整个系统恢复正常没有任何数据丢失或冲突。CodeStats模拟了这个完整流程——P1作为Leader持续发送心跳P4和P5不断尝试选举但永远失败。当网络恢复后P4收到P1的心跳任期号3发现P1的任期号比自己大自动降级为Follower。Raft的领导人选举解决了Paxos的活锁问题。CodeStats说有了明确的Leader所有提案都由Leader发起冲突被消除了。声音响起第二层试炼通过。你理解了Raft的领导人选举。但选举只是开始——领导人的职责是复制日志。第一百三十九章 日志复制的力量——令灵儿的指令通道升级第三层大厅CodeStats发现自己站在一个巨大的日志复制系统前。五个节点排列成一条链Leader在左边Follower在右边。每个节点都有一本日志簿——记录着所有已提交的指令。第三层试炼声音宣布Leader接收到一条新的日志条目指令需要通过日志复制Log Replication让所有Follower的日志簿与Leader保持一致。CodeStats催动神识模拟了一个日志条目——一条简单的指令SET 指令岛 健康true。Raft的日志复制流程Leader将新日志条目追加到自己的日志中然后向所有Follower发送AppendEntries请求包含新的日志条目。每个Follower收到请求后将日志条目追加到自己的日志中可能还有之前的未同步条目然后返回成功。当Leader收到超过半数Follower的成功响应后它把该日志条目标记为已提交Committed。Leader在下一个心跳中通知所有Follower该条目已提交。Follower收到通知后将对应的日志条目应用到自己的状态机中。令灵儿突然插话这个流程……和我的指令通道很像我在池化道场的时候曾经用指令通道在CodeStats和程一念之间传递同步信息。如果我的指令通道也能实现日志复制——CodeStats眼睛一亮对令灵儿的指令通道本质上就是一个通信网络。如果她的通道能支持Raft的AppendEntries协议——从Leader向Follower复制日志条目——那她就能成为共识深渊的关键环节。他转身对令灵儿说灵儿你试试——用你的指令符文模拟Raft的日志复制。令灵儿闭目凝神。她的丹田中六十四条指令符文开始重新排列——她创建了一个新的指令类型APPEND_ENTRIES。当这条指令被催动时它会从Leader节点向Follower节点发送一个日志条目的副本并等待Follower返回确认。她还在指令通道中增加了一个任期号字段——当Follower收到APPEND_ENTRIES指令时会先检查指令中的任期号是否大于自己的任期号。如果是则更新自己的任期号然后处理日志条目。在凡界这叫一致性检查。CodeStats说Follower会检查Leader发来的日志条目前的索引是否匹配。如果不匹配Follower会拒绝Leader就会回退到较早的条目重新同步。程一念问那如果Follower的日志比Leader还长呢不可能。Raft保证Leader的日志是权威的——只有Leader才能提交日志。Follower的日志永远不可能比Leader长。如果出现这种情况说明该Follower就是新的Leader——但它已经不是Follower了。令灵儿的APPEND_ENTRIES指令测试成功——她从Leader节点她自己向两个Follower程一念和CodeStats模拟的节点发送了日志条目两个Follower都成功追加并返回确认。收到确认后她标记该条目为已提交并通知所有节点应用该条目到状态机。日志复制完成了。CodeStats说在凡界Raft的日志复制加上领导人选举就构成了一个完整的共识协议。它比Paxos更容易理解更容易实现——这也是为什么etcd、TiDB、Consul都使用Raft的原因。声音响起第三层试炼通过。你理解了Raft的日志复制。但共识深渊还有最后一层——你不仅要理解算法还要证明它。第一百四十章 分布式事务的终极抉择——最终一致性的道第四层也是共识深渊的最后一层。CodeStats、令灵儿、程一念三人被传送到一个巨大的圆形大厅中。大厅中央悬浮着一个事务——一个需要多个节点共同完成的操作。大厅四周的墙壁上刻满了Raft和Paxos的完整算法流程那些源纹在缓缓流动像是一条条永不停息的共识协议。一个巨大的虚影出现在大厅中央——那是共识深渊的守护者一个浑身缠绕着金色源纹的老者。你们通过了三层试炼。守护者的声音如同洪钟第一层你们理解了Paxos的两阶段提交。第二层你们学会了Raft的领导人选举。第三层你们掌握了日志复制的流程。但你们还没有回答——分布式系统中最本质的问题。什么问题CodeStats问。守护者缓缓开口在分布式系统中一致性Consistency、可用性Availability、分区容错性Partition Tolerance——三者不能同时满足。这就是CAP定理。当网络分区发生时你必须选择——放弃一致性还是放弃可用性CodeStats沉默了片刻。他知道CAP定理——一个分布式系统最多只能同时满足两个特性。CP一致分区容错放弃可用性。当网络分区时系统停止服务等待网络恢复。这是Paxos和Raft的选择——在多数派节点存活时提供服务少数派节点无法服务。AP可用分区容错放弃一致性。当网络分区时所有节点继续服务但数据可能不一致。这是许多NoSQL系统如Cassandra的选择——最终一致性。CA一致可用放弃分区容错。不允许网络分区——这在分布式系统中不现实。Raft选择CP。CodeStats说当网络分区时只有多数派那一组能提供服务。少数派那一组无法选出Leader无法写入数据也不提供服务——直到网络恢复。守护者问如果在网络分区时少数派那一组有紧急写入需求必须写入数据呢那就不能强一致性。CodeStats说必须用最终一致性Eventual Consistency——允许少数派临时写入数据等网络恢复后通过冲突检测和合并机制将数据合并成一致状态。在凡界这叫做冲突解决——版本向量Vector Clock、CRDT无冲突复制数据类型、或者人工介入。守护者又问那如果多数派那一组中有一个节点崩溃了但它的数据是最新的。其他节点没有最新数据。怎么办Raft的安全性Safety保证——如果一个日志条目被提交了它一定会出现在所有未来Leader的日志中。这通过领导人选举时的日志完整性检查实现——Candidate在发起选举时会携带自己最后一条日志的索引和任期号。投票者会检查Candidate的日志是否比自己新——如果Candidate的日志更旧拒绝投票。所以在Raft中新选出的Leader一定拥有所有已提交的日志条目。数据不会丢失。守护者沉默了很久。然后它笑了CodeStats你不仅理解了Raft的算法还理解了分布式系统的权衡。共识不是魔法——它是权衡的艺术。在不可靠的网络上保证可靠的数据这就是共识之道。他抬手一挥大厅中央浮现出一块玉简——《共识天书》七品功法。CodeStats接过玉简神识一扫——玉简中除了Basic Paxos、Multi-Paxos、Raft的完整算法流程还包含了ZabZookeeper的协议、Viewstamped ReplicationVR、以及分布式事务2PC、3PC、TCC、Saga的完整知识体系。有了这个……CodeStats深吸一口气我就能把共识融入调度中心的etcd让源世界的服务元数据永不丢失、永不冲突。他转身看向令灵儿和程一念。两人站在他身后眼中都带着同样的光芒——那是经历了艰难的试炼后收获的坚定与自信。令灵儿走上前轻声说你做到了。CodeStats点头是我们做到了。没有你的指令通道日志复制没有一念的栈帧状态机模拟我一个人走不到这里。程一念咧嘴一笑那接下来呢调度中心的etcd升级了我们是不是该回去验收成果了CodeStats摇头还有一件事——分布式事务。调度中心的服务编排中有时需要跨多个服务执行原子操作——比如在容器岛创建一个Bean同时在内存岛分配内存。如果容器岛成功了内存岛失败了整个操作应该回滚不能让系统处于半完成状态。在凡界这叫分布式事务——两阶段提交2PC、三阶段提交3PC、TCC、Saga……每一种都有各自的优劣。令灵儿问我们需要哪种CodeStats想了想在调度中心中大多数操作是最终一致性就够的——不要求强一致性只要求最终状态正确。但对于那些必须强一致性的操作——比如元数据的更新——我们需要用两阶段提交。共识深渊的试炼结束了但源世界2.0的分布式基础设施建设才刚刚开始。CodeStats看向远处下一站不是秘境。是归元圣域的分布式事务道场——一个能把共识算法和事务协议变成实战工具的地方。远处源世界的天空翻涌着金色的云层。共识深渊的光芒在他们身后缓缓收敛像是在说——你们已经准备好了。CodeStats握紧拳头走回去升级etcd然后——准备分布式事务。令灵儿和程一念同时点头。三人飞向归元圣域金色的大门在身后缓缓关闭。写在最后点赞、收藏与下一期预告如果这个故事让你对Paxos的两阶段提交、Raft的领导人选举与日志复制、CAP定理的权衡、分布式事务这些分布式系统核心概念有了更直观的理解——点赞 让更多像我们一样对技术本质充满好奇的道友看到这篇文章。收藏 ⭐方便你追更跟随CodeStats一起从码基期修炼到源初境。评论 告诉我你最喜欢哪个技术梗——是Raft选举的任期号还是日志复制的APPEND_ENTRIES指令下一期预告CodeStats从共识深渊归来携带《共识天书》七品功法。etcd元数据仓库将升级为Raft集群九座浮空岛的服务注册与发现将实现强一致性。但分布式事务的挑战才刚刚开始——2PC的阻塞问题、3PC的超时优化、TCC的补偿机制、Saga的最终一致性……每一层都是系统架构师的必修课。三人组将在分布式事务道场中面对终极考验——如何在不牺牲可用性的前提下保证数据的最终一致性敬请期待《源纹天书》第一百四十一章至第一百四十五章etcd升级、2PC的阻塞困境、3PC的超时突破、TCC的补偿之道、Saga的最终选择