All writing Notes & observations

About RabbitMQ Interview

RabbitMQ interview common questions and answers

本文档覆盖 RabbitMQ 从基础到高级的所有面试高频考点,包含详细原理、实战方案和大厂面试标准答案,适用于校招、社招、中高级开发面试准备。


一、基础概念与架构篇

1. 为什么要使用消息队列?它解决了什么问题?

核心三大作用:

  1. 应用解耦

    • 传统微服务间同步调用存在强耦合,任何一个服务故障都会导致整个链路失败。

    • 通过 MQ 实现异步通信,服务间仅通过消息交互,无需感知对方状态,某服务临时故障时消息可暂存,恢复后继续处理。

    • 典型场景:电商下单后,订单服务无需同步调用库存、物流、支付服务,仅需发送消息即可,各服务独立消费处理。

  2. 异步处理

    • 对于非核心的耗时操作,通过 MQ 异步处理,主流程无需等待,大幅提升接口响应速度。

    • 典型场景:用户注册后,发送短信、邮件、初始化积分等操作无需同步等待,通过 MQ 异步执行,注册接口响应时间从 500ms 降至 50ms。

  3. 流量削峰

    • 在秒杀、大促等瞬时高流量场景,MQ 作为缓冲区,将瞬时高峰请求分散到一段时间内处理,避免后端服务被打垮。

    • 典型场景:秒杀活动瞬时 2 万 QPS(Query Per Second),后端服务仅能处理 1 万 QPS,通过 MQ 缓冲,将请求均匀分散到后续几秒处理,用户仅需延迟几秒收到结果,体验远好于被拒绝。

2. RabbitMQ 是什么?基于什么协议?

RabbitMQ 是一个开源的消息代理(Message Broker),基于 AMQP(Advanced Message Queuing Protocol,高级消息队列协议) 实现。

  • AMQP 是一个开放标准的应用层协议,定义了消息的流转、路由、可靠性等标准,支持复杂的路由规则和消息确认机制。

  • 与其他 MQ 不同,RabbitMQ 严格遵循 AMQP 协议,提供了更丰富的企业级特性。

3. RabbitMQ 的核心组件有哪些?各自的作用是什么?

组件 作用
Producer(生产者) 创建并发送消息的客户端,负责将业务消息发送到 Exchange。
Consumer(消费者) 订阅队列并处理消息的客户端,消费完成后发送确认信号。
Exchange(交换器) 接收生产者的消息,根据路由规则将消息分发到绑定的队列,是消息路由的核心。
Queue(队列) 存储消息的缓冲区,消息最终会到达队列等待消费,遵循 FIFO 原则。
Binding(绑定) 定义 Exchange 和 Queue 之间的关联关系,以及路由规则(Binding Key)。
Connection(连接) 客户端与 Broker 之间的 TCP 长连接,负责网络通信。
Channel(信道) Connection 内部的虚拟通道,所有 AMQP 命令都通过 Channel 执行,复用 TCP 连接,减少连接开销。
vHost(虚拟主机) 逻辑隔离单元,每个 vHost 拥有独立的 Exchange、Queue、权限,实现多租户隔离。

4. 什么是 Channel?为什么需要 Channel?

  • Channel 是 Connection 内部的虚拟连接,可以理解为 “轻量级的 TCP 连接”。

  • 为什么需要 Channel?

    1. 减少 TCP 连接开销:TCP 连接的创建和销毁开销很大,如果每个线程都创建一个 TCP 连接,高并发下系统会被大量连接耗尽资源。

    2. 复用连接:一个 TCP 连接可以创建多个 Channel,多个线程可以共享同一个 Connection,每个线程使用独立的 Channel,既保证了线程隔离,又减少了连接数。

    3. 高效通信:Channel 之间完全隔离,一个 Channel 的故障不会影响其他 Channel,而且 AMQP 协议通过 Channel ID 来区分不同的信道,效率极高。

5. 什么是 vHost?它的作用是什么?

vHost 是 RabbitMQ 中的逻辑隔离单元,相当于 Broker(也就是 RabbitMQ 服务器) 中的 “小集群”。

  • 作用:

    1. 多租户隔离:不同的业务、团队可以使用不同的 vHost,互相之间完全隔离,Exchange、Queue 的名称可以重复,不会冲突。

    2. 权限控制:可以在 vHost 级别配置用户权限,限制用户只能访问特定的 vHost,保证数据安全。

    3. 资源隔离:不同 vHost 的资源使用互相独立,避免某个业务的消息积压影响其他业务。

6. RabbitMQ 的 Exchange 有哪些类型?各自的路由规则是什么?

这是面试必考题,四种 Exchange 的区别一定要讲清楚:

  1. Direct Exchange(直连交换机)

    • 路由规则:精确匹配 Routing Key 和 Binding Key。

    • 消息的 Routing Key 和队列的 Binding Key 完全一致时,消息才会被路由到该队列。

    • 适用场景:点对点的消息发送,或者根据消息类型进行精确路由。

  2. Topic Exchange(主题交换机)

    • 路由规则:通配符模糊匹配,支持*和#通配符。

      • *:匹配一个单词(以.分隔的词)

      • #:匹配零个或多个单词

    • 例如:Binding Key 为order.#,可以匹配order.create、order.pay.success等 Routing Key。

    • 适用场景:发布订阅模式,根据主题进行灵活的消息路由,比如日志的不同级别、不同模块的消息。

  3. Fanout Exchange(扇出交换机)

    • 路由规则:广播模式,忽略 Routing Key,将消息广播到所有绑定的队列。

    • 只要队列绑定了这个交换机,不管 Binding Key 是什么,都会收到消息。

    • 适用场景:广播通知,比如集群内的配置更新、事件通知,所有订阅的服务都要收到消息。

  4. Headers Exchange(头交换机)

    • 路由规则:根据消息的 Header 属性进行匹配,忽略 Routing Key。

    • 可以配置x-match参数,all表示所有 Header 都要匹配,any表示任意一个匹配即可。

    • 适用场景:需要根据多个属性进行复杂路由的场景,使用较少,因为性能不如其他几种。

7. RabbitMQ 的消息最大能有多大?队列的长度有限制吗?

  • 消息最大大小:默认是 128MB,可以通过配置修改。生产环境建议不要发送过大的消息,超过 10MB 的消息建议先上传到存储,然后发送存储地址。

  • 队列长度:理论上没有限制,取决于服务器的内存和磁盘。但是消息过多会导致消息积压,影响性能,生产环境需要监控队列长度,设置告警。


二、工作机制与流转篇

1. RabbitMQ 中消息的完整流转过程是怎样的?

  1. 生产者建立 TCP 连接,创建 Channel。

  2. 生产者声明 Exchange、Queue,并通过 Binding Key 将两者绑定。

  3. 生产者发送消息到 Exchange,消息包含 Routing Key。

  4. Exchange 根据自身类型和 Binding 规则,将消息路由到对应的 Queue。

  5. 如果消息无法路由到任何队列,且配置了mandatory=true,消息会返回给生产者;否则直接丢弃。

  6. 消费者监听 Queue,当有消息到达时,消费者获取消息,处理业务逻辑。

  7. 消费者处理完成后,发送 ACK 确认给 Broker,Broker 收到 ACK 后,将消息从队列中删除。

  8. 如果消费者处理失败,发送 NACK,Broker 会根据配置将消息重新入队,或者转发到死信队列。

2. RabbitMQ 有哪些工作模式?

这是基础面试题,对应不同的使用场景:

  1. Simple(简单模式):单生产者、单消费者,消息直接发送到队列,适用于简单的任务处理。

  2. Work Queues(工作队列):单生产者、多消费者,多个消费者竞争消费同一个队列的消息,实现任务的负载均衡,适用于耗时任务的处理。

  3. Publish/Subscribe(发布订阅):使用 Fanout 交换机,生产者发送消息到交换机,所有绑定的队列都能收到消息,实现广播。

  4. Routing(路由模式):使用 Direct 交换机,根据 Routing Key 精确匹配,实现消息的选择性接收。

  5. Topic(主题模式):使用 Topic 交换机,通配符匹配,实现更灵活的主题路由。

3. 什么是 ACK 机制?自动 ACK 和手动 ACK 的区别是什么?

ACK(Acknowledgement)是消费者向 Broker 确认消息已经处理完成的机制。

  • 自动 ACK(autoAck=true):

    • Broker 一旦把消息发送给消费者,就会立即把消息从队列中删除,不管消费者有没有处理成功。

    • 优点:性能高,简单。

    • 缺点:如果消费者处理到一半宕机了,消息就会丢失,因为 Broker 已经把消息删了。

    • 适用场景:不重要的消息,比如日志收集,丢了也没关系。

  • 手动 ACK(autoAck=false):

    • 消费者处理完消息后,需要手动调用basic.ack告诉 Broker 处理完成,Broker 才会删除消息。

    • 如果消费者宕机了,没有发送 ACK,Broker 会把消息重新分发给其他消费者。

    • 优点:保证消息不丢失,可靠性高。

    • 缺点:需要手动处理,稍微复杂一点。

    • 适用场景:核心业务,比如订单、支付,必须保证消息处理成功。

4. 什么是 NACK?什么时候会用到?

NACK(Negative Acknowledgement)是消费者告诉 Broker 消息处理失败的机制。

  • 当消费者处理消息失败时,可以调用basic.nack或者basic.reject,告诉 Broker 处理失败。

  • 可以设置requeue参数:

    • requeue=true:Broker 会把消息重新入队,分发给其他消费者重试。

    • requeue=false:Broker 会把消息丢弃,或者转发到死信队列。

  • 适用场景:处理失败的消息,比如网络异常、业务异常,需要重试或者进入死信队列处理。

5. 什么是 prefetch_count?它的作用是什么?

prefetch_count是消费者的预取数量,用来控制消费者在收到 ACK 之前,最多能从 Broker 那里拿多少个消息。

  • 作用:

    1. 实现公平调度:默认情况下,RabbitMQ 会把消息轮询分发给消费者,不管消费者的处理能力,可能导致有的消费者很忙,有的很闲。

    2. 避免消息积压在消费者端:如果消费者处理很慢,Broker 还在不停的发消息,会导致消息都堆在消费者的内存里,浪费资源。

    3. 负载均衡:通过设置prefetch_count=1,可以保证消费者处理完一个消息,Broker 才会发下一个,这样处理快的消费者就能处理更多的消息,实现公平的负载均衡。


三、消息可靠性保障篇

这是面试的重中之重,几乎所有大厂都会问这部分。

1. RabbitMQ 可能会出现消息丢失的场景有哪些?怎么解决?

消息丢失分为三个场景,每个场景都有对应的解决方案:

丢失场景 原因 解决方案
生产者到 Broker 丢失 网络异常,消息没发到 Broker;或者 Broker 收到了,但是还没处理,生产者就以为成功了。 生产者确认机制:事务机制或者 Confirm 模式。
Broker 内部丢失 Broker 宕机了,内存中的消息还没持久化到磁盘,重启后消息就没了。 消息持久化:队列、交换机、消息都要设置为持久化。
消费者到 Broker 丢失 消费者收到消息,还没处理完就宕机了,Broker 已经把消息删了。 消费者手动 ACK:处理完再发 ACK,没处理完的话 Broker 会重发。

2. 什么是生产者 Confirm 模式?它和事务机制有什么区别?

这两个都是用来保证生产者的消息能成功到达 Broker 的机制。

  • 事务机制:

    • 生产者开启事务(channel.txSelect()),发送消息,然后提交事务(channel.txCommit())。

    • 如果提交成功,说明消息已经成功到达 Broker;如果失败,事务回滚,生产者可以重发。

    • 缺点:同步阻塞,性能非常低,吞吐量会下降 10 倍左右,生产环境基本不用。

  • Confirm 模式:

    • 生产者将 Channel 设置为 Confirm 模式,所有发送的消息都会被分配一个唯一的 ID。

    • 消息成功到达 Broker 后,Broker 会返回一个 ACK 给生产者;如果失败,返回 NACK。

    • 这个过程是异步的,生产者可以一边发消息,一边等回调,性能非常高。

    • Confirm 模式有三种实现:

      1. 单独确认:发一个,等一个确认,性能低。

      2. 批量确认:发一批,等一批的确认,吞吐量高,但是出问题不知道哪个消息失败了。

      3. 异步确认:通过回调函数处理 ACK/NACK,性能最高,生产环境推荐用这个。

两者的区别:

  • 事务是同步的,Confirm 是异步的。

  • 事务性能低,Confirm 性能高。

  • 事务是强一致性的,Confirm 是最终一致性的。

  • 生产环境优先用 Confirm 模式,事务基本不用。

3. 消息持久化要怎么做?是不是设置了持久化就一定不会丢消息?

持久化需要同时配置三个地方,缺一不可:

  1. 交换机持久化:声明 Exchange 的时候,设置durable=true,这样 Broker 重启后,Exchange 还在。

  2. 队列持久化:声明 Queue 的时候,设置durable=true,Broker 重启后,队列还在。

  3. 消息持久化:发送消息的时候,设置deliveryMode=2,这样消息会被持久化到磁盘,而不是只存在内存里。

注意:持久化也不能保证 100% 不丢消息!

  • 因为 Broker 收到消息后,会先写到内存,然后异步刷到磁盘。如果在刷盘之前 Broker 宕机了,消息还是会丢。

  • 要更高的可靠性,需要配合镜像队列,把消息同步到多个节点,只要有一个节点存活,消息就不会丢。

4. 什么是 mandatory 参数?什么是备份交换机?

  • mandatory 参数:

    • 当生产者发送消息,Exchange 无法将消息路由到任何队列的时候,如果mandatory=true,Broker 会把消息返回给生产者;如果是 false,消息直接丢弃。

    • 作用:让生产者感知到消息无法路由,避免消息悄无声息的丢失。

  • 备份交换机(Alternate Exchange):

    • 可以给一个 Exchange 设置一个备份交换机,当这个 Exchange 的消息无法路由的时候,会把消息转发到备份交换机,而不是返回给生产者或者丢弃。

    • 备份交换机一般用 Fanout 类型,绑定一个队列,所有无法路由的消息都会进入这个队列,然后可以有专门的消费者处理这些消息,或者报警。

    • 作用:统一处理无法路由的消息,比 mandatory 更优雅,不用生产者处理返回的消息。

5. 什么是幂等性?为什么需要幂等性?怎么实现?

  • 幂等性:同一个操作,执行一次和执行多次的结果是一样的,不会产生副作用。

  • 为什么需要?

    • 因为 RabbitMQ 的 “至少一次” 投递机制,可能会导致消息重复消费。

    • 比如:消费者处理完消息,发 ACK 的时候网络断了,Broker 没收到 ACK,就会把消息重发给其他消费者,导致同一个消息被消费两次。

    • 如果业务是扣库存、扣款,重复消费就会导致扣两次钱,出大问题。

实现幂等性的方案:

  1. 全局唯一消息 ID:

    • 生产者给每个消息生成一个唯一的 ID,比如 UUID,或者业务 ID。

    • 消费者收到消息后,先去 Redis 里查这个 ID 有没有存在过,用setnx原子操作,如果存在,说明已经消费过了,直接跳过;如果不存在,就处理业务,然后把 ID 存到 Redis。

  2. 业务唯一键 + 数据库唯一约束:

    • 比如订单支付消息,用订单 ID 作为唯一键。

    • 消费者处理的时候,先查数据库,这个订单有没有已经支付过了,如果有,就跳过。

    • 或者在数据库里给订单 ID 加唯一约束,插入的时候如果重复就会报错,捕获报错跳过。

  3. 状态机判断:

    • 业务有状态流转,比如订单的状态:待支付、已支付、已发货、已完成。

    • 每次处理消息的时候,先判断当前状态是不是可以执行这个操作,比如已经是已支付了,就不能再执行支付操作了,直接跳过。

  4. 分布式锁:

    • 处理消息之前,先抢锁,锁的 key 是消息 ID 或者业务 ID,抢到了才处理,没抢到就说明已经有其他线程在处理了,跳过。

6. RabbitMQ 的消息传输保障有哪些?

  • At most once(最多一次):消息最多投递一次,可能会丢,不会重复。适用于不重要的消息,比如日志。

  • At least once(最少一次):消息至少投递一次,不会丢,可能会重复。这是 RabbitMQ 默认的,也是大部分业务用的,需要配合幂等性来解决重复的问题。

  • Exactly once(恰好一次):消息正好投递一次,不丢不重。RabbitMQ 本身做不到,需要业务层配合幂等性来实现。


四、高级特性与实战场景篇

1. 什么是死信队列(DLX)?什么时候消息会变成死信?

死信队列(Dead-Letter-Exchange,DLX),就是用来存放那些无法被正常消费的消息的队列,相当于 “垃圾回收站”。

消息变成死信的三种情况:

  1. 消息被拒绝了:消费者调用basic.reject或者basic.nack,并且设置了requeue=false,消息不会重新入队,就变成死信。

  2. 消息 TTL 过期了:消息设置了过期时间,到时间了还没被消费,就变成死信。

  3. 队列满了:队列达到了最大长度,新的消息进不来,最早的消息就会被挤出去,变成死信。

死信队列的作用:

  • 处理失败的消息,比如消费失败的消息,不会直接丢了,而是放到死信队列,后续可以人工处理,或者重试。

  • 实现延迟队列,这个是最常用的。比如订单超时取消,发送一个延迟消息,30 分钟后如果订单还没支付,这个消息就会过期,变成死信,消费者监听死信队列,处理超时订单。

2. RabbitMQ 怎么实现延迟队列?有哪些方式?

RabbitMQ 本身没有直接的延迟队列功能,但是可以通过两种方式实现:

方式一:TTL + 死信队列(最常用)

  1. 声明一个普通的队列,设置死信交换机和死信路由键。

  2. 发送消息的时候,给消息设置 TTL(过期时间),比如 30 分钟,就是订单 30 分钟超时。

  3. 消息发送到这个普通队列,因为设置了 TTL,30 分钟后消息过期,就会变成死信,转发到死信队列。

  4. 消费者监听死信队列,消息到达的时候,正好是 30 分钟后,就可以处理超时的订单了。

注意: 这种方式有个问题,如果队列里的消息顺序不对,比如先发送了一个 1 小时过期的消息,再发送了一个 1 分钟过期的消息,那么 1 分钟的消息会被 1 小时的消息挡住,要等 1 小时的消息过期了,它才能过期,导致延迟不准。所以如果要不同的延迟时间,最好是按延迟时间分队列。

方式二:延迟交换机插件

RabbitMQ 官方提供了rabbitmq_delayed_message_exchange插件,安装之后,就可以声明一个延迟类型的交换机。

  • 发送消息的时候,在 Header 里设置x-delay,指定延迟的毫秒数。

  • 交换机会把消息存起来,到时间了再投递到队列。

  • 这种方式没有上面的顺序问题,支持任意的延迟时间,更灵活。

应用场景:订单超时取消、任务重试、定时通知。

3. 什么是优先级队列?

优先级队列可以让优先级高的消息先被消费。

  • 创建队列的时候,设置x-max-priority参数,指定最大的优先级,比如 10。

  • 发送消息的时候,设置priority属性,比如 5,优先级越高,越先被消费。

  • 适用场景:需要优先处理重要消息的场景,比如大促的时候,VIP 用户的订单优先处理。

4. 什么是惰性队列?

惰性队列(Lazy Queue)是 RabbitMQ 3.6 之后引入的特性,默认情况下,RabbitMQ 会尽量把消息存在内存里,提高消费速度,但是如果消息积压很多,就会占用大量内存,甚至触发内存告警。

  • 惰性队列会把消息直接存到磁盘里,不管有没有积压,这样即使消息积压几百万条,也不会占用太多内存。

  • 缺点:消费的时候需要从磁盘读,性能会稍微低一点。

  • 适用场景:消息可能会大量积压的场景,比如日志收集,或者非核心的业务,对性能要求不高,但是要避免内存被占满。

5. 怎么基于 RabbitMQ 实现 RPC?

RPC 就是远程调用,客户端发送请求,等待服务端返回结果。

  • 实现步骤:

    1. 客户端声明一个临时的回调队列,用来接收响应。

    2. 客户端发送请求消息,消息里带上correlation_id(唯一 ID),还有reply_to(回调队列的名字)。

    3. 服务端监听请求队列,收到请求后处理,然后把响应发送到回调队列,带上相同的correlation_id。

    4. 客户端监听回调队列,收到响应后,根据correlation_id匹配对应的请求,把结果返回。

  • 这样就实现了同步的 RPC 调用,RabbitMQ 官方的客户端也支持这个功能。


五、集群与高可用篇

1. RabbitMQ 的集群模式有哪些?区别是什么?

RabbitMQ 的集群主要有三种模式:

1. 普通集群模式(默认)

  • 集群中的节点会同步元数据(Exchange、Queue 的定义),但是消息不会同步,消息只存在于创建它的那个节点上。

  • 比如,节点 1 创建了一个队列,消息都存在节点 1,节点 2 的消费者消费这个队列的时候,会从节点 1 拉取消息。

  • 缺点:没有高可用,如果节点 1 宕机了,这个队列的消息就丢了,因为其他节点没有备份。

  • 适用场景:只是为了分担客户端的连接压力,不需要高可用的场景,生产环境基本不用。

2. 镜像队列模式(Mirrored Queues)

  • 这是 RabbitMQ 3.8 之前的高可用方案,队列的消息会同步复制到所有的镜像节点。

  • 主节点负责写,从节点同步数据,主节点宕机了,从节点会自动切换成新的主节点,消息不会丢。

  • 缺点:同步复制的性能开销比较大,因为每个消息都要同步到所有节点,而且是基于 GM 协议,一致性不是很强,脑裂的时候可能会丢数据。

3. 仲裁队列模式(Quorum Queues)

  • 这是 RabbitMQ 3.8 之后推荐的高可用方案,基于 Raft 协议实现。

  • 比镜像队列更可靠,一致性更强,脑裂的处理更好,性能也更好。

  • 支持多数派选举,只要多数节点存活,队列就可用,数据不会丢。

  • 现在生产环境都推荐用仲裁队列,代替原来的镜像队列。

2. 集群中的磁盘节点和内存节点有什么区别?

  • 磁盘节点:把元数据(用户、vHost、队列、交换机的信息)持久化到磁盘,重启后不会丢。

  • 内存节点:元数据只存在内存里,性能更高,但是重启后元数据就丢了。

  • 注意:集群中至少要有一个磁盘节点,不然所有节点都是内存节点的话,集群重启后所有的元数据都没了,相当于清空了。

  • 内存节点一般用来做性能优化,比如对性能要求很高的节点,但是必须要有磁盘节点来存元数据。

3. 为什么集群建议用奇数个节点?

因为仲裁队列和 Raft 协议需要多数派(Quorum)来选举和确认。

  • 比如 3 个节点,最多可以容忍 1 个节点宕机,只要 2 个节点存活,就能形成多数派,集群可用。

  • 如果是 2 个节点,最多只能容忍 0 个节点宕机,因为 1 个节点宕机了,剩下的 1 个节点,无法形成多数派(需要 2 个中的 2 个),集群就不可用了,和单节点没区别。

  • 所以奇数个节点,能在相同的节点数下,容忍更多的节点故障,比如 3 个节点比 2 个节点的可用性高,5 个比 4 个高。

4. 什么是脑裂?怎么解决?

脑裂就是集群因为网络分区,分成了两个小集群,每个小集群都认为对方宕机了,自己是主集群,各自写数据,等网络恢复后,就会有两个主节点,导致数据冲突、丢失。

RabbitMQ 的解决方式:

  • 配置集群的恢复策略:

    1. ignore:默认,自动恢复,但是可能会丢数据,只适合短暂的网络抖动。

    2. pause_minority:暂停少数派的节点,少数派的节点会停止服务,等网络恢复,这样就不会出现两个主集群,保证数据一致。

    3. autoheal:自动重启少数派的节点,恢复集群。

  • 另外,仲裁队列基于 Raft 协议,本身就可以很好的处理脑裂的问题,比镜像队列更可靠。

5. 普通集群和镜像集群的消息同步机制有什么区别?

  • 普通集群:只同步元数据,消息不同步,消息只在主节点。

  • 镜像集群:消息同步到所有镜像节点,主节点写,从节点同步,同步方式有同步和异步。

  • 仲裁队列:基于 Raft,多数派同步,写操作需要多数节点确认才成功,一致性更强。


六、性能优化与问题排查篇

1. 消息积压了怎么办?

消息积压是生产环境经常遇到的问题,也是面试常问的,处理分三步:

1. 紧急处理

  • 修复消费者:先找到消费者慢的原因,比如代码有 bug、数据库慢,修复它,恢复消费速度。

  • 临时扩容消费者:如果积压太多,临时加 N 倍的消费者实例,快速消费积压的消息。

  • 临时转移消息:写一个临时的程序,把积压的队列的消息,转移到 N 个临时队列,然后部署 N 倍的消费者,并行消费,快速把积压的消息清掉。

  • 生产者限流:如果生产者还在疯狂发消息,先限流,不让新的消息进来,先把旧的处理完。

2. 事后优化

  • 优化消费者性能:比如把同步改成异步,批量处理,优化数据库操作,提升消费速度。

  • 监控预警:监控队列长度,超过阈值就报警,提前发现问题,不要等积压了几百万才发现。

  • 惰性队列:如果经常有积压,可以用惰性队列,避免内存被占满。

2. 怎么保证消息的顺序性?

RabbitMQ 本身只能保证单个队列内的消息是 FIFO 的,但是如果有多个消费者,或者重试,就会乱序。

保证顺序性的方案:

  1. 单队列单消费者:最简单的方式,一个队列只有一个消费者,这样消息就是按顺序处理的,但是性能低,吞吐量低。

  2. 业务分区:把需要保证顺序的消息,按业务 ID 哈希,分到同一个队列,比如同一个用户的消息,都发到同一个队列,同一个订单的消息都发到同一个队列,然后每个队列一个消费者,这样同一个业务的消息就是顺序的,不同的可以并行,兼顾性能和顺序。

  3. 消息序列号:生产者给消息加序列号,消费者收到后,按序列号排序,乱序的话就等前面的消息处理完再处理,但是这个比较复杂。

3. RabbitMQ 的内存告警是怎么回事?

RabbitMQ 有个内存水位线,默认是可用内存的 40%,当内存使用率超过这个阈值的时候,就会触发内存告警,然后阻塞生产者的连接,不让生产者发消息了,避免内存耗尽导致宕机。

  • 这个时候需要处理:

    1. 看是不是消息积压了,消费者没消费,导致消息都堆在内存里。

    2. 清理掉没用的消息,或者加快消费速度。

    3. 也可以调整内存水位线,但是不建议,因为内存不够还是会宕机。

4. RabbitMQ 的性能瓶颈在哪里?

  • 磁盘 IO:如果开启了持久化,消息要刷到磁盘,磁盘的写入速度就是瓶颈,尤其是机械硬盘,SSD 会好很多。

  • 网络:集群之间的同步,还有客户端的网络,高并发下网络可能会成为瓶颈。

  • 内存:如果消息积压,内存不够,就会触发告警,阻塞生产者。

  • CPU:一般 CPU 不是瓶颈,除非有很多复杂的路由,或者加密、压缩。

5. 怎么监控 RabbitMQ?

  • Management 插件:自带的 Web 管理界面,可以看队列长度、连接数、消息速率。

  • API 监控:调用 RabbitMQ 的 HTTP API,获取监控指标,比如队列长度、消息数、内存使用率。

  • Prometheus + Grafana:用 RabbitMQ Exporter 把指标导出到 Prometheus,然后用 Grafana 做可视化,设置告警,比如队列长度超过 1 万就报警,内存超过阈值就报警。

  • 关键监控指标:队列长度、消息生产 / 消费速率、连接数、信道数、内存使用率、磁盘使用率、节点状态。


七、对比选型与最佳实践篇

1. RabbitMQ、Kafka、RocketMQ 的区别?怎么选型?

特性 RabbitMQ Kafka RocketMQ
协议 AMQP 自定义 自定义
吞吐量 万级 百万级 十万级
延迟 微秒级 毫秒级 毫秒级
可靠性 高,支持事务、Confirm 高,副本机制 高,支持事务
路由 非常丰富,四种 Exchange 简单,按 Topic 丰富,支持 Tag
适用场景 企业级业务消息,解耦、异步 大数据、日志、流处理 电商、订单,高可靠

选型建议:

  • 如果是业务系统,需要复杂的路由、可靠的消息投递,用 RabbitMQ,开发简单,功能丰富。

  • 如果是大数据、日志收集、流处理,高吞吐量,用 Kafka。

  • 如果是电商、金融,需要高可靠、事务消息,用 RocketMQ。

2. RabbitMQ 的最佳实践有哪些?

  1. 连接池:不要频繁创建连接,用连接池,复用连接,减少 TCP 开销。

  2. 信道复用:每个线程用独立的 Channel,不要多个线程共用一个 Channel,因为 Channel 不是线程安全的。

  3. 消息不要太大:超过 10MB 的消息不要发,先存到 OSS,然后发地址。

  4. 设置过期时间:不重要的消息设置 TTL,避免过期的消息还占着资源。

  5. 不要用自动 ACK:核心业务都用手动 ACK,保证消息不丢。

  6. 开启 Confirm 模式:生产者开启 Confirm,保证消息到达 Broker。

  7. 配置死信队列:所有核心队列都配置死信队列,处理失败的消息。

  8. 集群部署:生产环境不要用单机,一定要用集群,仲裁队列,保证高可用。

  9. 监控告警:一定要监控队列长度,提前发现积压问题。

  10. 幂等性:所有消费逻辑都要保证幂等,避免重复消费出问题。


八、大厂实战场景题

1. 电商订单超时未支付,怎么用 RabbitMQ 实现?

这是最经典的场景,答案就是延迟队列:

  1. 用户下单后,发送一个延迟消息,延迟 30 分钟,路由到订单超时队列。

  2. 消费者监听这个队列,30 分钟后收到消息。

  3. 消费者查询订单的状态,如果还是待支付,就取消订单,恢复库存。

  4. 如果用户在 30 分钟内支付了,就把这个延迟消息取消(或者消费的时候判断状态,跳过)。

  • 实现方式可以用 TTL + 死信队列,或者延迟交换机插件。

2. 秒杀系统怎么用 RabbitMQ 削峰?

  1. 秒杀开始后,用户的下单请求,先发到 RabbitMQ,而不是直接到订单服务。

  2. 订单服务慢慢的从队列里消费消息,处理订单,按照自己的处理能力来,比如 1 万 / 秒。

  3. 这样瞬时的 2 万 QPS 的请求,就被 MQ 缓冲了,不会打垮订单服务。

  4. 用户端可以提示 “排队中”,等处理完了再通知用户。

3. 如何基于 RabbitMQ 实现分布式事务?

用最终一致性的方案,比如可靠消息最终一致性:

  1. 本地事务执行前,先把消息存到本地的消息表,状态是待发送。

  2. 本地事务执行成功,然后发送消息到 MQ。

  3. 有个定时任务,扫描本地消息表,把待发送的消息重发,保证消息一定能发到 MQ。

  4. 消费者收到消息,执行本地事务,执行成功后 ACK。

  5. 如果消费者执行失败,就重试,直到成功。

  • 这样就保证了两个本地事务要么都成功,要么都失败,实现最终一致性。

4. 消息消费失败了,怎么重试?

  1. 消费者处理失败的时候,先判断重试次数,如果没到最大次数,就发送 NACK,设置requeue=true,让消息重新入队,重试。

  2. 但是直接重发的话,会马上重试,可能还是失败,所以可以用延迟重试,每次重试的间隔越来越长,比如 1s、2s、4s、8s,指数退避。

  3. 如果重试次数到了最大次数,还失败,就把消息转到死信队列,人工处理。


建议结合实际项目经验理解,面试时可以结合自己的项目案例来回答。