这里是有关消息队列的一些八股
1.MQ的作用
- 异步处理:系统可以将那些耗时的任务放在消息队列中异步处理,从而快速响应用户的请求。比如说,用户下单后,系统可以先返回一个下单成功的消息,然后将订单信息放入消息队列中,后台系统再去处理订单信息。
- 削峰/限流:用于应对系统高并发请求的瞬时流量高峰,通过消息队列,可以将瞬时的高峰流量转化为持续的低流量,从而保护系统不会因为瞬时的高流量而崩溃。先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。
- 降低耦合:生产者将消息放入队列,消费者从队列中取出消息,这样一来,生产者和消费者之间就不需要直接通信,生产者只管生产消息,消费者只管消费消息,这样就实现了解耦。
2.几种MQ的对比(技术选型)
RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|
公司 | Rabbit | Apache | 阿里 |
架构 | 传统的消息队列架构 | 基于主题(Topic)的发布-订阅模型 | 采用分布式架构 |
消息可靠性 | 高 | 高 | 一般 |
吞吐量 | 高 | 超高 | 超高 |
- RabbitMQ:强调消息的可靠传递和灵活路由,适合中小规模的企业应用和微服务架构。
- RocketMQ:专注于高性能、高可靠性和大规模分布式系统支持,适合复杂业务场景。
- Kafka:专为高吞吐量的流数据处理设计,适合实时数据管道和大数据分析。
3.RabbitMQ的工作模式
简单模式:一对一模式,一个生产者、一个消费者,一个队列,生产者发送消息,消费者消费消息
工作队列模式:一对多模式,一个生产者,多个消费者,一个队列,每个消费者从队列中获取唯一的消息。适用于资源密集型任务, 单个消费者处理不过来,需要多个消费者进行处理的场景。
发布订阅模式:
多了一个 Exchange 角色,而且过程略有变化:
生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
消费者,消息的接收者,会一直等待消息到来
消息队列,接收消息、缓存消息
交换机一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange与消息队列的绑定模式:
- Fanout(广播模式):将消息交给所有绑定到交换机的队列,当发送一条消息到fanout交换器上时,它会把消息投放到所有附加在此交换器上的队列
- Direct(路由模式):消息发送者通过指定不同的路由键将消息发送到交换机,交换机根据路由键将消息发送到对应的队列。
- Topic(主题模式):消息发送者通过指定主题(可以使用通配符)将消息发送到交换机,交换机根据主题将消息发送到对应的队列。
4.AMQP协议模型
Broker:代表着一个中间件应用,负责接收消息生产者的消息,然后将消息发送至消息接受者或者其他的broker。一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者 RabbitMQ 服务实例。
Channel:代表着producer、consumer和broker之间的逻辑连接,一个Connection可以包含多个Channel。Channel使得基于同一连接的不同进程之间与broker之间的交互相互隔离,不干扰。而不需要重新建立连接,channel在发生协议错误的时候会被关闭。
Exchange:这是所有被发送的消息首先到达的目的地,Exchange负责根据路由规则将消息路由到不同的目的地。路由规则包括下面几种:direct(point-to-point)、topic(publish-subscribe)和fanout(multicast)。
Queue:这是消息到达的最终目的地,到达queue的消息是已经准备好被消费的消息,一个消息可以被exchange copy发送至多个queue。
Binding:这是queue和exchange之间的虚拟连接,使得消息从哪个exchange路由到Queue。routing key可以通过binding和exchange routing规则关联。
5.延迟队列?rabbitmq怎么实现延迟队列?
延迟队列指的是存储对应的延迟消息,消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
RabbitMQ 本身是没有延迟队列的,要实现延迟消息,一般有两种方式:
- 通过 RabbitMQ 本身队列的特性来实现,需要使用 RabbitMQ 的死信交换机(Exchange)和消息的存活时间 TTL(Time To Live)。生产者发送带TTL的消息到一个队列,消息过期后路由到死信队列。然后消费者订阅死信队列
- 在 RabbitMQ 3.5.7 及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时,插件依赖 Erlang/OPT 18.0 及以上。
也就是说,AMQP 协议以及 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过 TTL 和 DLX 模拟出延迟队列的功能。
6.死信队列
消息在一个队列中变成死信 (dead message
) 之后,它能被重新发送到另一个交换器中,这个交换器就是 DLX(Dead-Letter-Exchange
),绑定 DLX 的队列就称之为死信队列。死信队列是用来处理无法被正常消费或处理的消息的特殊队列。
导致的死信的几种原因:
1.消息被拒绝(Rejected):当消费者拒绝消费消息或者消息超过消费者的最大重试次数时,消息会被发送到死信队列。
2.消息过期(Expired):如果消息在一定时间内没有被消费者处理,即超过了消息的过期时间,该消息也会被发送到死信队列。
3.队列达到最大长度(Queue Length Limit):当队列达到了定义的最大长度限制,新的消息无法进入队列,会将旧的消息发送到死信队列。
7.如何保证消息的可靠性(防止数据丢失)?
丢失的情况:消息到 MQ 的过程中搞丢,MQ 自己搞丢,MQ 到消费过程中搞丢。
生产者到 RabbitMQ:事务机制和 Confirm 机制,推荐:可以通过生产者启用Confirm 机制,在消息成功发送给RabbitMQ后接收确认回执,确保消息已被正确接收。
注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。
RabbitMQ 自身:持久化、集群、普通模式、镜像模式。
持久化:
- Exchange 设置为持久化 。
- Queue 设置为持久化 。
- 消息在发送时设置为持久化模式,即
deliveryMode=2
RabbitMQ 到消费者:手动消息确认:消费者在处理完消息后,需要显式地发送一个 ACK 确认信号给 RabbitMQ,RabbitMQ 才会从内存(和磁盘,如果是持久化消息的话)中移除消息。这样可以确保消息在处理过程中不会因为消费者进程挂掉而丢失
8.如何避免消息堆积?
- 优化消费者代码,提高消费能力。减少消费时间
- 消息TTL:可以为消息设置生存时间(TTL),即消息在队列中停留的最大时间。超过TTL的消息将被自动丢弃或死信
- 队列长度限制:可以通过策略或队列参数设置队列的最大长度。当队列达到最大长度时,RabbitMQ会根据策略丢弃或死信队列的前端消息
- 增加消费者数量(Horizontal Scaling):当消费者处理速度跟不上生产者发送消息的速度时,可以通过增加更多的消费者实例来并行处理消息,从而提升总体处理能力
- 使用死信队列(Dead Letter Queue, DLQ):对于无法立即处理或处理失败的消息,可以配置死信交换器和队列,当消息达到一定重试次数或者超过一定期限未被成功ACK时,消息将被转发到死信队列中,后续可以单独处理这部分消息,避免阻塞正常的消息流
9.如何防止消息重复消费(保证消费幂等性)?
RabbitMQ消息重复消费问题通常是由以下原因导致的:
1.生产者用于发送消息失败后的重试,导致又发送了一次重复的消息
2.消费者应用程序在处理消息时发生了错误,导致消息确认(ack)没有发送给RabbitMQ,从而导致RabbitMQ将消息重新分发给其他消费者进行消费。
3.网络问题或消费者应用程序重启时,RabbitMQ无法收到消息确认,也会导致消息重新分发。
为了解决消息重复消费问题(保证消费幂等性),可以采取以下措施:
- 使用消息唯一ID(幂等性):确保消息的处理是幂等的,即无论同一条消息被消费多少次,结果都是相同的。每个消息用一个唯一标识来区分,消费前先判断标识有没有被消费过,若已消费过,则直接ACK
- 消息确认:消费者应及时地发送消息确认(ack)给RabbitMQ,表示已经成功处理了消息。这样RabbitMQ就不会将消息重新分发给其他消费者。
- 加锁:消费者在处理消息之前,用Redis对当前消息设置分布式锁,key为消息id,如果设置失败,就拒绝处理
10.如何保证消息的顺序性?
RabbitMQ的queue本身就是队列,是可以保证消息的顺序投递的。
但是消息的顺序消费则是另一回事了,所谓的“顺序消费”意味着是否顺序达到目的地,比如:数据库。
看看以下场景:
一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
产生多个consumer去消费一个queue,极有可能是因为:消息消费太慢,所以盲目让多个consumer同时来消费,而忽略了消息消费顺序性。
在某些情况下,消息是需要保证顺序性的,如果上图中的data1, data2, data3 分别意味着对某条数据的增改删,但是如果乱序以后就变成了:删改增。
将原来的一个queue拆分成多个queue,每个queue都有一个自己的consumer。这种方案的核心是生产者在投递消息的时候根据业务数据关键值将需要保证顺序的同一类数据发送到一个queue中。
多个queue保证了消息消费的效率,每个queue对应单个消费者保证了消息消费的有序性。