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
消息积压 消费慢、故障 监控+限流+扩容+优化处理
posted @ 2023-03-15 14:07  Ideaway  阅读(265)  评论(0)    收藏  举报