RabbitMQ多源 多队列 动态队列
pom.xml
<!--RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
application.properties
################################ rabbitMQ ################################# spring.rabbitmq.first.host=127.0.0.1 spring.rabbitmq.first.port=5672 spring.rabbitmq.first.username=guest spring.rabbitmq.first.password=guest #spring.rabbitmq.second.host=17.1.26.140 #spring.rabbitmq.second.port=5672 #spring.rabbitmq.second.username=admin #spring.rabbitmq.second.password=123456 #rabbitMQ为自带了消息重试机制:当消费者消费消息失败时,可以选择将消息重新“推送”给消费者,直至消息消费成功为止。 开启消费者手动应答机制 #spring.rabbitmq.listener.simple.acknowledge-mode=manual mq.handle=gtkjjcxxpt_ybdb mq.queues=gtkjjcxxpt_ybdb,hello1,hello2
RabbitConfig
package diit.microservice.handle.config; import diit.microservice.handle.service.strategy.manager.HandleBaseFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.MessageListener; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration public class RabbitConfig { @Bean(name = "firstConnectionFactory") @Primary //默认使用 public ConnectionFactory firstConnectionFactory( @Value("${spring.rabbitmq.first.host}") String host, @Value("${spring.rabbitmq.first.port}") int port, @Value("${spring.rabbitmq.first.username}") String username, @Value("${spring.rabbitmq.first.password}") String password ) { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); connectionFactory.setHost(host); connectionFactory.setPort(port); connectionFactory.setUsername(username); connectionFactory.setPassword(password); return connectionFactory; } @Autowired private HandleBaseFactory handleBaseFactory; @Value("#{'${mq.queues}'.split(',')}") private String[] queueNames; private Logger logger = LoggerFactory.getLogger(getClass()); //通过SimpleMessageListenerContainer动态增加队列,可以根据配置或者运行时需求增加多个队列 //message -> {中为接收队列消息后对消息的处理部分 @Bean public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setQueueNames(queueNames); container.setMessageListener((MessageListener) message -> { System.out.println(message.getMessageProperties()); String stringData = new String(message.getBody()); logger.info("message:"+stringData); handleBaseFactory.saveOrUpdate(stringData); }); return container; } //第二个mq的数据源 // @Bean(name = "secondConnectionFactory") // public ConnectionFactory secondConnectionFactory( // @Value("${spring.rabbitmq.second.host}") String host, // @Value("${spring.rabbitmq.second.port}") int port, // @Value("${spring.rabbitmq.second.username}") String username, // @Value("${spring.rabbitmq.second.password}") String password // ) { // CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); // connectionFactory.setHost(host); // connectionFactory.setPort(port); // connectionFactory.setUsername(username); // connectionFactory.setPassword(password); // return connectionFactory; // } @Bean(name = "firstRabbitTemplate") @Primary //默认使用 public RabbitTemplate firstRabbitTemplate( @Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory ) { RabbitTemplate firstRabbitTemplate = new RabbitTemplate(connectionFactory); return firstRabbitTemplate; } // @Bean(name = "secondRabbitTemplate") // public RabbitTemplate secondRabbitTemplate( // @Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory // ) { // RabbitTemplate secondRabbitTemplate = new RabbitTemplate(connectionFactory); // return secondRabbitTemplate; // } @Bean(name = "firstFactory") public SimpleRabbitListenerContainerFactory firstFactory( SimpleRabbitListenerContainerFactoryConfigurer configurer, @Qualifier("firstConnectionFactory") ConnectionFactory connectionFactory ) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); configurer.configure(factory, connectionFactory); return factory; } // @Bean(name = "secondFactory") // public SimpleRabbitListenerContainerFactory secondFactory( // SimpleRabbitListenerContainerFactoryConfigurer configurer, // @Qualifier("secondConnectionFactory") ConnectionFactory connectionFactory // ) { // SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); // configurer.configure(factory, connectionFactory); // return factory; // } }
MQConsumer
package diit.microservice.handle.mq; import com.fasterxml.jackson.databind.ObjectMapper; import diit.microservice.handle.entity.DataHandle; import diit.microservice.handle.service.strategy.manager.HandleBaseFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Component //@RabbitListener(queuesToDeclare = @Queue("hello-world")) public class MQConsumer { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private HandleBaseFactory handleBaseFactory; @RabbitHandler // @RabbitListener(queues = "${mq.handle}", containerFactory = "secondFactory")//指定使用secondFactory交换机 @RabbitListener(queues = {"${mq.handle}","hello1","hello2"})//使用默认交换机 public void receive(Message data) { String stringData = new String(data.getBody()); logger.info("message:"+stringData); handleBaseFactory.saveOrUpdate(stringData); } @RabbitHandler @RabbitListener(queues = "${mq.send}")//使用默认交换机 public void receiveRequest(Message data) { String stringData = new String(data.getBody()); System.out.println(stringData); logger.info(stringData); } // @RabbitHandler // @RabbitListener(queuesToDeclare = @Queue("hello-world")) // public void receive02(String msg){ // System.out.println("receive02:"+msg); // } // @RabbitListener(bindings = { // @QueueBinding(value = @Queue, // exchange = @Exchange(value = "dd",type = "fanout")) // }) // public void receiveFanout(String msg){ // System.out.println("fanout:"+msg); // } // // @RabbitListener(bindings = { // @QueueBinding(value = @Queue, // key = {"info"}, // exchange = @Exchange(value = "route-directs",type = "direct")) // }) // public void receiveDirect(String msg){ // System.out.println("direct:"+msg); // } }
RabbitMQ中如何解决消息丢失、重复、顺序和积压问题
1. 消息丢失问题
原因分析
- 生产端未确认发送成功(比如 network 闪断)
- RabbitMQ服务器未持久化(queue或message未设置 durable/persistent)
- 消费端消费后未 ack,RabbitMQ把消息丢弃
- Broker 宕机导致内存中的消息丢失
解决方案
-
生产端确认(Publisher Confirm)
-
打开
publisher confirms
机制,每条消息发送后确认成功/失败。 -
Spring AMQP 示例:
java复制编辑rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { // 成功 } else { // 失败处理,比如重试 } });
-
-
消息持久化(Persistent Message)
- 队列 durable
- 消息 deliveryMode = 2 (persistent)
java复制编辑MessageProperties props = message.getMessageProperties(); props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
-
消费端ACK确认
- 消费者手动ack(不要auto-ack)
- ack后RabbitMQ才删消息,否则宕机会重投递
java 复制编辑 channel.basicAck(deliveryTag, false);
-
镜像队列或Quorum队列
- 保证副本同步,Broker节点宕机时也不丢失消息。
2. 消息重复问题
原因分析
- 消费端处理逻辑异常,未ack,RabbitMQ重新投递消息
- 消费端ack前应用自己挂掉,RabbitMQ重新投递
- 网络异常,消费确认丢失
解决方案
- 消费端幂等性设计
- 每条消息有全局唯一ID(比如UUID,订单号)。
- 消费前查一查:这条业务记录是否已经处理过。
- 常见方法:
- 数据库表+唯一索引
- Redis Set / Bloom Filter
- 消费端优雅ack
- 先处理业务逻辑,确认成功后再
basicAck
- 避免“处理到一半”就ack,导致半吊子问题
- 先处理业务逻辑,确认成功后再
- 死信队列(DLX)结合重试策略
- 消息处理异常不要无脑nack并requeue,可以限制最大重试次数,超过次数进入死信队列分析。
3. 消息顺序问题
原因分析
- RabbitMQ默认不保证顺序,尤其是多消费者并发消费时。
- 重新投递(requeue)会打乱顺序。
解决方案
- 严格顺序要求:单线程消费 + 单队列
- 一个队列只挂一个消费线程,逐条处理。
- 局部顺序要求:按分区(Sharding Key)路由
- 相同逻辑的消息(比如同一个订单)发到同一个队列
- 交换机按自定义key绑定不同的队列
- 避免requeue
- 消息处理时异常,直接nack丢到死信,不requeue,防止插队。
- 顺序消费框架封装
- 自己在消费端做一个“顺序缓存”,本地排队处理。
4. 消息积压问题
原因分析
- 消费端消费速度跟不上生产端发送速度
- 消费端故障,导致积压
- 消费处理异常(慢处理、阻塞处理)
解决方案
-
监控告警
- 定时监控队列堆积量(queue depth),超阈值报警。
-
快速失败(流控)
-
上游生产者限流(Rate Limit)
-
消费端拒绝接收新的消息(prefetch count 控制)
java 复制编辑 channel.basicQos(100); // 一次最多给我100条,处理完再来
-
-
扩容消费端
- 增加消费者线程数
- 水平扩展多个消费实例(微服务模式)
-
优化消费处理逻辑
- 非核心逻辑异步化(比如调用慢API、写日志等)
- 使用批量消费(batch consume)
-
冷备迁移
- 如果积压太大,直接关掉当前消费实例,开启新的更大资源的实例,快速扫荡 backlog。
总结一张表(重点速览)
问题 | 主要原因 | 核心解决方案 |
---|---|---|
消息丢失 | 未持久化、未确认、宕机 | Publisher Confirm + Persistent + 手动ACK + 镜像/Quorum |
消息重复 | 消费重投、网络异常 | 消费幂等性设计 + 业务处理后ACK |
消息顺序 | 多消费者并发、requeue | 单线程消费 + 分区路由 + 禁止requeue |
消息积压 | 消费慢、故障 | 监控+限流+扩容+优化处理 |