连载中 7/18

本地消息表:最朴素的最终一致

2026-06-17 · 3730 阅读 · 0 评论 · 0 赞

一个两难的顺序题

下单成功后要给积分服务发消息:加积分。看起来一行代码,老王却卡在顺序上——先发消息再提交订单事务?万一订单事务回滚,消息收不回来,用户没下单却加了积分;先提交事务再发消息?提交后进程恰好崩溃,消息永远没发出去,积分永远没加。两难的本质:本地事务和外部调用是两个世界,谁也不认谁的提交。破局的钥匙不是更聪明的框架,是一张表。

方案:业务与消息同库同事务

本地消息表的思路:把「发消息」这件事也变成数据库里的一行记录,和业务数据塞进同一个本地事务。订单和「待发消息」同生共死——事务提交,两个都在;事务回滚,两个都消失:

begin;
insert into orders ...;                    -- 业务数据
insert into local_msg (payload, status)    -- 消息记录,status=待发送
values (...);
commit;                                    -- 同一个事务,原子落库
-- 事务提交后(或后台任务扫描):读消息 → 发 MQ → 标记已发送

之后由后台任务扫描 status=待发送 的记录,投递到 MQ,成功后标记已发送;投递失败就留在表里,下一轮扫描重试。发送再不可靠,也架不住「数据库里躺着一条待发送」——只要记录在,消息迟早出去。这就是至少一次投递的由来:不是保证每次发送成功,而是保证失败永不丢。

四个关键设计点

一:必须同库。消息表和业务表要在同一个数据库实例里,本地事务的原子性才罩得住它们俩——这是整个方案的根基,跨库了就退回第 1 篇的两难。

二:扫描任务要设计。索引建在 status 加 create_time 上,扫描批量拉取(比如每轮 200 条),频率按业务容忍的延迟定(秒级延迟就每秒扫一轮)。量大的业务用「事务提交后顺手异步发一次 + 扫描兜底重试」的组合,绝大多数消息毫秒级出去,扫描只处理漏网之鱼。

三:重试要有阶梯。连续失败的记录按次数退避(1 分钟、5 分钟、30 分钟),超过阈值告警转人工——别让一条永久失败的消息把扫描任务拖死。

四:表要治理。已发送记录定期归档或删除,别让消息表长成大表反噬业务库。

消费端必须幂等

发送端是至少一次,意味着消费端可能收到重复消息(发送成功但标记失败,下一轮又发一遍)。所以「发送端本地消息表 + 消费端幂等」是固定搭配——消费幂等的完整武器库(唯一键、去重表、状态机)第 10 篇专讲,这里先立规矩:没有消费幂等,任何可靠消息方案都是在裸奔。

方案的得与失

维度表现
可靠性消息与业务同事务,零丢失
外部依赖无框架要求,MQ 只要普通发收即可
DB 开销每笔业务多一次消息表写入 + 扫描
时效秒级延迟(异步发可到毫秒级)
限制只适用于发起方有本地事务的场景

老王的评价:土,但极其可靠。不依赖任何分布式事务框架,两张表加一个定时任务就是全部家当,出了问题排查路径清晰(消息表查状态就行)。中小团队的默认选项。它的「土」也被 MQ 厂商看在眼里——RocketMQ 把这套「存起来再投递」的逻辑下沉到了消息服务器内部,那就是下一篇的事务消息。

小结

本地消息表一句话:业务数据与消息记录同库同事务保原子,后台扫描保投递,至少一次发送配消费幂等;方案土但零依赖,中小场景默认选项。想把这个模式从应用层下沉到 MQ 内部、省掉消息表和扫描任务?下一篇:RocketMQ 事务消息,半消息与回查机制。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式事务#本地消息表#最终一致#可靠消息#至少一次

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞