← 每日面试题 学习看板

如何基于本地消息表实现分布式事务?

面试题/02架构设计题/02分布式/02分布式事务/如何基于本地消息表实现分布式事务?.md

面试记录 2026-09-23

题目:如何基于本地消息表实现分布式事务?

难度:medium

评分:⭐⭐⭐☆☆

我的回答

所谓本地消息表,其实就是定义一个相当于工作链的东西,根据不同的状态去做不同处理。每个状态创建一个对应的本地消息,或者是数据库的记录。然后这时候根据这个消息的状态、消息的记录,在不同的服务去查到这个记录之后,执行自己的事。可能是转到 MQ,或者是怎么样。然后做,根据最后的事务状态做消息状态去,如果成功了就无所谓了,整个事务正常。如果失败了,可能就走定时任务去做处理,或者是失败了发送 MQ 消息,然后对应的有监听的服务去对应的做补偿机制或者是处理。

点评

评分:⭐⭐⭐☆☆

优点:

不足:

补充:

  1. 本地事务阶段:业务 SQL + 写消息表(status=INIT),同一本地事务保证原子性
  2. 消息投递阶段:定时扫表,扫 status=INIT 的消息 → 发 MQ → 更新 status=SENT
  3. 消费确认阶段:消费端消费 → 更新 status=DELIVERED → 业务处理完成更新 status=ACKED
  INIT (本地事务已提交,消息待发送)
    ↓ 定时扫表 + MQ 投递
  SENT (消息已发送,等待消费)
    ↓ 消费端 ACK
  DELIVERED (消费端已确认)
    ↓ 业务处理完成
  ACKED (业务已完成)

完整讲解

第一部分:知识重点剖析

核心考点定位:考察对本地消息表"用本地事务换分布式事务"的精确理解——核心是"业务 SQL + 写消息表 同一事务"。

知识点拆解:

  1. 本地消息表的本质(最关键)
  1. 完整状态机
   INIT (本地事务已提交,消息待发送)
     ↓ 定时扫表 + MQ 投递
   SENT (消息已发送,等待消费)
     ↓ 消费端 ACK
   DELIVERED (消费端已确认)
     ↓ 业务处理完成
   ACKED (业务已完成)

每个状态转换都是幂等的(重复执行结果一致)

  1. 核心组件
  1. 可靠性保证(三大目标)
  1. 与 RocketMQ 事务消息对比
维度本地消息表RocketMQ 事务消息
扫表逻辑业务方自建MQ 内置回查
业务侵入中低
可靠性依赖业务方依赖 MQ
部署复杂度低中
  1. 工程实现要点

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. "如果定时扫表任务挂了,消息怎么办?"
  1. "扫表任务多实例并发运行时,怎么避免重复投递?"
  1. "本地消息表怎么和 RocketMQ 事务消息选型?"

第三部分:面试者参考回答(300-600 字口语化)

面试官您好,本地消息表的核心思想是用"本地事务 + 异步消息"换"分布式事务",让业务方在本地事务里就把消息写进去,保证"业务成功 = 消息存在"。 完整流程分三步: 第一步是本地事务阶段。业务代码在一个本地事务里同时做两件事:执行业务 SQL(比如 INSERT order)和写消息表(INSERT message_table)。这里的关键是两条 SQL 在同一个本地事务里——要么都成功,要么都失败。这样保证了"业务成功 = 消息存在",不会出现"业务成功了但消息没写"的丢单问题。消息表里要存:消息 ID(唯一键)、业务 ID、消息内容、状态(INIT/SENT/DELIVERED/ACKED)、重试次数、下次重试时间。 第二步是消息投递阶段。有一个定时任务在后台扫表,扫 status = 'INIT' AND next_retry_time < NOW() 的消息,通过 FOR UPDATE SKIP LOCKED 加行锁避免多实例重复扫到,然后把这些消息投递到 MQ,更新消息状态为 SENT。如果投递失败,更新重试次数和下次重试时间,等下一轮再扫。 第三步是消费确认阶段。消费端消费 MQ 消息,必须做幂等处理——基于消息 ID 建唯一键,重复消息直接返回成功。业务处理成功后,回写消息表状态为 DELIVERED 或 ACKED。如果消费失败,进入消费端重试;超过最大重试次数转入死信队列,人工处理。 这里有个关键点:本地消息表的可靠性靠三件事保证——本地事务保证"不丢"、消息表主键 + 消费端幂等保证"不重复"、定时扫表 + 消费端重试保证"不丢单"。 和 RocketMQ 事务消息相比,本地消息表的扫表逻辑是业务方自己建的,而 RocketMQ 内置了 half message + 回查机制,业务方代码更简洁。选型看业务方运维能力:想自建可控 → 本地消息表;想用成熟 MQ → RocketMQ 事务消息。 工程落地时还有几个细节要注意:扫表任务要集群化(多实例),监控要覆盖消息积压、状态异常;死信队列要有人工处理流程;消息表要定期归档清理,否则数据会无限增长。 总结一句话:本地消息表的灵魂是"本地事务 + 异步消息",业务方在本地事务里把消息写进去,靠定时扫表和消费端幂等来保证最终一致。这是柔性事务工程化的经典模式,特别适合"上下游解耦、可容忍秒级延迟"的场景。