1、RabbitMQ 如何保证消息不丢失?
RabbitMQ 消息可靠性需要三个环节共同保障:
生产端:通过生产者确认,确认消息被 Broker 接收,配合 mandatory 回调处理路由失败,防止消息发到 Exchange 后没进队列。
存储端:消息持久化+队列持久化。并使用复制型队列,例如仲裁队列或Streams,保证 Broker 重启数据不丢。
消费端:手动 Ack,处理成功后才确认;失败消息进入死信队列;业务层做幂等,防止重复消费。
2、 什么是死信队列?如何导致的?
当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换器中,这个交换器就是死信交换器,跟它绑定的队列就称之为死信队列。
导致死信的常见原因:
消息被拒并且关闭重入队列、消息 TTL 过期。队列达到长度限制,消息被丢弃。
Quorum Queue 中消息返回次数超过阈值。
3、 RabbitMQ 有哪些交换机类型?
Direct Exchange(直连交换机):核心逻辑是路由键(Routing Key)与绑定键(Binding Key)完全一致时,消息才会被路由到对应队列。适合需要精准路由的场景。
Fanout Exchange(扇出交换机):核心逻辑是忽略路由键,将消息路由到所有与该交换机绑定的队列。适合需要广播消息的场景。
Topic Exchange(主题交换机):核心逻辑是通过通配符匹配路由键与绑定键,支持按"主题"批量路由消息。适合需要分类批量路由的场景。
Headers Exchange(头部交换机):核心逻辑是生产者发送消息时设置消息头,交换机根据绑定队列时的头部规则路由消息。实际使用较少。
4、 什么是延迟队列?怎么实现?
延迟队列保存的是延迟消息:消息已经发送到 RabbitMQ,但业务希望它过一段时间后再被消费者拿到。
RabbitMQ 本身没有延迟队列,要实现延迟消息,一般有两种方式:
使用 TTL + 死信交换器。消息先进入带 TTL 的队列,过期后被投递到死信交换器,再进入消费队列。
使用插件提供的交换器,可以按消息设置延迟时间。可以实现秒、分钟、小时级延迟,最多一两天;
扩展:
什么是队列头部阻塞?
当你为每条消息设置不同的 TTL,并将它们发送到同一个队列时。RabbitMQ 的过期消息判定机制是顺序的,它只会检查队列头部的消息是否过期。队列头部消息的过期时间,决定了整个队列的"阻塞"状态。例如A在队头,10秒过期,B在队中,1秒过期,那么它会一直等A过期,感知不到B过期了。
TTL+死信交换机有什么缺点?
缺点是如果为每个消息设置不同TTL,它容易受到队列头部阻塞影响;而如果每种延迟时间都建一组队列,维护成本较高。
如果要做天、周、月级调度,或者要堆积十万、百万级延迟消息如何处理?
应使用外部存储和调度系统。
5、 RabbitMQ 的高可用怎么保证?
分两层,集群和队列复制。
集群只负责管理元数据和连接,队列里的消息是否复制要看队列类型。
RabbitMQ 4.x 中:
Classic Queue:不复制消息,单节点存储。
镜像队列:已在 4.0 移除,是旧版本方案,基于主从复制,主节点宕机后需要选举,过程中可能丢消息。
仲裁队列:基于 Raft 协议,消息复制到多个节点,写入需要多数节点确认,适合订单、支付等不能丢消息的场景。
Streams:也是复制型,更适合日志回放和大量堆积场景。
6、怎么保证消息不乱序?
原因:生产者把顺序相关的消息发到同一个队列,但消费者是多线程并发消费,处理速度不同导致顺序错乱。
解决方案:
强制串行:只启动一个消费者单线程消费,消息按入队顺序逐个处理。吞吐量低。
分区顺序:使用它内置插件,根据请求的特征(如用户ID)计算哈希值,让相同特征消息进入同一队列。队列绑定的每个消费者内部存ID做分组排队,让相同ID的消息串行处理,不同ID的并行。
业务层排序:每条消息带一个序号,消费者收到后先检查序号,如果序号等于当前序号则处理,小于则丢弃,大于则等待,直到按顺序补齐。
7、怎么保证消息不重复?
原因:消费者因为处理超时或者网络中断等导致没有发ACK确认,导致RabbitMQ认为消费失败,重新投递消息。
解决方案:
数据库唯一约束:比如订单消息,用消息id作为唯一键插入消息表,处理前检查消息id是否存在。
业务状态机,比如订单状态从1→2,更新时加条件:update...set status=2 where status=1。
Redis分布式锁:消费前用SETNX加锁,加锁成功才设置redis标记位并处理业务,过期时间设长一些。重复消息来时如果标记存在就不处理。