0


RabbitMQ实现延时消息的两种方法

RabbitMQ实现延时消息的两种方法

1、死信队列

1.1消息什么时候变为死信(dead-letter)

  1. 消息被否定接收,消费者使用basic.reject 或者 basic.nack并且requeue 重回队列属性设为false。
  2. 消息在队列里得时间超过了该消息设置的过期时间(TTL)。
  3. 消息队列到达了它的最大长度,之后再收到的消息。

1.2死信队列的原理

当一个消息再队列里变为死信时,它会被重新publish到另一个exchange交换机上,这个exchange就为DLX。因此我们只需要在声明正常的业务队列时添加一个可选的"x-dead-letter-exchange"参数,值为死信交换机,死信就会被rabbitmq重新publish到配置的这个交换机上,我们接着监听这个交换机就可以了。

1.3 代码实现

  1. 引入amqp依赖
  2. 声明交换机,队列
packagecom.lank.demo.config;importorg.springframework.amqp.core.*;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.HashMap;importjava.util.Map;@ConfigurationpublicclassRabbitmqConfig{//死信交换机,队列,路由相关配置publicstaticfinalString DLK_EXCHANGE ="dlk.exchange";publicstaticfinalString DLK_ROUTEKEY ="dlk.routeKey";publicstaticfinalString DLK_QUEUE ="dlk.queue";//业务交换机,队列,路由相关配置publicstaticfinalString DEMO_EXCHANGE ="demo.exchange";publicstaticfinalString DEMO_QUEUE ="demo.queue";publicstaticfinalString DEMO_ROUTEKEY ="demo.routeKey";//延时插件DelayedMessagePlugin的交换机,队列,路由相关配置publicstaticfinalString DMP_EXCHANGE ="dmp.exchange";publicstaticfinalString DMP_ROUTEKEY ="dmp.routeKey";publicstaticfinalString DMP_QUEUE ="dmp.queue";@BeanpublicDirectExchangedemoExchange(){returnnewDirectExchange(DEMO_EXCHANGE,true,false);}@BeanpublicQueuedemoQueue(){//只需要在声明业务队列时添加x-dead-letter-exchange,值为死信交换机Map<String,Object> map =newHashMap<>(1);
        map.put("x-dead-letter-exchange",DLK_EXCHANGE);//该参数x-dead-letter-routing-key可以修改该死信的路由key,不设置则使用原消息的路由key
        map.put("x-dead-letter-routing-key",DLK_ROUTEKEY);returnnewQueue(DEMO_QUEUE,true,false,false,map);}@BeanpublicBindingdemoBind(){returnBindingBuilder.bind(demoQueue()).to(demoExchange()).with(DEMO_ROUTEKEY);}@BeanpublicDirectExchangedlkExchange(){returnnewDirectExchange(DLK_EXCHANGE,true,false);}@BeanpublicQueuedlkQueue(){returnnewQueue(DLK_QUEUE,true,false,false);}@BeanpublicBindingdlkBind(){returnBindingBuilder.bind(dlkQueue()).to(dlkExchange()).with(DLK_ROUTEKEY);}//延迟插件使用//1、声明一个类型为x-delayed-message的交换机//2、参数添加一个x-delayed-type值为交换机的类型用于路由key的映射@BeanpublicCustomExchangedmpExchange(){Map<String,Object> arguments =newHashMap<>(1);
        arguments.put("x-delayed-type","direct");returnnewCustomExchange(DMP_EXCHANGE,"x-delayed-message",true,false,arguments);}@BeanpublicQueuedmpQueue(){returnnewQueue(DMP_QUEUE,true,false,false);}@BeanpublicBindingdmpBind(){returnBindingBuilder.bind(dmpQueue()).to(dmpExchange()).with(DMP_ROUTEKEY).noargs();}}
  1. 声明一个类用于发送带过期时间的消息
packagecom.lank.demo.rabbitmq;importcom.lank.demo.config.RabbitmqConfig;importlombok.extern.slf4j.Slf4j;importorg.springframework.amqp.AmqpException;importorg.springframework.amqp.core.Message;importorg.springframework.amqp.core.MessagePostProcessor;importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;/**
 1. @author lank
 2. @since 2020/12/14 10:33
 */@Component@Slf4jpublicclassMessageSender{@AutowiredprivateRabbitTemplate rabbitTemplate;//使用死信队列发送消息方法封装publicvoidsend(String message,Integer time){String ttl =String.valueOf(time*1000);//exchange和routingKey都为业务的就可以,只需要设置消息的过期时间
        rabbitTemplate.convertAndSend(RabbitmqConfig.DEMO_EXCHANGE,RabbitmqConfig.DEMO_ROUTEKEY,message,newMessagePostProcessor(){@OverridepublicMessagepostProcessMessage(Message message)throwsAmqpException{//设置消息的过期时间,是以毫秒为单位的
                message.getMessageProperties().setExpiration(ttl);return message;}});
        log.info("使用死信队列消息:{}发送成功,过期时间:{}秒。",message,time);}//使用延迟插件发送消息方法封装publicvoidsend2(String message,Integer time){
        rabbitTemplate.convertAndSend(RabbitmqConfig.DMP_EXCHANGE,RabbitmqConfig.DMP_ROUTEKEY,message,newMessagePostProcessor(){@OverridepublicMessagepostProcessMessage(Message message)throwsAmqpException{//使用延迟插件只需要在消息的header中添加x-delay属性,值为过期时间,单位毫秒
                message.getMessageProperties().setHeader("x-delay",time*1000);return message;}});
        log.info("使用延迟插件发送消息:{}发送成功,过期时间:{}秒。",message,time);}}
  1. 编写一个类用于消费消息
packagecom.lank.demo.rabbitmq;importcom.lank.demo.config.RabbitmqConfig;importlombok.extern.slf4j.Slf4j;importorg.springframework.amqp.core.Message;importorg.springframework.amqp.rabbit.annotation.RabbitHandler;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereotype.Component;@Component@Slf4jpublicclassMessageReceiver{@RabbitHandler@RabbitListener(queues =RabbitmqConfig.DLK_QUEUE)publicvoidonMessage(Message message){
        log.info("使用死信队列,收到消息:{}",newString(message.getBody()));}@RabbitHandler@RabbitListener(queues =RabbitmqConfig.DMP_QUEUE)publicvoidonMessage2(Message message){
        log.info("使用延迟插件,收到消息:{}",newString(message.getBody()));}}
  1. 编写Controller调用发送消息方法测试结果
packagecom.lank.demo.controller;importcom.lank.demo.rabbitmq.MessageSender;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassMessageController{@AutowiredpublicMessageSender messageSender;//死信队列controller@GetMapping("/send")publicStringsend(@RequestParamString msg,Integer time){
        messageSender.send(msg,time);return"ok";}//延迟插件controller@GetMapping("/send2")publicStringsendByPlugin(@RequestParamString msg,Integer time){
        messageSender.send2(msg,time);return"ok";}}
  1. 配置文件application.properties
server.port=4399
#virtual-host使用默认的/就好,如果需要/demo需自己在控制台添加
spring.rabbitmq.virtual-host=/demo
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
  1. 启动项目,打开rabbitmq控制台,可以看到交换机和队列已经创建好。

在这里插入图片描述

在这里插入图片描述

  1. 在浏览器中请求http://localhost:4399/send?msg=hello&time=5,从控制台的输出来看,刚好5s后接收到消息。
2020-12-16 22:47:28.071  INFO 13304 ---[nio-4399-exec-1] c.l.rabbitmqdlk.rabbitmq.MessageSender   : 使用死信队列消息:hello发送成功,过期时间:5秒。
2020-12-16 22:47:33.145  INFO 13304 ---[ntContainer#0-1] c.l.r.rabbitmq.MessageReceiver           : 使用死信队列,收到消息:hello

1.4死信队列的一个小注意点

当我往死信队列中发送两条不同过期时间的消息时,如果先发送的消息A的过期时间大于后发送的消息B的过期时间时,由于消息的顺序消费,消息B过期后并不会立即重新publish到死信交换机,而是会等到消息A过期后一起被消费。

依次发送两个请求http://localhost:4399/send?msg=消息A&time=30和http://localhost:4399/send?msg=消息B&time=10,消息A先发送,过期时间30S,消息B后发送,过期时间10S,我们想要的结果应该是10S收到消息B,30S后收到消息A,但结果并不是,控制台输出如下:

2020-12-16 22:54:47.339  INFO 13304 ---[nio-4399-exec-5] c.l.rabbitmqdlk.rabbitmq.MessageSender   : 使用死信队列消息:消息A发送成功,过期时间:30秒。
2020-12-16 22:54:54.278  INFO 13304 ---[nio-4399-exec-6] c.l.rabbitmqdlk.rabbitmq.MessageSender   : 使用死信队列消息:消息B发送成功,过期时间:10秒。
2020-12-16 22:55:17.356  INFO 13304 ---[ntContainer#0-1] c.l.r.rabbitmq.MessageReceiver           : 使用死信队列,收到消息:消息A
2020-12-16 22:55:17.357  INFO 13304 ---[ntContainer#0-1] c.l.r.rabbitmq.MessageReceiver           : 使用死信队列,收到消息:消息B

消息A30S后被成功消费,紧接着消息B被消费。因此当我们使用死信队列时应该注意是否消息的过期时间都是一样的,比如订单超过10分钟未支付修改其状态。如果当一个队列各个消息的过期时间不一致时,使用死信队列就可能达不到延时的作用。这时候我们可以使用延时插件来实现这需求。

2 、延时插件

RabbitMQ Delayed Message Plugin是一个rabbitmq的插件,所以使用前需要安装它,可以参考的GitHub地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

2.1如何实现

  1. 安装好插件后只需要声明一个类型type为"x-delayed-message"的exchange,并且在其可选参数下配置一个key为"x-delayed-typ",值为交换机类型(topic/direct/fanout)的属性。
  2. 声明一个队列绑定到该交换机
  3. 在发送消息的时候消息的header里添加一个key为"x-delay",值为过期时间的属性,单位毫秒。
  4. 代码就在上面,配置类为DMP开头的,发送消息的方法为send2()。
  5. 启动后在rabbitmq控制台可以看到一个类型为x-delayed-message的交换机。

在这里插入图片描述

在这里插入图片描述

  1. 继续在浏览器中发送两个请求http://localhost:4399/send2?msg=消息A&time=30和http://localhost:4399/send2?msg=消息B&time=10,控制台输出如下,不会出现死信队列出现的问题:
2020-12-16 23:31:19.819  INFO 13304 ---[nio-4399-exec-9] c.l.rabbitmqdlk.rabbitmq.MessageSender   : 使用延迟插件发送消息:消息A发送成功,过期时间:30秒。
2020-12-16 23:31:27.673  INFO 13304 ---[io-4399-exec-10] c.l.rabbitmqdlk.rabbitmq.MessageSender   : 使用延迟插件发送消息:消息B发送成功,过期时间:10秒。
2020-12-16 23:31:37.833  INFO 13304 ---[ntContainer#1-1] c.l.r.rabbitmq.MessageReceiver           : 使用延迟插件,收到消息:消息B
2020-12-16 23:31:49.917  INFO 13304 ---[ntContainer#1-1] c.l.r.rabbitmq.MessageReceiver           : 使用延迟插件,收到消息:消息A

本文转载自: https://blog.csdn.net/S0001100/article/details/131329904
版权归原作者 柳落青 所有, 如有侵权,请联系我们删除。

“RabbitMQ实现延时消息的两种方法”的评论:

还没有评论