消息队列藏在代码缝隙里的取舍账
上周跟一个做电商的后端工程师蹲在公司楼下抽烟,他说上周大促差点把公司搞没。原因说出来离谱——为了削峰上了消息队列,结果消费端积压了三千万条订单消息,从下午三点堆到晚上八点,客服电话被消费者打爆,财务对账单差了二十万。
消息队列消费端积压监控Grafana面板截图
那个踩坑的工程师当时的问题其实很典型。他为了赶大促的上线时间,没给消费逻辑做幂等校验,直接把扣库存的操作写在了消费回调里。一旦网络抖动导致消息重复投递,库存就会多扣一次。本来想靠队列削峰,结果把线上故障的爆炸半径从几秒的接口超时,拉成了五小时的积压事故。最开始他看着监控面板上的积压数字一直涨,还觉得是正常的流量峰值,等反应过来要扩容消费节点的时候,磁盘已经因为堆积的消息写满了,整个集群的响应速度直接掉了一半。

三种主流消息队列功能对比选型表
没有哪款消息队列是通吃的,你选它的那点优势,迟早要在别的地方连本带利还回去。比如Kafka胜在高吞吐顺序写,那它的代价就是单条消息的延迟比RabbitMQ高,不适合对实时性要求极高的事务类消息;RabbitMQ路由灵活协议全,代价就是吞吐上不去,扛不住每秒十万级的写入;RocketMQ兼顾事务和吞吐,那它的代价就是生态相对没那么开放,周边的运维工具比Kafka少一大截。对吧?
我之前见过一个团队,为了追技术热点,把原来用了两年的RabbitMQ全换成了Kafka,换完之后才发现,原来的业务场景只需要灵活的路由规则,不需要那么高的吞吐,结果为了适配Kafka的路由逻辑,前后改了三个月的代码,还因为配置不当丢了好几次用户注册的消息,得不偿失。

那些没人写在官方文档里的坑
很多人第一次搭消息队列的时候,照着教程敲完代码,跑通了发消息收消息的流程,就觉得万事大吉。真等线上跑了半年,问题全冒出来了。比如你有没有遇到过,消息发出去了,消费端就是收不到,查了半天发现是队列的权限配错了,某个主题的生产权限给了,消费权限漏开了。又比如你给消费端设了自动重试,结果遇到一条永远处理失败的脏消息,系统就每隔三秒钟重试一次,日志刷得磁盘都满了,连带着把整个节点的CPU打满。还有个更隐蔽的问题——你以为消息队列是可靠的,所有消息只要发出去就不会丢,结果集群重启的时候,刚好赶上磁盘刷盘的间隙,没写进去的消息全没了,业务方找过来的时候,你连解释的依据都找不到。
真的,踩过一次这种坑,你就再也不会把”消息队列保证不丢消息”这句话挂在嘴边了。之前有个做本地生活的团队,做优惠券的发放,用了消息队列,结果大促的时候集群刷盘延迟,丢了两万条发券的消息,用户在评论区骂了整整一周,运营那边临时补券补到凌晨三点。
还有个更常见的误区——为了用队列而用队列。有些团队明明两个服务在同一个机房,网络延迟不到1毫秒,非要跨服务调个接口,中间绕一层消息队列,把本来同步的调用变成异步,结果本来能立刻返回的结果,现在要等几秒,用户点个按钮转半天圈,体验差得要死。说白了,消息队列的核心价值从来不是”让系统看起来更高级”,而是帮你把系统之间的耦合度降下来,把你扛不住的流量峰值,挪到你能扛的时间段慢慢消化。
上周那个踩了大促坑的工程师,这周把消费端的逻辑拆了,把幂等校验加上了,给积压的消息做了单独的监控告警,把消费失败的死信队列单独拉出来做了人工处理流程。他说现在再遇到流量上来,第一反应不是把消息往队列里一扔就完事,而是先算,现在的消费速度,多久能把积压的消息消完,要不要临时扩几个消费节点,要不要暂时把非核心的消费逻辑先停掉。
没什么银弹。
技术从来不是靠堆组件解决问题的。你多接一个中间件,就要多算一笔运维的账,多担一份出故障的风险。消息队列是个好工具,但它从来不是给你兜底的安全绳。
别把消息队列当万能缓冲垫
说实话,这事没那么玄。很多团队刚接触消息队列的时候,第一反应是”我有流量洪峰我不怕,扔队列里慢慢消就行”。队列本身是个死的缓冲区,它不会帮你消化任何一条消息,只会帮你把排队的时间拉长。就像你早高峰进地铁站,安检口开得少,人在入口堆成山,你总不能说入口的栏杆帮你疏通了客流吧?栏杆只是把堵在马路上的人,挪到了地铁站厅里而已。选队列的时候别盯着吞吐量吹
麻烦在于,所有技术选型的账,从来不是只算吞吐量这一笔。我见过太多团队,明明业务场景是要发交易成功的通知短信,每天总消息量加起来不到十万条,非要上三节点的Kafka集群,光集群的日常运维成本,比整个短信业务的收入还高。也见过做实时日志采集的团队,为了图简单用RabbitMQ,结果日志量上来之后,队列的内存直接被打满,节点每隔两小时就要重启一次。
那些没人写在官方文档里的坑
