活动开始后,下单接口最先扛不住。
用户点击下单,接口里要校验活动、检查库存、创建订单、扣优惠券、发通知、更新报表。平时这条链路没问题,活动流量一上来,接口开始超时。
这时候很多人会说:加 MQ。
这个判断不一定错,但它只说了一半。MQ 可以把同步链路拆开,可以把突发流量先接住,但它不会让压力消失。它只是把压力从接口线程、数据库事务和同步调用里,转移到了队列、消费者和补偿逻辑里。
如果后面消费能力跟不上,问题不会消失,只会换个名字:消息积压。
消息队列最容易被说成一句话:削峰填谷。
但真正上了项目以后,你会发现它没有这么轻松。流量是削下来了,可积压从哪里看,失败怎么重试,重复消费怎么处理,消费者挂了以后谁来补,都是要落到具体代码和运维动作里的。
我以前也见过一种情况:接口很快返回成功,业务方以为事情已经完成了。实际上消息还在队列里排着,后面的消费者已经慢了十几分钟。
这时候队列不是解决了问题,而是把问题换了一个地方继续等你。
接口变快只是第一步,后面的积压、重试、幂等、补偿,才是项目里真正磨人的地方。
一、削峰是把流量摊平,不是把流量变没
活动高峰最难的是瞬时流量。
一秒内涌进来的请求,数据库和下游服务不一定能即时处理。MQ 的价值,是先把请求接住,再让消费者按系统能承受的速度慢慢处理。
突发请求
-> 写入 MQ
-> 消费者按能力消费
-> 数据库稳定写入这叫削峰填谷。
但这里有个前提:业务允许异步。
用户提交后能不能看到“处理中”?库存有没有被预占?支付超时后怎么释放?失败后怎么通知?这些问题没想清楚,MQ 只会把同步失败变成异步混乱。
二、异步解耦会让主链路变轻,也会让排查变难
订单创建后,可能要发通知、加积分、同步 CRM、更新报表。
如果这些动作都放在下单接口里同步执行,接口会越来越慢,也越来越容易被某个非核心环节拖垮。
用 MQ 后,订单服务只发布事件,其他服务订阅处理。主链路变轻了,模块之间也更独立。
代价是链路变长了。
以前一个接口失败就知道失败。现在可能是消息没发出去、消费者没消费、消费失败重试、下游处理了一半、补偿任务还没跑。
所以用了 MQ,就必须配套日志、追踪、重试、死信和补偿。异步不是不用管了,而是要换一种方式管。
三、重复消费一定要当成默认情况
第一次用 MQ 时,很容易希望“一条消息只消费一次”。
工程上更稳的想法是:重复消费一定会发生。
网络抖动、消费者超时、ack 失败、服务重启、分区重平衡,都可能导致同一条消息被再次投递。可靠系统不应该依赖“消息绝不重复”,而应该让消费逻辑幂等。
比如:
- 用业务唯一号去重。
- 处理前检查业务状态。
- 用唯一索引防重复写入。
- 记录消费日志。
- 更新时带版本号或状态机约束。
扣款、发券、加积分这类操作如果没有幂等保护,重复消费一次就可能造成真实损失。
四、消息丢失和消息积压都要提前设计
消息丢失通常出现在三个地方:生产者没发成功,MQ 没持久化成功,消费者处理失败但错误 ack。
关键业务里,生产端要确认发送结果,MQ 要开启持久化和副本机制,消费端要在业务处理成功后再 ack。
更重要的业务,可以用本地消息表或 Outbox,把业务数据和待发送消息放在同一个本地事务里,再由后台任务可靠投递。
消息积压则是另一种压力反馈。
积压时不要只盲目加消费者。先看原因:是消费逻辑慢、数据库慢、下游接口慢,还是某类异常消息一直重试。
有时候加消费者会把下游打得更惨。真正要做的是找到瓶颈,再决定扩容、限流、降级、拆 Topic,还是临时跳过低优先级任务。
五、顺序要求越强,吞吐越容易被锁住
顺序消息听起来很美,但它会牺牲并发能力。
如果要求全局顺序,基本就只能串行处理,吞吐会很低。实际项目里更多是局部顺序:同一个订单、同一个用户、同一个设备的消息要按顺序处理。
这时可以按业务 ID 分区,让同一个业务对象的消息进入同一个队列或分区。
不要轻易说“所有消息都要顺序”。先问清楚:到底是哪一类业务对象需要顺序?
六、可以带走的判断清单
- MQ 适合削峰和解耦,但前提是业务允许异步。
- 异步链路要配套日志、追踪、重试和补偿。
- 消费端一定要幂等。
- 关键消息要考虑本地消息表或 Outbox。
- 消息积压时先找瓶颈,不要只加消费者。
- 顺序消息尽量做局部顺序,不要追求全局顺序。
MQ 不会让压力消失,它只是把压力换了一个地方。
本系列:
暂无评论