一次讲清消息队列:从削峰填谷到可靠投递
下单、支付、发短信、扣库存,一次请求背后常常串着好几件任务。把这些任务一个接一个串行做完,用户要等很久;任何一个环节变慢,整条链路都会被拖慢。消息队列就是为了解开这种纠缠而出现的:它让生产消息的一方和处理消息的一方不再直接等待彼此,中间多了一个可靠的缓冲层。理解了这个缓冲层,就理解了消息队列的大半价值。
一、消息队列到底解决什么问题
第一个问题是解耦。调用方只需要把消息发出去,不用知道谁来处理、什么时候处理。新增一个处理方时,调用方一行代码都不用改,只要让新的处理方去订阅同一类消息。
第二个问题是异步。用户下单之后,短信通知、积分发放、数据统计这些事都可以慢慢做。主流程只做最关键的几件事:校验、落单、返回结果,其余工作交给队列慢慢消化,接口响应自然变快。
第三个问题是削峰填谷。流量高峰来临时,队列先把请求接下来,攒在那里,再按处理方的能力有节奏地放出去;流量低谷时,攒下的任务继续消化,资源不会闲置。
这三件事合起来,就是消息队列最常见的价值:让系统在面对波动时既稳得住,也快得起来。
二、核心模型:生产者、队列与消费者
消息队列的核心角色只有三个:生产者负责发送消息;队列负责暂存消息;消费者负责取走消息并处理。生产者发完消息就可以继续做自己的事,不用等待消费者。
在此基础上,常见的投递方式有两种。一种是点对点:一条消息只会被一个消费者取走,适合任务分发。另一种是发布订阅:一类消息可以有多个订阅者,每个订阅者都会收到一份,适合事件通知。
还要理解主题这个概念。主题相当于消息的分类标签,消费者按主题订阅自己关心的消息。生产者只管往主题里发,至于是谁在消费,生产者完全不用操心。这种不知情,恰恰是解耦的关键。
三、削峰填谷是怎么实现的
削峰填谷靠的是队列的缓冲能力。假设平时每秒进来一百个请求,促销时突然涨到每秒一千个。如果直接打到处理方,数据库和服务很可能被压垮。有了队列,突发流量先进入队列排队,处理方按每秒两百个的速度匀速消费,峰值被摊平了。
这里有两个要点。第一,队列的容量要够大,能够装下高峰期积压的消息,否则消息无处可放,系统照样会崩。第二,消费速度要可控,可以通过增加消费者数量来提高消费速度,也可以在流量回落后慢慢把积压消化掉。削峰不是让流量消失,而是把同一时间的压力分摊到一段时间里。
还要注意一点:排队本身会带来延迟。不是所有场景都适合排队,比如要求立刻返回结果的强交互请求,就不适合丢进队列里慢慢处理。
四、可靠投递的四道防线
消息最怕的就是丢。想让消息可靠投递,通常要守住四道防线。
第一道在生产端。生产者发完消息之后,要等到队列确认收到才算成功。如果没有收到确认,就要重试或者告警。只发不管,是丢消息最常见的原因。
第二道是持久化。消息写进队列之后,要落到磁盘上保存下来,而不是只留在内存里。这样即使队列服务重启,消息也不会丢。
第三道在消费端。消费者拿到消息之后,要先处理、处理成功后再确认。确认之前消息一直保留在队列里,万一消费者中途崩溃,消息还能被重新取出来交给别人处理。反过来,如果先确认再处理,处理失败消息就丢了。
第四道是重试与死信。处理失败的消息不会立刻消失,而是按策略重试几次。重试几次仍然失败的消息,会被送进死信队列单独存放,等人工介入处理,而不是悄悄丢弃。
这四道防线环环相扣:生产确认保证消息进了队列,持久化保证消息留得住,消费确认保证消息被处理,重试与死信保证异常有处可去。缺少任何一道,可靠性都会打折。
五、顺序性与重复消费
用了消息队列之后,两个问题几乎一定会遇到:顺序和重复。
顺序问题来自于并行消费。同一类消息被多个消费者并行处理时,先发的消息可能后处理完。如果业务要求严格有序,就要把同一类消息固定分到同一个分区或者同一个消费者,代价是牺牲一部分并发能力。是否需要严格有序,要看业务本身:订单状态流转需要有序,日志采集通常不需要。
重复消费来自于确认机制。消费者处理成功但确认消息丢失时,队列会以为消息没处理完,于是重新投递一次,消费者就会收到重复消息。解决办法是让消费逻辑做到幂等:同样一条消息处理一次和处理多次,结果是一样的。常见做法是给每条消息一个唯一编号,处理前先查编号是否已经处理过,处理过就直接跳过。
六、什么时候不该用消息队列
消息队列不是万能的。它引入了新的组件,就意味着更多的运维成本、更多的故障点和更难排查的问题。
如果调用方必须立刻拿到处理结果,就不要用消息队列,直接调用更简单。如果系统规模很小,流量也很平稳,消息队列带来的复杂度可能超过收益。如果消息量极小、一年也产生不了几条消息,用定时任务或者简单的重试机制就够了。
选型之前先想清楚三个问题:消息丢了能不能接受?消息乱序了会不会出事?消息重复消费了会不会出错?这三个问题想清楚了,队列的配置方式、多大容量、要不要重试,答案自然就出来了。
七、小结:一个可落地的检查清单
回顾全文,消息队列的价值可以用两句话概括:用缓冲扛住流量波动,用确认与重试保障消息不丢。落地时可以对照这份清单检查:生产者是否等待队列确认,失败是否重试;队列是否开启持久化,容量是否留出余量;消费者是否先处理后确认,处理失败是否进入重试;重复投递时消费逻辑是否幂等;需要顺序的场景是否做了分区绑定;死信队列是否有人定期查看。
把这几件事做到位,消息队列就能从一个时髦的组件,变成系统里真正可靠的底座。
- 点赞
- 收藏
- 关注作者
评论(0)