分布式解决方案

分布式事务解决方案

1. XA 两阶段提交协议

两阶段提交协议(Two-phase commit protocol,简称2PC)是一种分布式事务处理协议,旨在确保参与分布式事务的所有节点都能达成一致的结果。此协议被广泛应用于许多分布式关系型数据管理系统,以完成分布式事务。

它是一种强一致性设计,引入一个事务协调者的角色来协调管理各参与者的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。

1、准备阶段(Prepare phase)

  1. TM 向所有 RM 发送 prepare 请求
  2. RM 执行本地事务(但不提交)
  3. RM:
    • Redo / Undo Log
    • 锁定资源
    • 返回 YES / NO

如果 RM 执行成功,向 TM 返回 Ready;下面是两个参与者都执行成功的结果:

如果 RM 执行成功,向 TM 返回 Ready ;如果失败(如违反约束),返回 Fail

2、提交阶段(Commit phase)

这一阶段的行为取决于第一阶段的结果:

  • 情况 A :全部成功 若所有 RM 都返回 ReadyTM 向所有 RM 发送 Commit 指令。RM 完成提交并释放锁。
  • 情况 B :有任何一个失败 若有任何一个 RM 返回 Fail 或超时未响应,TM 向所有 RM 发送 Rollback 指令。RM 利用第一阶段记录的日志进行回滚。

2. XA 三阶段提交

三阶段提交(3PC,Three-Phase Commit)是对二阶段提交(2PC)的改进版。它通过引入超时机制预提交阶段 ,主要解决了 2PC 的阻塞问题

我们将 3PC 拆解为三个阶段:CanCommitPreCommitdoCommit


一、 三阶段的具体流程

1. CanCommit (询问阶段)

协调者向参与者发送 CanCommit 请求。

  • 协调者:询问参与者,你的状态是否可以执行事务?
  • 参与者 :检查自身状态、资源锁情况。如果觉得没问题,返回 Yes,否则返回 No

改进点:这一步不锁资源,只是"轻量级"地确认一下大家是否都活着且有空,减少了后续无谓的资源锁定。

2. PreCommit (预提交阶段)

协调者根据第一阶段的反馈决定。

  • 情况 A(全是 Yes) :协调者发送 PreCommit。参与者执行事务操作,并将 Undo/Redo 信息记入日志(锁定资源),但不提交
  • 情况 B(有 No 或超时) :协调者发送 Abort,参与者取消事务。

改进点:此时参与者已经进入了"准提交"状态。

3. doCommit (正式提交阶段)
  • 执行提交 :如果协调者收到了所有参与者的 Ack(确认),则发送 doCommit 指令。参与者完成事务提交,释放锁。
  • 中断事务 :如果协调者收到任何一个 No 或者指令超时,则发送 Rollback

二、 3PC 解决的核心痛点

3PC 相比 2PC 最大的两个改进是:

  1. 引入超时机制

    • 在 2PC 中,如果协调者挂了,参与者会死等,导致资源永久锁定。
    • 在 3PC 中,如果参与者在 PreCommit 之后,长时间没收到协调者的 doCommit 指令,参与者会自动触发提交(因为既然到了这一步,说明前两步大家都同意了,提交成功的概率极高)。
  2. 降低阻塞范围

    • 通过第一阶段的 CanCommit 探测,排除了由于参与者挂掉而导致的长时间无效锁定。

三、 3PC 依然存在的问题(脑裂)

虽然 3PC 解决了阻塞,但它带来了一个更严重的一致性问题:数据不一致(脑裂)

  • 场景 :进入第三阶段 doCommit 时,协调者发出了回滚(Abort)指令。
  • 异常 :如果网络出现分区,某个参与者没收到 Abort 指令,由于 3PC 的"超时自动提交"机制,这个参与者会在超时后自动提交事务
  • 后果:一部分参与者回滚了,一部分参与者提交了,分布式事务彻底失败,数据不一致。

四、 现实中的选择

在实际的工业级互联网项目中:

  • 3PC 极少被使用。因为它增加了通信次数(由 2 次变 3 次),且依然不能完美解决一致性问题。
  • 主流方案
    1. Seata AT:基于 2PC 改良,利用 Undo Log 解决了 2PC 的长时间锁表问题。
    2. Paxos/Raft 算法:通过"多数派"投票机制保证强一致性,比 3PC 健壮得多。

3. TCC 事务

TCC (Try-Confirm-Cancel) 是一种补偿型分布式事务解决方案,它属于业务层面的柔性事务。

与 Seata AT 等"自动"回滚方案不同,TCC 要求开发者手动编写三个阶段的业务逻辑,因此它对代码的侵入性最高,但性能也是最强的。


一、 TCC 的三个阶段

TCC事务是TryConfirmCancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面人为实现。 2PC3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务。

1. Try (尝试阶段)
  • 核心逻辑 :完成业务检查,并预留必须的业务资源。
2. Confirm (确认阶段)
  • 核心逻辑:执行真正的业务操作。
3. Cancel (取消阶段)
  • 核心逻辑:释放 Try 阶段预留的资源,进行业务补偿(回滚)。

二、 TCC 必须解决的"三大异常"

这是面试和实际开发中最重要的三个坑,必须在代码逻辑中规避:

异常名称 场景描述 解决方案
空回滚 (Empty Rollback) Try 阶段因为网络抖动压根没收到请求,但随后触发了 Cancel 请求。 Cancel 逻辑中需判断 Try 是否执行过,若没执行过则直接返回成功。
幂等 (Idempotency) 由于网络不稳定,Confirm 或 Cancel 指令被多次重复发送。 必须记录事务 ID,每次执行前检查该事务是否已处理。
悬挂 (Hanging) Cancel 指令比 Try 指令先到达(网络拥堵导致)。当 Cancel 执行完后 Try 才到,此时资源被永久锁定。 Try 执行前先检查 Cancel 是否已执行过,若已执行则不再执行 Try。

四、 TCC 的优缺点总结

优点
  • 性能极高:它不需要像 2PC 那样在数据库层面长时间锁行数据。资源锁定是在业务层面完成的。
  • 跨数据库/跨语言:只要是能提供 HTTP 或 RPC 接口的服务,都能纳入 TCC,不受底层数据库 ACID 的限制。
缺点
  • 开发量翻倍:每个业务逻辑都要写三遍(Try/Confirm/Cancel),维护成本极高。
  • 业务耦合重:开发者需要深度参与资源的预留和释放逻辑,对资深开发者的依赖度高。

五、 选型建议

  • 什么时候用 TCC?并发量要求极高资金安全敏感的场景(如:银行转账、秒杀系统余额扣减)。
  • 普通业务(如你的岗位权限配置)
    强烈建议不要使用 TCC 。对于这类低频、非高并发的配置业务,使用 Seata AT本地消息表 效率更高,代码也更整洁。

4. 本地消息表(可靠消息最终一致性)

本地消息表(可靠消息最终一致性) 是互联网系统中最常用、最稳妥的一种分布式事务解决方案,本质思想是:

利用了 各系统本地的事务来实现分布式事务, 把"业务操作"和"发消息"放在同一个本地事务里,通过异步 + 重试 + 幂等,保证最终一致性。

🔹 一、 业务服务(生产者)

本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候,将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。

🔹 二、 消息投递(异步)

后台定时任务轮询 本地消息 表,将"待发送"的消息发给 MQ

🔹 三、 消费者处理(幂等)

消费者服务:接收消息,先做幂等校验,执行业务,MQ 返回确认(Ack )后,将本地消息表的状态改为"已完成"或删除。

🔹 四、重试

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,需要有最大重试次数限制。超过次数(如 5 次)后,将状态改为"失败"并触发人工介入或告警。

可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

优点

  • 简单可靠:不需要引入复杂的分布式事务中间件(如 Seata)。
  • 最终一致性:只要本地事务成功,消息一定能发出去,下游最终一定会处理。
  • 性能较好:不需要像 2PC 那样跨服务锁定资源。

缺点/挑战

  • 业务侵入:每个需要一致性的操作都要手动写消息表,代码较杂。
  • 数据库压力:高并发下,定时任务轮询消息表会占用数据库 IO。
  • 幂等性要求:由于消息可能重发(比如发送成功但更新状态失败),下游服务必须实现幂等校验。

5. 消息事务

RocketMQ 事务消息 是实现分布式事务"最终一致性"的高级方案。它在核心逻辑上与"本地消息表"非常相似,但最巧妙的地方在于:它将消息表的功能内置到了 MQ 内部,从而减轻了应用数据库的压力。


一、 核心流程(三阶段)

RocketMQ 事务消息的实现依赖于 Half Message(半消息) 机制,具体步骤如下:

  1. 发送半消息

    生产者先发送一条消息到 MQ。此时,MQ 会将消息存入一个特殊的内部队列,但不会投递给消费者(消费者不可见)。

  2. 执行本地事务

    生产者收到 MQ 的"半消息发送成功"确认后,开始执行本地数据库操作(如:新增岗位)。

  3. 提交/回滚指令

    • 本地事务成功 :发送 COMMIT 指令。MQ 收到后将消息标记为"可投递",消费者开始消费。
    • 本地事务失败 :发送 ROLLBACK 指令。MQ 直接丢弃该消息。

二、 兜底机制:事务回查 (Check)

如果生产者在发送 COMMIT/ROLLBACK 指令前宕机了,或者网络抖动导致指令丢失,该怎么办?

RocketMQ 会自动发起回查(Check)

  • MQ 发现某条半消息长时间没有指令,会主动询问生产者的一个接口:"这条消息对应的本地事务到底成功了没?"。
  • 生产者检查本地数据库状态,再次返回 COMMITROLLBACK

三、 优缺点总结

优点
  • 解耦更彻底:业务库不再需要维护额外的消息表。
  • 高并发能力:RocketMQ 能够支撑极高的消息吞吐量。
  • 自动重试与回查 :当消费端抛出异常或返回 RECONSUME_LATER 时,RocketMQ 不会丢弃这条消息,而是将其放入 重试队列 ,保证了极端情况下的最终一致性。如果消息达到了最大重试次数依然失败,它会被移送到 死信队列
缺点/注意点
  • 依赖 MQ:如果 MQ 服务挂了,整个业务的事务性会受影响。
  • 开发成本:必须实现回查接口,且需要保证回查逻辑的准确性。
  • 幂等性 :和所有异步方案一样,下游消费者必须实现幂等,防止消息重复消费。

总结

在分布式事务(特别是 RocketMQ 事务消息)的架构中,上游执行成功而下游消费失败是一个非常经典的问题。

RocketMQ 的设计原则是:保证消息一定投递到下游,但不保证下游一定能执行成功。 如果下游执行失败,系统通常会通过以下三个层级的防御机制来处理:

对于你正在做的系统:

  • 如果你的项目已经是微服务架构且已经引入了 RocketMQ,那么直接用事务消息是最优雅的选择。
  • 如果你想快速交付,且不想维护复杂的 MQ 回查逻辑,Seata AT 或者 本地消息表 可能更适合。

你想看一看下游消费者在接收到岗位变更消息时,具体该如何做"幂等校验"的代码示例吗?

什么是分布式 ID?

在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?

这个时候就需要生成分布式 ID了。

分布式 ID 的核心目标是:全局唯一、有序(可选)、高性能、高可用。常见解决方案可以按实现方式分为以下几大类,面试和实际项目里基本都围绕这些展开。


一、数据库类方案

1️⃣ 数据库自增 ID(单库 / 多库)

优点

  • 实现简单
  • 有序、可读性好

缺点

  • 单点瓶颈
  • 扩展性差
  • 高并发下数据库压力大

2️⃣ 数据库号段(Segment / Hi-Lo)

做法

  • 数据库一次分配一段 ID(如 1000 个)
  • 应用内存中自增使用
  • 用完再向 DB 申请下一段

优点

  • DB 访问次数大幅降低
  • ID 趋势递增
  • 性能高

缺点

  • 号段浪费
  • 实现略复杂
  • 重启可能导致跳号

二、缓存 / 中间件类方案

3️⃣ Redis 原子自增

优点

  • 性能极高
  • 实现简单
  • 原子性有保障

缺点

  • 依赖 Redis
  • 持久化 / 主从切换要处理好
  • 跨 Redis 集群复杂

三、时间 + 机器标识类

4️⃣ UUID

优点

  • 本地生成
  • 无中心节点
  • 理论上绝对唯一

缺点

  • 太长(36 位)
  • 无序(v4)
  • 数据库索引性能差

5️⃣ Snowflake(雪花算法) ⭐⭐⭐⭐⭐

结构(64 位)

复制代码
| 1bit 符号位 | 41bit 时间戳 | 10bit 机器ID | 12bit 序列号 |

优点

  • 高性能
  • 趋势递增
  • 本地生成,无网络依赖
  • 分布式友好

缺点

  • 依赖时间
  • 时钟回拨问题
  • 机器 ID 管理复杂

适用场景

  • 高并发核心业务
  • 订单号、流水号

四、Zookeeper 类方案

7️⃣ Zookeeper 顺序节点

优点

  • 强一致
  • 天然分布式

缺点

  • 性能一般
  • 依赖 ZK
  • 不适合超高并发

五、综合对比(面试高频)

方案 有序 性能 依赖 复杂度
DB 自增 DB
号段 ✔✔✔ DB ⭐⭐⭐
Redis INCR ✔✔✔✔ Redis ⭐⭐
UUID ✔✔
Snowflake ✔✔✔✔✔ 本地 ⭐⭐⭐
ZK ZK ⭐⭐⭐

相关推荐
Leon-zy2 小时前
Redis7.4.5集群部署3主3从
redis·分布式·缓存·云原生
小北方城市网3 小时前
SpringBoot 集成 Elasticsearch 实战(全文检索与聚合分析):打造高效海量数据检索系统
java·redis·分布式·python·缓存
资生算法程序员_畅想家_剑魔16 小时前
Java常见技术分享-分布式篇-分布式系统基础理论
java·开发语言·分布式
jonyleek17 小时前
告别硬编码:通过逻辑编排引擎的RabbitMQ监听实现灵活自动化
分布式·自动化·rabbitmq·服务编排·逻辑引擎
Tony Bai20 小时前
【分布式系统】05 时间的幻象 —— Lamport 与 Vector Clock 如何重建分布式因果?
分布式
梁bk21 小时前
[spring cloud] Seata分布式事务管理
分布式·spring·spring cloud
重学一遍1 天前
深啃项目第四篇-kafka
分布式·kafka
利刃大大1 天前
【RabbitMQ】延迟队列 && 事务 && 消息分发
分布式·消息队列·rabbitmq·队列
rchmin1 天前
分布式事务一致性方案介绍
分布式