
兄弟们今天想聊一个被问得最多、也最容易被绕晕的话题分布式事务一致性还有 Paxos/Raft 共识。这俩词天天混在一起出现但很多人架构评审时争了半天最后发现说的根本不是同一件事。我见过不少团队一边说要上 Raft 保证数据一致一边又在为订单和库存的最终一致性发愁完全没搞清楚这俩到底谁该管谁。这篇文章就把这层窗户纸捅破它俩分别解决什么问题、在什么场景下协作、工程落地时哪些坑比书上写的更坑。先说个最容易踩的认知误区有人觉得 Raft 是一种“分布式事务方案”还有人觉得只要用了共识算法订单库存就不会超卖。这两种想法都跑偏了。Raft 解决的是“多个副本就某个日志达成一致”分布式事务解决的是“多个服务或多个数据库原子地完成同一笔业务”。一个是副本层的事一个是应用层的事层次完全不同。但现实中又确实存在紧密关联——很多分布式事务协调器的高可用底层靠的就是 Raft。所以这篇博文的核心价值就是帮你把这两个概念拆开揉碎再用一套可落地的架构判断方法串起来。1. 先搞清楚我们说的“一致性”到底是哪一种一致性这个词在分布式系统里被严重滥用不同语境下含义天差地别。我建议所有做架构设计的同学脑子里先把一致性拆成三个抽屉事务一致性、复制一致性、顺序一致性别混着用。1.1 应用层的事务一致性ACID 的那个 C事务一致性说的是数据库在事务执行前后所有约束、索引、外键都能保持正确状态要么全成功要么全回滚。单机数据库靠锁、日志、MVCC 就能搞定一旦业务跨多个库、多个服务事情就变了——你没法再用单个数据库的 undo log 去做全局回滚因为根本没有一个全局的“总控事务管理器”能看到所有资源。我当年第一次做跨库下单的时候天真地把订单库和库存库放进了同一个 Spring 事务里靠着 Atomikos 走 XA。测试环境一切正常一上压测就暴露问题单条链路里涉及多个数据库连接事务动辄秒级数据库连接池直接被打满整个下单接口的 RT 从 80 毫秒飙升到 3 秒以上。这就是第一个要认清的事实分布式事务和单库事务的复杂度不在一个数量级它不再是数据库层面的一个特性而是应用系统层面的一个架构设计。1.2 副本层的复制一致性Paxos/Raft 的那个 CRaft 和 Paxos 解决的一致性问题用大白话说就是一群副本节点如何让它们对一个值达成一致。最经典的场景是数据库主从复制——主节点挂了从节点能不能安全接管多个从节点之间该信谁的数据。Raft 通过领导者选举、日志复制、多数派确认来保证只要大多数节点认为是这个值那这个值就是最终的。这个“多数派确认”是关键。它的核心逻辑是在 N 个节点中任何两个多数派集合必然有交集。也就是说旧领导者的数据和新领导者的数据一定存在至少一个重叠的节点新领导可以通过这个交集把旧数据完整恢复出来。这个数学结论是整个共识算法可靠性的基石——它保证不问过程如何只要法定人数健全新选出的领导者就一定能拿到所有已提交的日志。1.3 一张表说清楚两者的差异不少朋友跟我说“一致性”两个字看着都头大。我直接给一张对照表你以后评审架构时拿这张表去对场景维度分布式事务一致性Paxos/Raft 共识解决的问题多服务/多资源之间原子提交多副本之间的日志和状态达成一致操作粒度业务事务订单、库存、资金日志条目一条命令、一次写请求依赖前提各资源已具备单机事务能力各副本已具备顺序执行日志的能力典型方案2PC、TCC、SAGA、事务消息Multi-Paxos、Raft状态特征最终一致/强一致需要事务状态机线性一致日志被多数派确认即提交底层依赖通常依赖消息队列或协调器依赖网络超时、选举、心跳机制记住这句话分布式事务管的是业务全局状态共识协议管的是副本局部日志。前者像施工总指挥后者像施工队里的班组组长——班组组长保证自己队里的人都按同一张图纸干活总指挥保证整个工地最后按要求交房。2. 分布式事务的实操选择从 2PC 到 SAGA没有银弹这一节咱们聊聊实际选型。我不打算把每种方案做成教科书式的罗列而是结合我自己的实践感受把每种方案的“手感”和“坑位”讲清楚。2.1 强一致的 2PC / XA读起来很美好用起来很沉重2PC 把事务提交分成两个阶段准备阶段和提交阶段。协调者先问所有参与者“准备好了没”都回复 OK 之后再广播“执行提交”。这个模型最直观但有两个致命痛点第一是同步阻塞。准备阶段会持有数据库资源锁任何一个参与者网络超时或宕机整个事务就一直挂着连带一堆业务数据被锁死。我之前做过一个压测对比单库事务平均 20 毫秒跨库 2PC 在 80% 流量下平均时长变成 800 毫秒长尾甚至到 2 秒以上——那还是只有两个库的情况下。第二是协调者单点。2PC 的协调者如果是单机部署它一挂所有未决事务全部悬挂参与者不知道是该提交还是回滚。这时候你不得不引入高可用协调器高可用本身又得靠共识协议去实现这就引出了后文要讲的“共识协议是分布式事务的地基”这个观点。总之2PC 适合强一致且并发量可控的场景比如跨行转账后台清算不适合高并发用户请求链路。2.2 TCC业务补偿的典型套路TCC 是 Try-Confirm-Cancel 三段式。Try 阶段做资源预检查预留Confirm 阶段做真正提交Cancel 阶段做回滚补偿。它比 2PC 轻量因为它不持锁而是靠业务接口的幂等和补偿设计来保证一致性。但 TCC 的坑在于“代码侵入性极强”。每个参与方必须实现三套业务逻辑Try 怎么预留、Confirm 怎么写正式数据、Cancel 怎么把预留的删掉。举个例子库存服务要写 Try 接口去冻结库存Confirm 接口把冻结变成扣减Cancel 接口把冻结解掉。这套逻辑不光是数据库 UPDATE 那么简单还得考虑高并发下的超卖控制。我见过一个团队用了 TCC 之后每个参与服务都膨胀了三套接口光是接口联调和边界测试就占了一个月工时。我个人的使用建议是TCC 适合资金类、强管控类业务而且要严格评估每个参与方的接口改造成本。如果参与方多业务链条长TCC 的复杂度会指数级上升。2.3 SAGA 和事务消息最终一致的两条实用路SAGA 的核心思想是把长事务拆成一串本地事务每个本地事务都有对应的补偿操作正向执行失败就反向依次补偿。它的最大优点是无锁吞吐量高适合订单、库存这类链路型业务。但它的弱点也很明显隔离性差中间步骤对外可见——A 步骤完成了B 步骤失败了别的系统可能在 A 步骤完成后就读到了中间态数据。你需要靠业务规则去容忍或规避这种中间态比如订单状态机把“待支付”“已支付”“库存锁定中”这些状态外露让下游知道该等还是该冲。事务消息本地消息表是另一种非常务实的方案。核心流程业务数据修改和写消息表放在同一个本地事务里通过 MQ 把消息发出去下游消费后执行业务并回执。它的关键点是“本地事务先落库再发布事件”保证消息和业务操作要么一起成功要么一起失败避免丢了消息造成业务漏单。我最推荐中小团队从小步快跑的角度优先试 SAGA 或事务消息原因很简单不锁资源、不搞预分配、不需要参与者硬编码三套接口落地成本低一个量级。至于它和强一致之间的差距用我后文要讲的需求分析方法去评估。2.4 选型对照表给一个直观方案方案一致性强度吞吐影响代码侵入性适用场景2PC/XA强一致高低依赖数据库跨库强一致低并发TCC强一致业务保证中极高资金类、资源预占SAGA最终一致低中高长链路、可补偿业务事务消息最终一致低低订单/库存解耦通用这张表给的是第一直觉真正选型时还要看你的回滚成本、业务容忍度、团队维护能力。比如资金类业务回滚成本极高哪怕 TCC 侵入性强你也得上信息流内容推荐这类业务SAGA 都嫌重一个 MQ状态机就够了。3. Paxos/Raft 共识领导者和法定人数的故事这一节别想复杂了我讲 Raft 用最直白的方式理解它的核心机制之后你自然能分清它和分布式事务的边界。3.1 共识问题的朴素描述共识要解决的就是一堆计算机怎么对同一个值达成一致。你可以把它类比成一群人在微信群里对“我们几点开会”投票Everyone 看到的是同样的聊天记录最后得按同一时间执行。现实中节点会掉线、网络会延迟、消息会乱序群聊协议就得保证不管多乱大多数人认可的结论就是最终结论。Raft 把这个问题拆成了三个子问题领导者选举、日志复制、安全性。其中安全性是核心——它保证所有已提交的日志最终都能被后续领导者完整保留并执行。这个保证靠的是上一节说的“多数派交集”当前任领导者提交过的日志必然存在于某个和当前多数派重叠的节点上因此新选出的领导者一定能找到那条日志不会丢数据。3.2 Raft 的日志复制流程我用一个具体的写入流程给你走一遍假设当前有个三节点 Raft 组领导者是节点 A客户端把写请求发给领导者 AA 把这条命令追加到自己的日志里然后并行广播给节点 B 和 C。B 和 C 收到后写日志并回复 A。A 等到包括自己在内的多数派这时是 2/3都确认后就标记这条日志为已提交然后应用到状态机并回复客户端。整个过程的关键是“多数派确认才提交”而不是所有节点都确认。这个机制带来一个直观结论只要多数派活着系统就能继续服务少数派挂了不影响可用性。但前提是领导者一定不能脑裂。Raft 靠任期编号和随机选举超时来防止脑裂——节点只有在当前任期过期且没收到心跳时才能发起选举新领导者必然拿到大多数节点的投票旧领导者即便网络分区还在运行它的任期已经是旧的写请求不会被多数派新领导者接受。3.3 Raft 能保证什么不能保证什么能保证的多个副本之间日志的一致性、领导者变更安全性、对客户端状态机的线性一致性——只要你在应用层配合使用“通过领导者读写”的路径。不能保证的跨多个独立 Raft 组的业务一致性。比如订单服务用了 Raft库存服务也用了 Raft订单服务和库存服务之间的业务一致性Raft 管不了。这个时候你就需要前文讲的分布式事务方案去协调两个服务的状态。我在实际项目里经常看到的一个误区是把所有服务的数据都塞进一个大的 Raft 组试图让“业务一致性”被日志顺序自动解决。这基本是自找麻烦日志顺序只保证了每个二进制 log entry 的执行顺序但业务事务跨了多个服务之间的网络调用日志顺序无法跨越进程边界事务状态机的灵活性也很差几乎可以宣告这是个失败的架构。4. 两者是上下游关系不是替代关系这是这篇文章的核心结论值得单独开一节讲透。4.1 为什么共识协议是分布式事务的地基想象一下你有个分布式事务协调器它本身是单点挂了事务就悬挂——前文说 2PC 有这个问题。那怎么解决给协调器做一个高可用集群让它选主、日志复制这本质就是 Raft 的事。这意味着很多分布式事务框架的高可用模块底层都靠共识协议撑腰。从这个角度说共识协议和分布式事务从来不是一个维度的东西前者是后者的基础设施就像地基和大楼的关系。另一个典型场景是配置中心。分布式事务里面每个参与者的配置、路由规则、事务模式如 TCC 策略需要统一管理配置中心本身必须保证多副本强一致这时候 Raft 又出场了。一个成熟的分布式事务系统往往同时依赖了 MQ 的可靠性、配置中心的一致性、以及协调器本身的高可用其中配置中心和协调器高可用都离不开共识算法。4.2 数据库副本用 Raft跨库事务用分布式事务这句话基本概括了大多数线上系统的真实部署形态。以 MySQL 为例单库主从复制就是 Raft 或类 Raft 协议的天下比如 MGR 就是基于 Paxos 的但用户一次下单涉及订单库、库存库、优惠券库三个库时库和库之间的原子性就得交给分布式事务框架。你要理清的核心逻辑是数据库主从副本之间用 Raft 保证数据一致解决“机器挂了数据丢没丢”的问题多个数据库之间用分布式事务保证状态一致解决“业务操作成不成功”的问题。这完全是两个维度互不替代。4.3 实际架构中的组合案例我分享一个自己做过的典型订单架构把两者怎么组合讲清楚。整个订单链路分成三块接入层、数据层、异步层。接入层的状态服务用 Raft 集群保证状态读写的强一致比如订单状态机、支付状态缓存这些状态必须绝对准确。数据层的订单库、库存库分别做主从复制主从间用半同步复制或类 Raft 协议保证机架级容灾。异步层负责订单创建后的后续流程比如发短信、送积分、推送物流信息这些用事务消息加本地消息表保证最终一致。这个架构里事务消息解决了“订单创建成功”和“后续动作必须发生”的一致性Raft 解决了“状态服务挂了一个节点用户还能不能读到自己订单”的可用性一致。两者各有分工谁也不替代谁。5. 架构师视角一致性需求的四个判断步骤每次评审架构我都按一套固定框架问自己问题。这套框架帮我把“要不要用分布式事务”“用哪种强度”的争论变成一道选择题。5.1 步骤一能不能容忍最终一致这是第一个分岔口。如果能容忍最终一致直接上事务消息或 SAGA省心省力。如果不能容忍才需要考虑 2PC 或 TCC。怎么定义“能不能容忍”呢我的判断方法是问业务方两个问题用户操作结束后如果不刷新页面多久能看到所有数据变正确这段时间内如果看到中间态会不会引发投诉、损失或资损比如支付成功后余额没立刻变但 30 秒内一定能变——用户能接受如果余额一个月不变那业务方就炸了。5.2 步骤二失败场景的降级方案不管用什么方案总有失败。财务类系统最怕的是失败后产生不可逆的坏账所以这类系统哪怕用 SAGA也得有对账脚本兜底。技术选型时一定要把“失败后的恢复成本”列入评估指标。具体做法每个核心事务必须有事务 ID、状态字段、补偿动作表以及定时扫表的恢复 Job。事务 ID 是幂等的基础状态字段让你知道事务进行到哪一步补偿动作表记录谁已经确认、谁还在等待恢复 Job 负责把悬挂的事务重新调度。5.3 步骤三运维和回放工具是否齐全我见过不少团队上 TCC 上了三个月最后因为补偿逻辑没人维护变成了“定时杀进程”这种野路子。准备引入一种分布式事务方案之前先问自己三个运维问题事务执行出现脏数据能不能一键查出来参与者失败重试日志能不能清晰追踪数据恢复时能不能按事务 ID 精准回滚某一部分这三个问题哪怕有一个回答不了方案就得暂缓。5.4 步骤四常见误区速查最后列几个高频误区评审时对照自查误区一把 2PC 当万金油。并发稍高一点就锁死很多人低估了锁等待的代价。误区二TCC 只写三套接口就完事。你还需要考虑 Confirm/Cancel 的幂等处理否则重复调用会把数据改错。误区三事务消息里的 MQ 一挂就完了。消息可靠性没有兜底方案的设计一定会在线上出问题。误区四Raft 能解决一切一致性问题。Raft 保证的是日志一致不是业务一致两者差着一个“业务调用”的距离。6. 踩坑实录三种典型的“一致性事故”最后分享三个真实踩过的坑都是我亲眼见或亲手处理的比原理更值得记笔记。6.1 脑裂与双主的噩梦当时我们有一个基于 Raft 的高可用缓存集群网络发生了短暂的交换机抖动旧领导者所在分区由于无法联系多数派一直重试但没停止服务。另一个分区选出了新领导者正常接受写入。旧分区恢复后旧领导者发现自己任期落后却把部分未同步的数据推给了客户端——相当于客户端在两个版本间读到不一致。这个坑的根源在于Raft 的安全性依赖“投票任期”机制但客户端读操作如果没有强制走领导者就可能读到旧领导者的过期数据。修复方法是客户端读取强制查领导者节点并校验任期号同时把领导者切换时的连接断开操作做成强制动作禁止旧领导者继续对外服务。这套规则是血的教训换来的——别再以为 Raft 组默认就是安全的。6.2 事务悬挂2PC 的“幽灵分支”有一次我们做跨库资金调整2PC 协调者在准备阶段收到了一个参与者的 OK但在提交阶段网络断了。协调者重试几次仍然失败事务一直处于 undecided 状态。事后网络恢复可事务状态已经丢失用户的钱转了但余额库里一直没扣减。这就是典型的“事务悬挂”——协调者不知道到底提交了没有恢复时也无法判断。我的解决方式分两层第一层协调器必须持久化所有事务状态并配合多副本高可用这里 Raft 就派上用场了第二层每个参与者必须有事务超时回滚机制超过一个明确阈值比如 60 秒必须主动查询事务状态不能再无限等待。两个机制加在一起才能把悬挂时间压制到分钟级。6.3 补偿遗漏SAGA 的“三明治危机”SAGA 的设计是正向调用失败后执行反向补偿。看起来顺理成章但实际执行时经常出现“补偿本身也失败了”的情况。我遇到过一次创建订单后调用库存扣减库存服务超时SAGA 编排器决定执行创建订单的反向补偿但订单服务这时候正好发布新版本补偿接口引入了一个空指针异常结果补偿也失败订单留在了一个半完成状态。从此我在设计 SAGA 时多了一条铁律补偿动作必须“设计成最终可成功的动作”幂等、重试、异常吞掉三个要素缺一不可。补偿动作的重试策略要比正向动作高一个优先级而且每次重试都要报警。另外SAGA 编排器的状态机必须有“人工干预”模式——自动化全部都挂了时允许运维按事务 ID 手动推进状态。我个人在实际操作中的体会是分布式事务和共识协议这两个词最大的“坑”不在技术本身而在团队的认知分层。每次评审前先让每个参会者说出“这个方案要解决的一致性是哪个层次的问题”大部分人说完自己就清醒了。最后再分享一个小技巧不管你选哪种方案保持“全局事务 ID 状态机 定时扫表恢复 Job”的铁三角设计至少能避免 80% 的分布式事务故障。这比任何花哨的协议论证都更实用。