连载中 2/20

引入 MQ 的代价:天下没有免费的午餐

2026-07-29 · 3848 阅读 · 0 评论 · 0 赞

先交代一起事故

上一篇结尾提过:老王把 MQ 插进下单链路的第三周,会员王阿姨的积分翻了一倍。排查下来原因很简单——积分服务消费超时,MQ 没等到 ACK,按规则重投了一次。两次消费,两份积分,客服电话响了一上午。

这不是 MQ 的 bug,而是它的设计使然。这一篇把引入 MQ 要背的债一条条摆上桌——先把代价看清楚,再决定吃不吃这顿午餐。

代价一:一致性从数据库问题变成了分布式问题

同步调用时代,订单和积分库哪怕是两个库,下单服务也能在业务上保证先后:先扣库存成功,再发积分,失败了就补偿。调用方看得见每一步的结果。

改成消息之后,事情劈成了两半:

  • 订单落库成功,消息没发出去——下游永远不知道有这单,积分丢了一份。这是「发消息」和「写数据库」两步之间的原子性问题;
  • 消息发出去了,订单落库失败——下游白忙一场,凭空多了笔积分。

数据库事务的边界只到自己那台库,伸不进 MQ,也伸不进下游的库。原子性要靠新的机制去补:本地消息表、事务消息、对账补偿——这些是第 10 篇的主菜,这里先立个碑:拆开同步的那一刀,同时切断了事务的边界。

代价二:重复消费是常态,不是意外

王阿姨的积分翻倍不是倒霉,是必然。MQ 的可靠性承诺叫「至少一次投递」(At Least Once):消息宁可重发一千次,也不允许悄悄丢掉。为什么这么设计?因为发送方和 Broker 之间的确认可能丢——消息明明送到了,ACK 却在网络里迷了路,发送方只能重发。想做到「恰好一次」,要么用两阶段提交把性能拖垮,要么就得把幂等做进业务里。

所以消费端的铁律是:每一条消息都可能被投递多次,消费逻辑必须幂等。加积分这种操作要带业务单号去重,而不是裸 add。幂等的具体三板斧(唯一键、状态机、去重表)在第 7 篇展开。

代价三:一条消息的一生,横跨五个系统

同步调用排障:看调用链,A 慢就是 A 的问题,一步到位。消息链路排障:消息从生产者发出,进 Broker 存储,路由到队列,被消费者拉走,消费失败进重试,重试耗尽进死信——每一跳都可能出事,每一跳的日志在不同的系统里。

老王后来复盘积分事故时,光是把「消息到底投递了几次」查清楚就花了俩小时:生产端日志在订单服务,投递次数在 MQ 控制台,消费日志在积分服务,三者还对不上时间戳。引入 MQ 的那一刻,就必须同步上链路追踪:给每条消息带上全局唯一的 messageKey 和 traceId,否则出事时你连案发现场都拼不出来。

代价四:运维是一份新工作

MQ 自己就是一个分布式系统:要部署、要扩容、要监控、要升级、要做磁盘容量规划。队列积压要有人看,磁盘打满会导致 Broker 拒写,版本升级可能触发协议变更。小团队上 MQ 之前先掂量:有没有人能接住它出事的时候。用云厂商的托管服务可以省掉一部分,但监控告警、积压处理、容量规划这些事省不掉。

代价五:延迟变得不确定

同步调用的延迟是可预期的:下游 100ms,那就是 100ms。消息链路的延迟取决于队列深度:空闲时毫秒级,积压十万条时可能要等几分钟。异步的延迟是「尽力而为」,不是「承诺兑现」。业务上对时效敏感的路径(比如库存扣减),别指望 MQ 给你确定性。

哪些场景不该用 MQ

场景特征为什么不该
强一致要求:扣款、扣库存异步意味着窗口期不一致,超卖就是这么来的
调用链短且量小两个服务每天百来条消息,引入 MQ 纯属给自己找事
调用方需要立即拿到结果MQ 不返回业务结果,查询类请求老老实实走 RPC
没人维护 MQ引入一个没人接得住的中间件,等于埋雷

上 MQ 之前,先问四个问题

  • 这条支路允许延迟和最终一致吗?不允许,就留在同步链路里;
  • 消费逻辑能改成幂等吗?不能改,先别上队列,先改代码;
  • 出问题时有链路追踪和对账手段吗?没有,先把可观测性补齐;
  • 谁值班谁会修?答不上来,先培训或者先别上。

四个问题都答得上来,这顿午餐才吃得踏实。

小结

MQ 的代价清单:一致性要重新设计、重复消费必须幂等、排查链路变长、运维多摊子事、延迟不再确定。它换来的解耦、异步、削峰都是真金白银,但先掂量债务,再谈收益。

代价认清了,下一个问题是:市面上 RocketMQ、Kafka、RabbitMQ 三大主流各有性格,哪一台适合老王的咖啡馆?下一篇做选型,把三家的账摊开来算。

☕
503

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

#消息队列#MQ#一致性#幂等#架构权衡

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞