创作不易,大家点点关注🌹🌹🌹
1.什么是消息队列?
你可以把消息队列理解为一个使用队列来通信的组件。它的本质,就是个转发器,包含发消息、存消息、消费消息的过程。
这里可以以一种更加容易理解的方式解释消息队列:
想象一下,你经营着一家非常火爆的餐厅。顾客们源源不断地进来点餐(这就好比是系统中的各种请求或事件发生,产生了需要处理的 “消息”)。
以前,服务员(就像系统里直接处理请求的部分)得同时兼顾接待新顾客、记录菜单、把菜单送到厨房,还得关注每个菜品做没做好、给顾客上菜,忙得晕头转向。要是顾客太多,服务员就可能手忙脚乱,甚至把订单弄混或者忘记处理。
后来,你想出了一个办法,在餐厅入口放了一个专门记录订单的本子(这就是消息队列)。顾客进来后,服务员只需要快速把顾客点的餐写在本子上(生产者往消息队列里发送消息),然后就可以去招呼下一位顾客了。
厨房的厨师(消费者)呢,就按本子上记录的顺序,一道一道地做菜(从消息队列里按顺序取出消息并处理)。这样一来,服务员不用再操心每个订单处理得怎么样了,专心接待新顾客就行;厨师也不用被服务员催来催去,可以有条不紊地做菜。即使突然来了一大批顾客,订单都写在本子上,也不会出现混乱,大家都能各司其职。
2.消息队列的选择
Kafka、ActiveMQ、RabbitMQ、RocketMQ来进行不同维度对比。
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 10 万级 | 10 万级 |
时效性 | 毫秒级 | 微秒级 | 毫秒级 | 毫秒级 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
消息重复 | 至少一次 | 至少一次 | 至少一次 最多一次 | 至少一次最多一次 |
消息顺序性 | 有序 | 有序 | 有序 | 分区有序 |
支持主题数 | 千级 | 百万级 | 千级 | 百级,多了性能严重下滑 |
消息回溯 | 不支持 | 不支持 | 支持(按时间回溯) | 支持(按offset回溯) |
管理界面 | 普通 | 普通 | 完善 | 普通 |
3.消息队列的使用场景
-
解耦:可以在多个系统之间进行解耦,将原本通过网络之间的调用的方式改为使用MQ进行消息的异步通讯,只要该操作不是需要同步的,就可以改为使用MQ进行不同系统之间的联系,这样项目之间不会存在耦合,系统之间不会产生太大的影响,就算一个系统挂了,也只是消息挤压在MQ里面没人进行消费而已,不会对其他的系统产生影响。
-
异步:加入一个操作设计到好几个步骤,这些步骤之间不需要同步完成,比如客户去创建了一个订单,还要去客户轨迹系统添加一条轨迹、去库存系统更新库存、去客户系统修改客户的状态等等。这样如果这个系统都直接进行调用,那么将会产生大量的时间,这样对于客户是无法接收的;并且像添加客户轨迹这种操作是不需要去同步操作的,如果使用MQ将客户创建订单时,将后面的轨迹、库存、状态等信息的更新全都放到MQ里面然后去异步操作,这样就可加快系统的访问速度,提供更好的客户体验。
-
削峰:一个系统访问流量有高峰时期,也有低峰时期,比如说,中午整点有一个抢购活动等等。比如系统平时流量并不高,一秒钟只有100多个并发请求,系统处理没有任何压力,一切风平浪静,到了某个抢购活动时间,系统并发访问了剧增,比如达到了每秒5000个并发请求,而我们的系统每秒只能处理2000个请求,那么由于流量太大,我们的系统、数据库可能就会崩溃。这时如果使用MQ进行流量削峰,将用户的大量消息直接放到MQ里面,然后我们的系统去按自己的最大消费能力去消费这些消息,就可以保证系统的稳定,只是可能要跟进业务逻辑,给用户返回特定页面或者稍后通过其他方式通知其结果。
4.消息重复消费怎么解决?
只有让消费者的处理逻辑具有幂等性,保证无论同一条消息被消费多少次,结果都是一样的,从而避免因重复消费带来的副作用。
对于已经消费成功的消息,本地数据库表或Redis缓存业务标识,每次处理前先进行校验,保证幂等。
5.消息丢失怎么解决的?
使用一个消息队列,其实就分为三大块:生产者、中间件、消费者,所以要保证消息就是保证三个环节都不能丢失数据。
-
生产者的消息确认:生产者在发送消息时,需要通过消息确认机制来确保消息成功到达。
-
存储消息:broker收到消息后,需要将消息持久化到磁盘上,避免消息因内存丢失。
-
消费者的消息确认:消费者在处理完消息后,再向消息队列发送确认(ACK),如果消费者未发送确认,消息队列需要重新投递该消息。
除此之外,如果消费者持续消费失败,消息队列可以自动进行重试机制或将消息发送到死信队列(DLQ)或通过日志等其他手段记录异常的消息,避免因一时的异常导致消息丢失。
6.消息队列的核心术语
-
生产者:负责向消息队列中发送消息的应用程序或服务。生产者将消息发送到指定的队列或主题,供消费者消费。
-
消费者:从消息队列中读取和处理消息的应用程序或服务。消费者根据业务逻辑处理收到的消息,并可以向消息队列发送确认。
-
Broker:消息队列的核心组件,负责接收、存储和分发消息。
-
队列:存储消息的容器,消息按照先进先出的顺序在队列中存储。队列中的每条消息通常只能被一个消费者消费一次。
-
主题 (Topic):用于在发布/订阅模型中,消息生产者将消息发布到一个主题,多个订阅该主题的消费者可以接收到相同的消息。
-
死信队列 (Dead Letter Queue):当消息因为消费失败、多次重试后未成功处理、消息过期或队列达到最大长度等原因被丢弃时,消息可以被转移到死信队列中。
7.如何保证消息的有序性?
单一生产者和单一消费者:
-
使用单个生产者发送消息到单个队列,并由单个消费者处理消息。可以确保消息按照生产者的发送顺序消费。
-
方法简单但是容易性能瓶颈,无法利用并发的优势。
顺序队列:
-
一些消息队列(如RabbitMQ)支持顺序队列,消息在队列中的存储顺序与土地顺序一致。如果使用单个顺序队列,消息将按顺序被消费。
-
可以使用多个顺序队列来提高并发处理能力,并使用特定规则将消息分配到不同的顺序队列中。
分区:
-
Kafka 可以通过将消息划分到同一个分区(Partition)来保证消息在分区内是有序的,消费者按照分区顺序读取消息就可以保证消息顺序。
-
这也可能会限制消息的并行处理程度,需要在顺序性和吞吐量之间进行权衡。
8.如何处理消息积压问题
消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问题时,我们需要先排查,是不是有bug产生了。
如果不是bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优为批量处理消息。如果还是慢,我们可以考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力。
常见有以下几种方式提升消费者的消费能力:
-
增加消费者线程数量:提高并发消费能力。
-
增加消费实例:在分布式系统中,可以水平扩展多个消费实例,从而提高消费速率。
-
优化消费者逻辑:检查消费者代码,减少单个消息的处理时间。例如:减少I/O操作、使用批量处理等。
除此之外还可以进行限流和降级处理:
-
对消息生产端进行限流,降低生产速率,避免消息积压进一步恶化。
-
对非关键消息进行丢弃或延迟处理,只保留高优先级的消息,提高系统的响应速度。
9.消息队列设计成推消息还是拉消息?
推模式(Push):消息队列将消息主动推送给消费者,适合实时性要求高、消费者能够及时处理消息的场景。
-
优点:实时性好,消息可立即送达消费者。
-
缺点:难以控制消费速度,容易导致消费者过载,尤其是在高并发时。
拉模式(Pull):消费者主动从消息队列中拉取消息,适合消费能力有限、需要根据自身处理能力调控速率的场景。
-
优点:消费者可以根据自身负载决定拉取速率,避免过载;更适合批量处理。
-
缺点:可能会导致消息延迟,实时性不如推模式,尤其是拉取频率较低时。