本文 的 原文 地址
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的面试题:
如何根据应用场景选择合适的消息中间件?
Rocketmq消息0丢失,如何实现?
Rocketmq如何保证消息可靠?
对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
最近有小伙伴在面试美团,遇到了相关的面试题, 小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V175版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
三大MQ指标对比
分布式、微服务、高并发架构中,消息队列(Message Queue,简称MQ)扮演着至关重要的角色。
消息队列用于实现系统间的异步通信、解耦、削峰填谷等功能。
| 对比指标 | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|
| 应用场景 | 中小规模应用场景 | 分布式事务、实时日志处理 | 大规模数据处理、实时流处理 |
| 开发语言 | Erlang | Java | Scala & Java |
| 消息可靠性 | 最高 (AMQP协议保证) | 较高 (基于事务保证) | 中等 (基于副本机制保证) |
| 消息吞吐量 | 低 万级到十万级 | 中等 十万级到百万级 | 高 百万级或更高 |
| 时效性 | 毫秒级 | 毫秒级 | 毫秒级 |
| 支持的语言和平台 | Java、C++、Python等 | Java、C++、Go等 | Java、Scala、Python等 |
| 架构模型 | virtual host、broker、exchange、queue | nameserver、controller、broker | broker、topic、partition、zookeeper/Kraft |
| 社区活跃度和生态建设 | 中等 活跃的开源社区和丰富的插件生态系统 | 较高 阿里巴巴开源,稳定的社区支持 | 最高 活跃的开源社区和广泛的应用 |
| github star | 10.8k | 19.4k | 25.2k |
RocketMQ、Kafka、RabbitMQ,如何选型?
RocketMQ、Kafka、RabbitMQ,如何选型?
最为详细的方案,请参考尼恩团队的架构方案: 招行面试:RocketMQ、Kafka、RabbitMQ,如何选型?
对比分析三大MQ常见问题
下面, 对比分析三大MQ常见问题。
消息丢失问题

1、RocketMQ解决消息丢失问题:
生产端: 采用同步发送(等待Broker确认)并启用重试机制,结合事务消息(如预提交half消息+二次确认commit)确保消息可靠投递。
Broker端:配置同步刷盘(消息写入磁盘后返回确认)和多副本同步机制(主从节点数据冗余)防止宕机丢失,同时通过集群容灾保障高可用。
消费端:消费者需手动ACK确认,失败时触发自动重试(默认16次),最终失败消息转入死信队列人工处理,避免异常场景下消息丢失。
最为详细的方案,请参考尼恩团队的架构方案: 滴滴面试:Rocketmq消息0丢失,如何实现?
2、Kafka解决消息丢失问题:
生产端:设置acks=all确保消息被所有副本持久化后才响应,启用生产者重试(retries)及幂等性(enable.idempotence=true)防止网络抖动或Broker异常导致丢失
Broker端:配置多副本同步(min.insync.replicas≥2)和ISR(In-Sync Replicas)机制,仅同步成功的副本参与选举;避免unclean.leader.election.enable=true(防止数据不全的副本成为Leader)
消费端:关闭自动提交位移(enable.auto.commit=false),手动同步提交(commitSync)确保消息处理完成后再更新位移,结合消费重试及死信队列兜底
最为详细的方案,请参考尼恩团队的架构方案: 得物面试:消息0丢失,Kafka如何实现?
3、RabbitMQ解决消息丢失问题:
生产端:启用Publisher Confirm模式(异步确认消息持久化)并设置mandatory=true路由失败回退,结合备份交换机处理无法路由的消息;事务消息因性能损耗仅限关键场景使用。
Broker端:消息与队列均需持久化(durable=true)防止宕机丢失,部署镜像队列集群实现多节点冗余;同步刷盘策略确保数据落盘后响应。
消费端:关闭自动ACK,采用手动ACK并在业务处理成功后提交确认;消费失败时重试(重试次数可配置)并最终转入死信队列人工干预,避免消息因异常未处理而丢失。
消息积压问题
1、RocketMQ解决消息积压问题:
RocketMQ通过横向扩展(增加消费者实例、队列数量)、提升消费能力(线程池调优、批量消费)、动态扩容、消息预取、死信队列隔离无效消息,并支持消费限流及监控告警,快速定位处理积压问题。
RocketMQ还提供了消息拉取和推拉模式,消费者可以根据自身的处理能力主动拉取消息,避免消息积压过多。
最为详细的方案,请参考尼恩团队的架构方案: 阿里面试:如何保证RocketMQ消息有序?如何解决RocketMQ消息积压?
2、Kafka解决消息积压问题
Kafka通过 横向扩展(增加分区及消费者实例)、优化消费者参数(如批量拉取、并发处理)、提升消费逻辑效率(异步化、减少I/O),并动态监控消费滞后指标。
必要时限流生产者或临时扩容消费组,结合分区再平衡策略快速分发积压消息负载。
Kafka还提供了消息清理(compaction)和数据保留策略,可以根据时间或者数据大小来自动删除过期的消息,避免消息积压过多。
3、RabbitMQ解决消息积压问题
RabbitMQ通过调整消费者的消费速率来控制消息积压。
可以使用QoS(Quality of Service)机制设置每个消费者的预取计数,限制每次从队列中获取的消息数量,以控制消费者的处理速度。
RabbitMQ还支持消费者端的流量控制,通过设置basic.qos或basic.consume命令的参数来控制消费者的处理速度,避免消息过多导致积压。
消息重复消费问题
1、RocketMQ解决消息重复消费问题
-
使用消息唯一标识符(Message ID):在消息发送时,为每条消息附加一个唯一标识符。消费者在处理消息时,可以通过判断消息唯一标识符来避免重复消费。可以将消息ID记录在数据库或缓存中,用于去重检查。
-
消费者端去重处理:消费者在消费消息时,可以通过维护一个已消费消息的列表或缓存,来避免重复消费已经处理过的消息。
2、Kafka解决消息重复消费问题
- 幂等性处理:在消费者端实现幂等性逻辑,即多次消费同一条消息所产生的结果与单次消费的结果一致。这可以通过在业务逻辑中引入唯一标识符或记录已处理消息的状态来实现。
- 消息确认机制:消费者在处理完消息后,提交已消费的偏移量(Offset)给Kafka,Kafka会记录已提交的偏移量,以便在消费者重新启动时从正确的位置继续消费。消费者可以定期提交偏移量,确保消息只被消费一次。
3、RabbitMQ解决消息重复消费问题
-
幂等性处理:在消费者端实现幂等性逻辑,即无论消息被消费多少次,最终的结果应该保持一致。这可以通过在消费端进行唯一标识的检查或者记录已经处理过的消息来实现。
-
消息确认机制:消费者在处理完消息后,发送确认消息(ACK)给RabbitMQ,告知消息已经成功处理。RabbitMQ根据接收到的确认消息来判断是否需要重新投递消息给其他消费者。
最为详细的方案,请参考尼恩团队的架构方案: 最系统的幂等性方案:一锁二判三更新
消息有序性
1、Rabbitmq 解决有序性问题
模式一:单队列单消费者模式
-
将需要保证顺序的消息全部发送到同一个队列,且消费者设置为单线程处理。
-
原理:RabbitMQ 队列天然支持 FIFO 顺序存储,单消费者避免并发处理导致乱序。
示例代:
// 生产者发送到同一队列
rabbitTemplate.convertAndSend("order.queue", "message1");
rabbitTemplate.convertAndSend("order.queue", "message2");
// 消费者单线程监听
@RabbitListener(queues = "order.queue")
public void processOrder(String message) {
// 顺序处理逻辑
}
缺点:无法横向扩展消费者,吞吐量受限。
模式二:消息分组策略
按业务标识分区(如订单 ID、用户 ID),相同分组的消息路由到同一队列,每个队列对应一个消费者。
实现方式: 生产者通过哈希算法或自定义路由键将关联的消息分配到特定队列。
- 生产者根据业务标识生成路由键,如
routingKey = orderId.hashCode() % queueCount。 - 声明多个队列,绑定到同一交换机,并根据路由键规则分发消息。
代码示例:
// 生产者发送消息时指定路由键
String orderId = "ORDER_1001";
String routingKey = "order." + (orderId.hashCode() % 3); // 分配到3个队列之一
rabbitTemplate.convertAndSend("order.exchange", routingKey, message);
优势:在保证同分组顺序性的同时,允许不同分组并行处理。
消费者并发控制 设置
prefetchCount=1
确保每次只处理一个消息,关闭自动应答,手动确认后再获取新消息:
spring:
rabbitmq:
listener:
simple:
prefetch: 1
效果:防止消费者同时处理多个消息导致乱序。
2、RocketMQ解决有序性问题
RocketMQ实现顺序消息的核心是通过生产端和消费端双重保障:
-
全局顺序需单队列(性能受限),分区顺序通过Sharding Key哈希分散到不同队列,兼顾吞吐量与局部有序性。需避免异步消费、消息重试乱序,失败时跳过当前消息防止阻塞
-
生产者使用MessageQueueSelector将同一业务标识(如订单ID)的消息强制路由至同一队列,利用队列FIFO特性保序;
-
消费端对 同一队列启用 单线程拉取 + 分区锁机制(ConsumeOrderlyContext),确保串行处理。
最为详细的方案,请参考尼恩团队的架构方案:
3、Kafka解决有序性问题
Kafka实现顺序消息的核心在于分区顺序性:
- 生产端:相同业务标识(如订单ID)的消息通过固定Key哈希至同一分区(
Partitioner),利用分区内消息天然有序性保序; - 消费端:每个分区仅由同一消费者组的一个线程消费(单线程串行处理),避免并发消费乱序;
事务消息
1、RabbitMQ的事务消息
-
RabbitMQ支持事务消息的发送和确认。在发送消息之前,可以通过调用"channel.txSelect()"来开启事务,然后将要发送的消息发布到交换机中。如果事务成功提交,消息将被发送到队列,否则事务会回滚,消息不会被发送。
-
在消费端,可以通过"channel.txSelect()"开启事务,然后使用"basicAck"手动确认消息的处理结果。如果事务成功提交,消费端会发送ACK确认消息的处理;否则,事务回滚,消息将被重新投递。
public class RabbitMQTransactionDemo {
private static final String QUEUE_NAME = "transaction_queue";
public static void main(String[] args) {
try {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
// 创建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
try {
// 开启事务
channel.txSelect();
// 发送消息
String message = "Hello, RabbitMQ!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
// 提交事务
channel.

最低0.47元/天 解锁文章
997

被折叠的 条评论
为什么被折叠?



