ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

分布式事务一致性与Paxos/Raft共识:核心差异与工程配合

分布式事务一致性与Paxos/Raft共识:核心差异与工程配合 前几天帮一个团队 review 微服务架构对方同学很认真地在白板上画了一张图三个服务之间用 Seata 做分布式事务每个服务底下的 MySQL 主从复制用了半同步然后指着图跟我说“主从这一层的一致性我们用 Raft 共识来保证”。这句话让我站在原地愣了几秒——先不说 MySQL 主从复制走的是 binlog 加半同步协议跟 Raft 没有直接关系把 Seata 这种分布式事务方案和 Raft 共识算法放在同一个层级去讨论本身就是理解上出了偏差。类似的概念混淆我在各种技术分享、面试答辩、架构评审的场合遇到过太多次。分布式事务一致性和 Paxos/Raft 共识一个是事务处理领域的老大难一个是分布式系统理论的基石名字里都带“一致”大家就容易放在一起比较甚至混着用。这篇东西我想认真梳理清楚它们各自到底解决什么问题为什么经常被并列提起真实工程里又该怎么配合。适合正在做微服务拆分、分布式存储中间件选型或者准备系统设计面试的同学看把这两个概念彻底掰开后面选型不会慌。1. 先搞清楚分布式事务一致性到底在解决什么问题1.1 从一个订单场景说起用一个最经典的例子开场用户在电商平台下单订单服务写一条订单记录库存服务扣减一个库存支付服务冻结一笔金额。如果这三个操作写在同一个数据库事务里一句BEGIN; UPDATE order...; UPDATE stock...; COMMIT;就能保证要么全部成功、要么全部回滚。但系统一旦拆成三个独立部署的微服务、三个独立的数据库实例单机的数据库事务就管不到别人家的数据了。所谓分布式事务一致性核心要解决的问题是跨越多个独立数据源和服务的一系列读写操作如何让它们作为一个整体对外呈现“原子性”。也就是要么所有参与方都提交要么所有参与方都回滚不能出现订单建了但库存没扣、钱扣了但订单没生成这种中间状态。这里必须先分清两个容易混淆的“一致性”语境。第一个语境是 ACID 里的 Consistency它描述的是一个事务执行前后数据满足业务约束的状态第二个语境是 CAP 理论里的 Consistency它描述的是分布式系统各副本对外表现出的线性一致读。分布式事务强调的是一组跨资源操作的事务性它跟“多个副本之间的读一致性”不是一回事。很多时候大家觉得共识算法能解决“一致性问题”其实心里默认的是 CAP 里的线性一致性而事务里的一堆烂摊子共识算法根本覆盖不了。1.2 分布式事务的主流方案清单跨资源的事务协调工程上几十年下来沉淀了几套经典方案。按实现层次从低到高排大概是这么个序列方案核心思想一致性强度典型代价2PC两阶段提交协调者先询问所有参与者能否提交全部同意后统一提交强一致原子性严格协调者单点、参与者阻塞、锁持有时间长3PC三阶段提交在 2PC 基础上引入超时和准备提交状态减少阻塞窗口强一致但引入了更多轮次实现复杂实际使用较少TCCTry-Confirm-Cancel业务层拆成预留、确认、取消三个阶段用业务补偿代替数据库锁最终一致短事务内尽力而为侵入性强每个操作都要写三个方法Saga长事务把长事务拆成多个本地事务失败时逐级反向补偿最终一致无隔离性补偿逻辑难写本地消息表 / 事务消息业务操作和消息写入绑定在同一个本地事务里异步投递最终一致需要消息中间件支持和回查机制这些方案有个共同点它们都在处理“一组不同位置的数据修改”之间的羁绊关系。也就是说问题域天然是**多资源Multi-Resource、多参与者Multi-Participant**的。2PC 把问题收敛到一个协调者身上TCC 把问题下放到业务代码里Saga 把问题摊平成顺序补偿本质都是在为“跨库跨服务的原子性”找出路。1.3 为什么2PC不是万能的很多入门资料会把 2PC 当默认答案但实际生产系统里直接用裸 2PC 的反而少。根本原因在于它的两个固有短板一是阻塞。参与者完成 Prepare 之后资源被锁定必须等协调者发出最终指令。如果协调者这时候宕机了参与者只能在等待中消耗锁资源业务吞吐直接掉到脚面。二是单点。协调者本身就是个单点它一挂整个全局事务悬在半空没有第三方能替它做决定。也别迷信什么“2PC 是强一致所以最好”的说法。2PC 保证的是提交阶段的事务原子性不保证性能不保证参与者不可用时的可用性。数据库内部的 XA 协议就是 2PC 的工程实现用过的人都知道跨库 XA 事务在压力上来时全局锁竞争能把数据库拖到让人怀疑人生。所以现在业界的主流迭代方向是用 TCC、Saga、事务消息这类最终一致方案把锁持有时间缩短把强一致降级成“业务上可接受的对账和补偿”。这里有一层理解非常关键分布式事务无论如何做前提都是“事务参与方彼此知道对方在做什么”。要么通过协调者统一调度要么通过消息把操作串行化。它解决的是参与方之间的意愿一致——你愿不愿意提交他愿不愿意提交大家怎么统一。这个问题的应答者是多个异构的服务和数据库。2. Paxos/Raft共识到底是什么2.1 共识问题的最原始定义如果把分布式事务里的参与者看成多个“都想改自己数据”的独立个体那么共识问题里的节点则是多个“存着同一份数据的副本”。共识算法要回答的问题非常纯粹在可能发生节点宕机、网络分区的情况下一组节点如何对某个值达成一致并且这个值一旦确定就不能被推翻。举个例子一个 KV 存储系统有 5 个副本节点客户端写了一个keyfoo, valuebar这个写入必须让大多数节点都记住。如果有的节点收到、有的节点没收到客户端再次读的时候可能读到旧值也可能读到新值系统就乱了。共识算法的作用就是让这 5 个节点像一个整体一样对“值是什么”达成不可逆的一致。Paxos 的提出者是 Leslie Lamport解决的就是这个副本一致问题。它有一个很反直觉的地方 Paxos 允许系统在节点故障、消息丢失、网络延迟很高的环境下依然对单个值达成一致。而且一旦某个值被选定后面所有节点只能学习这个值不能反悔。这是共识算法安全性的核心——不可推翻性。但要真去吃透 Basic Paxos 的 Prepare、Promise、Accept、Learned 这一套状态机迁移很多人会绕晕。不是 Paxos 本身逻辑有问题而是它屈从于简洁的理论推导把工程上大量可以简化的现实场景比如选主、日志连续复制都抽象掉了。这也是后来 Raft 出现的理由。2.2 Raft比Paxos好在哪里Raft 的出发点不是提出新理论而是把 Multi-Paxos 的思路工程化、可理解化。论文标题就直说了“In Search of an Understandable Consensus Algorithm”。它把共识问题拆成了三个相对独立的子问题Leader 选举节点通过随机超时发起选举获得大多数选票的节点成为 Leader任期内负责接收客户端写请求。日志复制Leader 把客户端的写操作包装成日志条目通过 AppendEntries RPC 广播给 FollowerFollower 本地落盘后回复确认日志被复制到大多数节点后即可提交。安全性Raft 通过选举限制只有日志足够新的节点才能当选和提交限制Leader 只能提交当前任期内的日志保证已提交的日志永远不会被覆盖。拿 Raft 和 Paxos 做对比最经典的一句话是Paxos 解决的是“对单个值达成一致”Raft 解决的是“对一串有序日志达成一致”。后者之所以可行是因为 Raft 一直维持着一个强 Leader所有共识过程都在 Leader 的主导下进行避免了一堆平级节点之间反复协商的复杂度。工程上Raft 用追加日志的方式天然适配复制状态机Replicated State Machine模型——所有副本按相同顺序应用相同日志状态就永远一致。还有一个关键设计领导权唯一性。Raft 用任期Term来划分时间段每个任期最多只有一个 Leader。万一发生分区少数派那边选出的 Leader 会因为无法联系到多数派而无法提交日志这就从协议层面杜绝了“两个 Leader 同时提交导致状态分叉”的灾难。这个约束是很多人在讲 Raft 时最容易忽略但实际最值钱的地方。2.3 工程里常见的Raft实现选型讲完了理论落到工程。Raft 的知名实现非常多而且大多是开箱即用的实现所属项目语言特点etcd/raftetcdGo最经典的 Raft 库etcd 核心适合做服务发现的强一致存储hashicorp/raftConsul 等GoAPI 简洁文档友好适合快速集成SOFAJRaft蚂蚁集团Java生产级 Raft 实现支持多组复制社区活跃raft-rsTiKVRust性能极佳被大量 Rust 分布式系统采用braft百度C百度内部大量业务使用性能强但学习成本高选 Raft 实现的时候一要看它是否支持成员变更加入新节点、踢掉节点二要看它的存储层是否可插拔有的默认存在内存里有的落盘三要看它有没有暴露快照机制。别小看快照日志无限增长不管的话一个集群跑几个月日志重放能慢到让你怀疑人生。3. 两者到底差在哪一张图看清问题域3.1 三个维度拆解核心差异把分布式事务一致性和 Paxos/Raft 共识放到一张表里对比差异就非常明显了维度分布式事务一致性Paxos/Raft 共识问题域多个异构资源之间的操作原子性多个同构副本之间的状态一致参与者不同服务、不同数据库各自管不同数据同一份数据的多个副本节点一致性语义事务原子性全做或全不做线性一致性 / 状态机一致性典型工具2PC、TCC、Saga、事务消息Raft、Paxos、ZABZooKeeper数据模型每个参与者持有不同数据分片每个节点持有同一份数据故障模型参与者故障、网络分区、协调者单点节点宕机、消息丢失、分区后的多数派可用核心差异说白了就一句话分布式事务管的是“多份不同数据的命运绑不绑在一起”共识管的是“同一份数据的多个副本能不能保持一致”。一个是横向跨资源协调一个是纵向副本冗余。前者在业务层、应用层施法后者在存储层、基础设施层做法。很多人问“有了 Raft 做副本同步是不是就不需要分布式事务了”答案显然是否定的。Raft 只能保证同一个数据在 A 节点和 B 节点上长得一样管不了订单库和库存库两个独立数据源之间的业务流转。反过来也一样——分布式事务协议解决不了副本之间因网络分区导致的数据分叉。这俩不是替代关系而是各管一摊。3.2 什么时候会混在一起状态机复制下的“事务”现实中确实有一个场景会让人觉得这俩重叠了那就是基于共识算法实现分布式数据库内部事务的场景。以 TiDB/TiKV 为例TiKV 底层用 raft-rs 做多副本强一致复制保证每个 Region 的数据在多个副本上是一致的但一个跨 Region 的分布式事务仍然要走 Percolator 模型的事务协议——两阶段提交的变体加上 MVCC 时间戳。这时候 Raft 提供的是“副本层面的可靠性”事务协议提供的是“跨 Region 操作的原子性”。两层缺一不可各司其职。又比如 ZooKeeper 用 ZAB 协议和 Raft 同源但更早实现复制你可以在 ZooKeeper 上实现分布式锁但你不能拿 ZooKeeper 本身去管你的订单和库存。它提供的是一种基础的一致性服务至于你的业务要跨多少资源、怎么补偿那得业务层自己设计。这也是为什么很多分布式数据库架构图里你会看到底层清一色铺着 Raft/Paxos 做多副本同步上层又叠加了两阶段提交或优化变体来做分布式事务。两层各解决一个问题组合起来才是一个完整的高可用、强一致的分布式数据库。3.3 常见误区速查表结合这些年在社区、评审、答辩里见到的高频误区整理了一份“避雷清单”误区实际上“用了 Raft 就能保证数据一致性”Raft 只能保证已提交数据在大多数节点上一致读写路径上的客户端行为、缓存、索引都还需要额外设计“Paxos 能替代分布式事务”Paxos 不感知事务的隔离性和回滚事务状态需要上层协议自己维护“2PC 就是共识算法”2PC 是事务提交协议不处理节点故障后的重新收敛也没有多数派概念“Raft 和 Paxos 二选一即可”它们解决同层问题选型只看工程特性事务方案还得另配“强一致永远比最终一致好”强一致意味着锁和阻塞吞吐会受影响业务允许的最终一致往往能换来大幅性能提升4. 真实工程案例订单与库存的分布式事务该怎么落地4.1 案例背景与架构分层前面理论讲得再多不如直接看一个工程案例。假设我们要设计一个“下单扣库存”系统架构分三层接入层用户下单入口生成订单号调用业务服务。业务服务层订单服务、库存服务、账户服务分别独立部署各自使用独立数据库。存储层每个服务的数据库做主从复制用 Raft 或 DRBD 之类的机制保证主数据在第二个机房/第二副本上有备份提供高可用。在这个架构里订单、库存、账户三个库之间的数据一致性是分布式事务的管辖范围而每个库自身的主从副本一致性是Raft 共识的管辖范围。你可以清楚地看到这是两个正交的维度。如果不用分布式事务会怎样最简单粗暴的后果是用户下单成功订单写进去了但扣库存失败超卖就发生了。最终用户收到货系统账面却对不上。所以我们必须选一种机制把这个场景兜住。4.2 TCC与Saga的选型推演先排除 2PC。理由很简单业务接口的响应时间要求毫秒级2PC 的全局锁和协调者阻塞在互联网高并发场景下撑不住。再看 TCC 和 Saga 二选一。TCC 方案订单服务执行 Try 阶段把订单状态置为“待确认”库存服务 Try 阶段预扣库存账户服务 Try 阶段冻结金额三个都成功后进入 Confirm 阶段各自把状态置为最终态。任何一步失败执行 Cancel 反向释放。Saga 方案依次执行“创建订单 - 扣库存 - 扣款”三个本地事务如果到扣款这步失败了反向执行“回补库存 - 取消订单”。我的选择是本地并发不高、公司内部系统用 Saga 编排模式代码简单每个事务方法都是独立本地事务好测试面向 C 端的高并发下单链路用 TCC 更合适因为 Try 阶段已经把资源预占好了Confirm 阶段只是状态翻转速度快不太需要 Saga 里漫长的补偿链。TCC 落地时有个核心功夫在三个方法的幂等和空回滚上。比如 Try 超时了协调者直接发起 CancelCancel 必须能识别“没有成功执行过 Try”的空回滚场景否则会重复释放资源。这个坑我踩过当时 Cancel 里直接写了扣减逻辑线上出现库存变负数排查了半天才发现空回滚没处理。TCC 的每个阶段方法都必须带上事务 ID 和状态标识先查再改保证幂等。4.3 副本同步层用Raft保证可用性再看底层副本一致性。订单库的高可用可以用 MySQL 组复制Group Replication或者外围的复制中间件来实现它们很多底层就是 Paxos 或者类 Paxos 协议。也可以直接用 Raft 自带的多副本能力比如用 TiKV、etcd 这类天生带 Raft 的存储来存关键状态让 Raft 帮我们把同一份数据复制到多个节点。这里面有个细节要留意Raft 的多数派写入是牺牲了一部分写性能换取一致性的。5 个副本要写成功 3 个才返回网络 RTT 决定了写入延迟下限。为了缓解这个成本工程上常配合 Read Index 和 Lease Read 机制做本地读减少读请求也走一遍共识流程。这个话题展开又是好几篇文章简单记一句话共识算法保证写入的确定性读路径的扩展要靠上层缓存和一致性协议配合。5. 一致性收敛机制系统不可能是完美的5.1 对账与幂等设计即便我们用了 TCC 或者 Saga也不可能保证 100% 不出现中间状态。进程可能挂着消息可能丢网络可能闪断。这时候一致性收敛机制就派上用场了——也就是热搜词里那个“一致性正则化机制”的工程含义。我理解它指的是让系统在不一致发生后能自动检测、自动纠正最终回到业务上可接受的一致状态。实际落地最常用的三板斧对账任务每天离线跑一张核对表把订单状态、库存流水、支付流水拉出来比对发现对不齐的自动触发补偿或者告警。幂等表每个业务操作都带全局唯一 ID在数据库里建一张幂等记录表重复请求来了直接忽略。状态机校验所有业务单据维护一个状态机待支付、已支付、已发货、已完成允许的迁移路径只有一套非法迁移直接报错。这三板斧配合好即使分布式事务在某一环节出了问题系统也能通过下一次重试或离线对账“自愈”。这也是最终一致方案的底气所在——它不追求每个瞬间的强一致但保证一个时间窗口内收敛到一致。5.2 设计一致性收敛时的注意点做收敛机制容易踩几个坑。第一个是补偿操作本身也要幂等。你扣库存失败了调补偿补偿失败再补偿如果补偿的补偿还不幂等就会雪崩。所以补偿操作必须每次使用同一补偿单据 ID数据库层加唯一约束。第二个是对账要有明确的业务口径。订单和库存怎么算对得上要定义清楚一个订单对应 N 件商品库存扣减流水总和 订单商品件数。没有口径的对账就是耍流氓最后告警刷屏也没人看得懂。第三个是收敛要有超时和熔断。某一条数据的补偿一直失败不能无限重试要标记为“人工介入”状态转入告警。线上系统最怕静默地不断重试把资源耗尽。我见过一个项目补偿任务跑死日志一天涨了几十个 G就是因为没有熔断。6. 社区高频问题与排查实录6.1 经典误区Raft能替代分布式事务吗这个问题几乎在每个分布式系统社区都会出现。答案在前面已经说了不能。但我想补充一个更具体的视角Raft 能保证的是“同一个状态机在多个节点上的一致”不能保证“两个不同的状态机之间的业务流转”。举个极端例子订单服务有一个 Raft 集群保证订单状态强一致库存服务也有一个 Raft 集群保证库存状态强一致。但订单服务往库存服务发扣减请求时网络闪断。Raft 只能保证各自集群内部没问题但“订单侧认为扣了、库存侧实际没扣”这种分布式事务语义得靠事务协议或者最终补偿来保证。Raft 管得再严也管不到另一个集群的提交。6.2 2PC阻塞与协调者故障的处理如果你还是想在特定场景用 2PC比如内部系统、低并发、强一致需求要处理的两个核心故障是协调者宕机和参与者超时。实践中有几个调整手法给协调者做 HA用 Raft 再搭一个协调者集群保证协调者本身不单点。这倒是 Raft 服务分布式事务的典型用法etcd 经常被用来当这个协调者状态存储。参与者 Prepare 成功后必须设一个事务超时时间超时后自动回滚本地事务不要一直傻等协调者。协调者恢复后要做事务状态恢复读取事务日志补发 Commit 或 Abort。没有事务日志的 2PC 实现就是定时炸弹。6.3 脑裂与选主抖动Raft 集群在生产环境最典型的痛点是选主抖动。原因通常是网络分区、磁盘延迟、GC 停顿导致心跳超时Follower 发起选举而老 Leader 还在。处理思路包括调大心跳超时倍数。Raft 论文建议心跳超时是广播时间的 10 倍以上很多项目默认值设置得太激进导致抖动频繁。避免把 Raft 节点和重负载业务混部。GC 停顿或者 CPU 飙高一停就是几百毫秒选举就来了。监控 Term 变化。如果 Term 一直在涨说明集群频繁换主八成是网络或者调度问题要查链路而不是查共识算法本身。6.4 排查思路清单最后放一个排查速查清单遇到一致性问题按这个顺序走思路基本不会乱先确认是哪一层的问题是业务层跨服务事务还是存储层副本不一致。两层排查手段完全不同。看有没有对账机制。没有对账先补对账再谈定位。查幂等。很多“丢数据”其实是重复写入覆盖了幂等表一查便知。查状态机。单据状态是否跳了非法路径能从业务侧直接暴露脏数据。查共识集群健康Term、Leader 是否稳定、日志是否积压。把分布式事务的协调记录和共识集群的写入记录放一起比对谁先谁后一目了然。这套方法在大型系统里很实用。说穿了分布式系统一定会出问题关键是要快速定位到问题出在哪个层次。层次分清楚了处理手段是现成的。我在实际项目中体会最深的一点是分工越明确系统越稳定。拿到一个系统第一件事不是选框架而是划清楚哪些一致性是分布式事务负责的哪些是共识算法负责的哪些是业务补偿负责的。把这个边界划清后面每一步都有据可依。最后再送你一个小技巧不管是写 TCC 的 Cancel 还是 Saga 的补偿先写幂等再写业务逻辑这个顺序能帮你避开线上至少一半的脏数据。
返回列表