SpringBoot(四十)SpringBoot集成RabbitMQ利用逾期时间+死信队列实现延长队列

[复制链接]
发表于 2025-12-20 04:45:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
前边我们利用RabbitMQ实现了高并发下对流量的削峰填谷。正常在现实应用中大概也就够用了。

有的时间呢,我们必要利用到延长队列,RabbitMQ不像RocketMQ一样默认就支持延长队列,RabbitMQ是不支持延长队列的,但是呢?我们可以通过正常的队列加上消息的逾期时间,设置死信队列,来模拟实现延长队列。

一:创建寻常队列(设置逾期时间),绑定死信队列
很简单就是创建两个寻常队列,将一个寻常队列A指定为另一寻常队列B的死信队列。

且给寻常队列B设置逾期时间。

1:设置类具体代码如下:
具体代码如下:
  1. package com.modules.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.amqp.core.*;
  5. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  6. import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.amqp.rabbit.connection.ConnectionFactory;
  9. import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. @Configuration
  13. public class RabbitMQConfig
  14. {
  15.     @Value("${spring.rabbitmq.host}")
  16.     private String host;
  17.     @Value("${spring.rabbitmq.port}")
  18.     private int port;
  19.     @Value("${spring.rabbitmq.username}")
  20.     private String userName;
  21.     @Value("${spring.rabbitmq.password}")
  22.     private String password;
  23.     @Value("${spring.rabbitmq.listener.prefetch}")
  24.     private int prefetch;
  25.     @Value("${spring.rabbitmq.listener.concurrency}")
  26.     private int concurrentConsumers;
  27.     @Value("${spring.rabbitmq.listener.max-concurrency}")
  28.     private int maxConcurrentConsumers;
  29.     /**
  30.      * 链接RabbitMQ
  31.      * @return
  32.      */
  33.     @Bean
  34.     public ConnectionFactory connectionDirectFactory()
  35.     {
  36.         CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
  37.         connectionFactory.setHost(host);
  38.         connectionFactory.setPort(port);
  39.         connectionFactory.setUsername(userName);
  40.         connectionFactory.setPassword(password);
  41.         connectionFactory.setPublisherConfirms(true); //必须要设置
  42.         return connectionFactory;
  43.     }
  44.     /**
  45.      * 配置RabbitMQ参数
  46.      * @return
  47.      */
  48.     @Bean
  49.     public SimpleRabbitListenerContainerFactory rabbitDirectListenerContainerFactory()
  50.     {
  51.         SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
  52.         factory.setConnectionFactory(connectionDirectFactory());
  53.         //设置最小并发的消费者数量
  54.         factory.setConcurrentConsumers(concurrentConsumers);
  55.         //设置最大并发的消费者数量
  56.         factory.setMaxConcurrentConsumers(maxConcurrentConsumers);
  57.         //限流,单位时间内消费多少条记录
  58.         factory.setPrefetchCount(prefetch);
  59.         // json转消息
  60.         //factory.setMessageConverter(new Jackson2JsonMessageConverter());
  61.         //设置rabbit 确认消息的模式,默认是自动确认
  62.         //factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
  63.         //设置rabbit 确认消息的模式,默认是自动确认
  64.         factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
  65.         return factory;
  66.     }
  67.     /**
  68.      * 回调函数
  69.      * @param connectionFactory
  70.      * @return
  71.      */
  72.     @Bean
  73.     public RabbitTemplate createDirectRabbitTemplate(ConnectionFactory connectionFactory)
  74.     {
  75.         RabbitTemplate rabbitTemplate = new RabbitTemplate();
  76.         rabbitTemplate.setConnectionFactory(connectionFactory);
  77.         //设置开启Manatory,才能触发回调函数,无论消息推送结果怎么样都会强制调用回调函数
  78.         rabbitTemplate.setMandatory(true);
  79.         // 设置确认发送到交换机的回调函数 =》 消息推送到server,但是在server里找不到交换机 / 消息推送到sever,交换机和队列啥都没找到 / 消息推送到server,找到交换机了,但是没找到队列 / 消息推送成功
  80.         rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
  81.             if(ack)
  82.             {
  83.                 System.out.println("发送者消息确认成功!");
  84.             }
  85.             else
  86.             {
  87.                 System.out.println("发送者消息确认失败,考虑重发:"+cause);
  88.             }
  89.             //System.out.println("相关数据:"+correlationData);
  90.             //System.out.println("确认情况:"+ack);
  91.             //System.out.println("原因:"+cause);
  92.             //System.out.println("===============================");
  93.         });
  94.         //设置确认消息已发送到队列的回调  =》 消息推送到server,找到交换机了,但是没找到队列 触发这个回调函数
  95.         rabbitTemplate.setReturnsCallback(returnedMessage -> {
  96.             System.out.println("交换机为:"+returnedMessage.getExchange());
  97.             System.out.println("返回消息为:"+returnedMessage.getMessage());
  98.             System.out.println("路由键为:"+returnedMessage.getRoutingKey());
  99.             System.out.println("回应消息为:"+returnedMessage.getReplyText());
  100.             System.out.println("回应代码为:"+returnedMessage.getReplyCode());
  101.             System.out.println("===============================");
  102.         });
  103.         return rabbitTemplate;
  104.     }
  105.     //正常队列
  106.     // 交换机
  107.     public static final String NORMAL_EXCHANGE  = "normal_exchange";
  108.     // 队列名称
  109.     public static final String NORMAL_QUEUE  = "normal_queue";
  110.     // 路由键
  111.     public static final String NORMAL_ROUTE = "normal_route";
  112.     //死信队列
  113.     // 交换机
  114.     public static final String DEAD_EXCHANGE = "dead_exchange";
  115.     // 队列名称
  116.     public static final String DEAD_QUEUE = "dead_queue";
  117.     // 路由键
  118.     public static final String DEAD_ROUTE = "dead_route";
  119.     /**
  120.      * 死信交换机
  121.      * @return
  122.      */
  123.     @Bean
  124.     public Exchange deadExchange()
  125.     {
  126.         // 创建死信队列交换机
  127.         return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
  128.     }
  129.     /**
  130.      * 死信队列
  131.      * @return
  132.      */
  133.     @Bean
  134.     public Queue deadQueue()
  135.     {
  136.         // 创建死信队列
  137.         return QueueBuilder.durable(DEAD_QUEUE).build();
  138.     }
  139.     /**
  140.      * 死信交换机绑定死信队列
  141.      * @param deadExchange
  142.      * @param deadQueue
  143.      * @return
  144.      */
  145.     @Bean
  146.     public Binding deadBinding(Exchange deadExchange,Queue deadQueue)
  147.     {
  148.         // 死信交换机绑定死信队列
  149.         return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTE).noargs();
  150.     }
  151.     /**
  152.      * 设置正常队列(过期时间)
  153.      */
  154.     /*@Bean
  155.     public Queue TTLQUEUE()
  156.     {
  157.         Map<String, Object> map = new HashMap<>();
  158.         map.put("x-message-ttl", 30000); // 队列中的消息未被消费则30秒后过期
  159.         return new Queue(NORMAL_QUEUE, true, false, false, map);
  160.     }//*/
  161.     /**
  162.      * 绑定死信交换机及路由key(该正常队列内的消息无法被正常消费时,会转发给绑定的死信交换机通过路由key转发到死信队列)
  163.      */
  164.     @Bean
  165.     public Queue normalQueue()
  166.     {
  167.         // 创建队列
  168.         return QueueBuilder.durable(NORMAL_QUEUE)
  169.                 .deadLetterExchange(DEAD_EXCHANGE)// 绑定死信队列交换机
  170.                 .deadLetterRoutingKey(DEAD_ROUTE)// 绑定死信队列路由
  171.                 .ttl(30000)// 设置消息过期时间
  172.                 .build();
  173.     }
  174.     /**
  175.      * 正常队列交换机
  176.      */
  177.     @Bean
  178.     public DirectExchange normalExchange()
  179.     {
  180.         // return new DirectExchange(NORMAL_EXCHANGE);
  181.         // 创建交换机
  182.         return ExchangeBuilder.directExchange(DEAD_EXCHANGE).build();
  183.     }
  184.     /**
  185.      * 正常交换机绑定正常队列
  186.      */
  187.     @Bean
  188.     public Binding binding(Queue normalQueue,Exchange normalExchange)
  189.     {
  190.         // 将 队列 交换机 路由key绑定到一起。
  191.         return BindingBuilder.bind(normalQueue).to(normalExchange).with(NORMAL_ROUTE).noargs();
  192.     }//*/
  193. }
复制代码
2:生产者代码如下:
  1. package com.modules.controller.rabbitmq;
  2. import org.springframework.amqp.AmqpException;
  3. import org.springframework.amqp.core.Message;
  4. import org.springframework.amqp.core.MessagePostProcessor;
  5. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.RequestParam;
  9. import org.springframework.web.bind.annotation.RestController;
  10. @RestController
  11. public class TTLController
  12. {
  13.     @Autowired
  14.     private RabbitTemplate rabbitTemplate;
  15.     @GetMapping("/java/ttlproduct")
  16.     public String sendTrafficMessage(@RequestParam String message)
  17.     {
  18.         for (int i = 1; i <= 100; i++)
  19.         {
  20.             // 使用java多线程来模拟多用户并发请求
  21.             final int temp = i;
  22.             new Thread(
  23.                     ()->{
  24.                         // 给RabbitMQ发送消息
  25.                         rabbitTemplate.convertAndSend(
  26.                                 "normal_exchange",
  27.                                 "normal_route",
  28.                                 "hello world:"+temp,
  29.                                 new MessagePostProcessor() {
  30.                                     @Override
  31.                                     public Message postProcessMessage(Message message) throws
  32.                                             AmqpException
  33.                                     {
  34.                                         // System.out.println("发送回调:"+temp);
  35.                                         System.out.println(message);
  36.                                         return message;
  37.                                     }
  38.                                 });
  39.                     }
  40.             ).start();
  41.         }
  42.         return "Message sent";
  43.     }
  44. }
复制代码
这里向上边创建的寻常队列推送消息。

3:斲丧者
斲丧者监听死信队列,上边我们创建的寻常队列的消息逾期时间是30秒,相当于我们向寻常队列中推送消息之后,30秒逾期则进入死信队列中,斲丧者监听死信队列,等候消息进入死信队列之后再举行斲丧处理处罚。如许就模拟了一个延长队列。
代码如下:
  1. package com.modules.controller.rabbitmq;
  2. import com.rabbitmq.client.Channel;
  3. import org.springframework.amqp.AmqpException;
  4. import org.springframework.amqp.core.Message;
  5. import org.springframework.amqp.core.MessagePostProcessor;
  6. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  7. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Component;
  10. import java.io.IOException;
  11. @Component
  12. public class TTLConsumer
  13. {
  14.     @Autowired
  15.     private RabbitTemplate rabbitTemplate;
  16.     /**
  17.      * 监听死信队列
  18.      * @param message
  19.      * @param channel
  20.      * @throws InterruptedException
  21.      * @throws IOException
  22.      */
  23.     @RabbitListener(queues = "dead_queue")
  24.     public void receiveMessage(Message message, Channel channel) throws InterruptedException, IOException
  25.     {
  26.         // 为了演示一个一个消费的情况,这里使用线程暂停来延迟控制台输出
  27.         Thread.sleep(100);
  28.         // ===============================
  29.         // 处理消息,例如写入数据库或进行计算
  30.         System.out.println("TTL Received message: " + new String(message.getBody()));
  31.         //System.out.println("channel: " + channel);
  32.         // =================================
  33.         // 成功处理后手动确认消息
  34.         long deliveryTag = message.getMessageProperties().getDeliveryTag();
  35.         //System.out.println("deliveryTag:"+deliveryTag);
  36.         channel.basicAck(deliveryTag, false);
  37.     }
  38. }
复制代码
运行生产者,登录RabbitMQ控制台,如下图所示:



以上大概就是SpringBoot集成RabbitMQ实现延长队列的全过程。

PS:redis也是可以通过zset来模拟实现延长队列的,score存时间戳,每次取当前时间多少秒之前的数据即可。这里不做过多讨论。

有好的发起,请在下方输入你的批评。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表