rabbitmq的延迟消息队列实现

第一部分:延迟消息的实现原理和知识点

使用RabbitMQ来实现延迟任务必须先了解RabbitMQ的两个概念:消息的TTL和死信Exchange,通过这两者的组合来实现上述需求。

消息的TTL(Time To Live)

消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。

可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串:

rabbitmq的延迟消息队列实现_第1张图片
image.png

当上面的消息扔到队列中后,过了3分钟,如果没有被消费,它就死了。不会被消费者消费到。这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去。单靠死信还不能实现延迟任务,还要靠Dead Letter Exchange。

Dead Letter Exchanges

Exchage的概念在这里就不在赘述。一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。

  1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。

  2. 上面的消息的TTL到了,消息过期了。

  3. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。

Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

实现延迟队列

延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。


rabbitmq的延迟消息队列实现_第2张图片
image.png

生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如3分钟。消息会在Queue1中等待3分钟,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。

完成延迟任务的实现。

第二部分:具体实现例子

1、新建立消息队列配置文件rabbitmq.properties

 #rabbitmq消息队列的属性配置文件properties
 rabbitmq.study.host=192.168.56.101
 rabbitmq.study.username=duanml
 rabbitmq.study.password=1qaz@WSX
 rabbitmq.study.port=5672
 rabbitmq.study.vhost=studymq
 
 #Mail 消息队列的相关变量值
 mail.exchange=mailExchange
 mail.exchange.key=mail_queue_key
 
 
 #Phone 消息队列的相关变量值
 phone.topic.key=phone.one
 phone.topic.key.more=phone.one.more
 
 #delay 延迟消息队列的相关变量值
 delay.directQueue.key=TradePayNotify_delay_2m
 delay.directMessage.key=TradePayNotify_delay_3m

2、新建立配置文件,申明延迟队列相关的配置信息如:spring-rabbigmq-dlx.xml

 
 
 
      
 
     
     
     
 
     
     
         
             
         
     
 
     
     
     
     
     
     
 
     
     
         
     
 
     
 
     
     
 
     
     
         
     
 
     
     
         
             
             
             
         
     
 
     
     
         
             
             
         
     
 
     
     
         
             
             
         
      
 

3、新建立延迟队列测试Controller

 package org.seckill.web;
 
 import org.seckill.dto.SeckillResult;
 import org.seckill.entity.Seckill;
 import org.seckill.utils.rabbitmq.Impl.MQProducerImpl;
 import org.seckill.utils.rabbitmq.MQProducer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.amqp.core.Message;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import java.util.Date;
 
 /**
  * Description: 消息队列测试
  */
 @Controller
 @RequestMapping("/rabbitmq")
 public class RabbitmqController {
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
  
     @Value("${delay.directQueue.key}")
     private String delay_directQueue_key;
 
     @Value("${delay.directMessage.key}")
     private String delay_directMessage_key;
 
     @Autowired
     private MQProducerImpl delayMQProducerImpl;111 
     /**
      * @Description: 消息队列
      * @Author:
      * @CreateTime:
      */
     @ResponseBody
     @RequestMapping("/sendDelayQueue")
     public SeckillResult testDelayQueue() {
         SeckillResult result = null;
         Date now = new Date();
         try {
             Seckill seckill = new Seckill();
        //第一种情况,给队列设置消息ttl,详情见配置文件
             for (int i = 0; i < 2; i++) {
                 seckill.setSeckillId(1922339387 + i);
                 seckill.setName("delay_queue_ttl_" + i);
                 String msgId = delayMQProducerImpl.getMsgId();
                 Message message = delayMQProducerImpl.messageBuil(seckill,msgId);
                 delayMQProducerImpl.sendDataToRabbitMQ(delay_directQueue_key, message);
             }
         //第二种情况,给消息设置ttl
             for (int i = 0; i < 2; i++) {
                 seckill.setSeckillId(1922339287 + i);
                 seckill.setName("delay_message_ttl_" + i);
                 String msgId = delayMQProducerImpl.getMsgId();
                 Message message = delayMQProducerImpl.messageBuil(seckill,msgId);
                 if (message != null) {
                     //给消息设置过期时间ttl,为3分钟
                     message.getMessageProperties().setExpiration("180000");
                     delayMQProducerImpl.sendDataToRabbitMQ(delay_directMessage_key, message);
                 }
             }
             result = new SeckillResult(true, now.getTime());
         } catch (Exception e) {
             logger.error(e.getMessage(), e);
         }
         return result;
     }
 
 }

4、编写延迟消息确认类和监听类:
NotifyConfirmCallBackListener.java

 package org.seckill.rabbitmqListener.notify;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
 import org.springframework.amqp.rabbit.support.CorrelationData;
 
 /**
  * Description: 延迟任务测试--->消息确认回调类
  */
 public class NotifyConfirmCallBackListener implements ConfirmCallback {
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
     /**
      * Confirmation callback.
      *
      * @param correlationData correlation data for the callback.
      * @param ack             true for ack, false for nack
      * @param cause           An optional cause, for nack, when available, otherwise null.
      */
     public void confirm(CorrelationData correlationData, boolean ack, String cause) {
         logger.info("延迟测试---确认消息完成-------->confirm--:correlationData:" + correlationData.getId() + ",ack:" + ack + ",cause:" + cause);
     }
 }

NotifyConsumerListener.java

 package org.seckill.rabbitmqListener.notify;
 
 import com.alibaba.fastjson.JSONObject;
 import com.rabbitmq.client.Channel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
 
 /**
  * Description: 订单通知队列监听服务
  * 实现延迟任务的功能
  */
 public class NotifyConsumerListener implements ChannelAwareMessageListener {
 
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
     /**
      * Callback for processing a received Rabbit message.
      * 

Implementors are supposed to process the given Message, * typically sending reply messages through the given Session. * * @param message the received AMQP message (never null) * @param channel the underlying Rabbit Channel (never null) * @throws Exception Any. */ public void onMessage(Message message, Channel channel) throws Exception { try { //将字节流对象转换成Java对象 // Seckill seckill=(Seckill) new ObjectInputStream(new ByteArrayInputStream(message.getBody())).readObject(); String returnStr = new String(message.getBody(),"UTF-8"); JSONObject jsStr = JSONObject.parseObject(returnStr); logger.info("延迟测试--消费开始:名称为--===>" + jsStr.getString("name") + "----->返回消息:" + returnStr + "||||消息的Properties:--》" + message.getMessageProperties()); //TODO 进行相关业务操作 //成功处理业务,那么返回消息确认机制,这个消息成功处理OK channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { //消息已经进行过一次轮询操作,还是失败,将拒绝再次接收本消息 logger.info("消息已重复处理失败,拒绝再次接收..."); channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); // 拒绝消息 //TODO 进行相关业务操作 } else { //消息第一次接收处理失败后,将再此回到队列中进行 再一次轮询操作 logger.info("消息即将再次返回队列处理..."); //处理失败,那么返回消息确认机制,这个消息没有成功处理,返回到队列中 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }

NotifyFailedCallBackListener.java

 package org.seckill.rabbitmqListener.notify;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;

 /**
  * Description: 延迟任务测试----> 消息发送失败回调类
  */
 public class NotifyFailedCallBackListener implements ReturnCallback {

     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
     /**
      * Returned message callback.
      *
      * @param message    the returned message.
      * @param replyCode  the reply code.
      * @param replyText  the reply text.
      * @param exchange   the exchange.
      * @param routingKey the routing key.
      */
     public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
         logger.info("延迟测试------------->return--message:" +
                 new String(message.getBody()) +
                 ",replyCode:" + replyCode + ",replyText:" + replyText +
                 ",exchange:" + exchange + ",routingKey:" + routingKey);
     }
 }

5、编写消息队列的操作类和接口:
MQProducer.java

 package org.seckill.utils.rabbitmq;
 
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.core.MessagePostProcessor;
 import org.springframework.amqp.rabbit.support.CorrelationData; 

/**
 * Description: No Description
 */
 public interface MQProducer {
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param message
      */
     void sendDataToRabbitMQ(java.lang.Object message);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param message
      * @param messagePostProcessor
      */
     void sendDataToRabbitMQ(java.lang.Object message, MessagePostProcessor messagePostProcessor);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param message
      * @param messagePostProcessor
      * @param correlationData
      */
     void sendDataToRabbitMQ(java.lang.Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param routingKey
      * @param message
       */
     void sendDataToRabbitMQ(java.lang.String routingKey, java.lang.Object message);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param routingKey
      * @param message
      * @param correlationData
      */
     void sendDataToRabbitMQ(java.lang.String routingKey, java.lang.Object message, CorrelationData correlationData);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param routingKey
      * @param message
      * @param messagePostProcessor
      */
     void sendDataToRabbitMQ(java.lang.String routingKey, java.lang.Object message, MessagePostProcessor messagePostProcessor);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param routingKey
      * @param message
      * @param messagePostProcessor
      * @param correlationData
      */
     void sendDataToRabbitMQ(java.lang.String routingKey, java.lang.Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param exchange
      * @param routingKey
      * @param message
      */
     void sendDataToRabbitMQ(java.lang.String exchange, java.lang.String routingKey, java.lang.Object message);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param exchange
      * @param routingKey
      * @param message
      * @param correlationData
      */
     void sendDataToRabbitMQ(java.lang.String exchange, java.lang.String routingKey, java.lang.Object message, CorrelationData correlationData);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param exchange
      * @param routingKey
      * @param message
      * @param messagePostProcessor
      */
     void sendDataToRabbitMQ(java.lang.String exchange, java.lang.String routingKey, java.lang.Object message, MessagePostProcessor messagePostProcessor);
 
     /**
      * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key.
      * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。
      * @param exchange
      * @param routingKey
      * @param message
      * @param messagePostProcessor
      * @param correlationData
      */
     void sendDataToRabbitMQ(java.lang.String exchange, java.lang.String routingKey, java.lang.Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData);
 
     Message messageBuil(Object handleObject, String msgId);
 
     String getMsgId();
 }

MQProducerImpl.java

 package org.seckill.utils.rabbitmq.Impl;
 
 import com.alibaba.fastjson.JSONObject;
 import org.seckill.utils.rabbitmq.MQProducer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.amqp.AmqpException;
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.core.MessageBuilder;
 import org.springframework.amqp.core.MessagePostProcessor;
 import org.springframework.amqp.core.MessageProperties;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.amqp.rabbit.support.CorrelationData;
 import org.springframework.stereotype.Component;
 
 import java.io.UnsupportedEncodingException;
 import java.util.UUID;
 
 /**
  * Description: 消息生产者操作主体类
  */
 @Component
 public class MQProducerImpl implements MQProducer{
 
     private static final Logger logger = LoggerFactory.getLogger(MQProducerImpl.class);
 
     private RabbitTemplate rabbitTemplate;
 
     /**
      * Sets the rabbitTemplate.
      * 

*

You can use getRabbitTemplate() to get the value of rabbitTemplate

* * @param rabbitTemplate rabbitTemplate */ public void setRabbitTemplate(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param message */ public void sendDataToRabbitMQ(Object message) { try { if (message instanceof Message){ Message messageSend = (Message) message; String msgId = messageSend.getMessageProperties().getCorrelationId(); CorrelationData correlationData = new CorrelationData(msgId); rabbitTemplate.convertAndSend(rabbitTemplate.getRoutingKey(),message,correlationData); }else { rabbitTemplate.convertAndSend(message); } } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param message * @param messagePostProcessor */ public void sendDataToRabbitMQ(Object message, MessagePostProcessor messagePostProcessor) { try { if (message instanceof Message){ Message messageSend = (Message) message; String msgId = messageSend.getMessageProperties().getCorrelationId(); CorrelationData correlationData = new CorrelationData(msgId); rabbitTemplate.convertAndSend(rabbitTemplate.getRoutingKey(),message,messagePostProcessor,correlationData); }else { rabbitTemplate.convertAndSend(message, messagePostProcessor); } } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a default routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param message * @param messagePostProcessor * @param correlationData */ public void sendDataToRabbitMQ(Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData) { try { rabbitTemplate.convertAndSend(message, messagePostProcessor, correlationData); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param routingKey * @param message */ public void sendDataToRabbitMQ(String routingKey, Object message) { try { if (message instanceof Message){ Message messageSend = (Message) message; String msgId = messageSend.getMessageProperties().getCorrelationId(); CorrelationData correlationData = new CorrelationData(msgId); rabbitTemplate.convertAndSend(routingKey,message,correlationData); }else { rabbitTemplate.convertAndSend(routingKey, message); } } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param routingKey * @param message * @param correlationData */ public void sendDataToRabbitMQ(String routingKey, Object message, CorrelationData correlationData) { try { rabbitTemplate.convertAndSend(routingKey, message, correlationData); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param routingKey * @param message * @param messagePostProcessor */ public void sendDataToRabbitMQ(String routingKey, Object message, MessagePostProcessor messagePostProcessor) { try { if (message instanceof Message){ Message messageSend = (Message) message; String msgId = messageSend.getMessageProperties().getCorrelationId(); CorrelationData correlationData = new CorrelationData(msgId); rabbitTemplate.convertAndSend(routingKey,message,messagePostProcessor,correlationData); }else { rabbitTemplate.convertAndSend(routingKey, message, messagePostProcessor); } } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a default exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param routingKey * @param message * @param messagePostProcessor * @param correlationData */ public void sendDataToRabbitMQ(String routingKey, Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData) { try { rabbitTemplate.convertAndSend(routingKey, message, messagePostProcessor, correlationData); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param exchange * @param routingKey * @param message */ public void sendDataToRabbitMQ(String exchange, String routingKey, Object message) { try { if (message instanceof Message){ Message messageSend = (Message) message; String msgId = messageSend.getMessageProperties().getCorrelationId(); CorrelationData correlationData = new CorrelationData(msgId); rabbitTemplate.convertAndSend(routingKey,message,correlationData); }else { rabbitTemplate.convertAndSend(exchange, routingKey, message); } } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param exchange * @param routingKey * @param message * @param correlationData */ public void sendDataToRabbitMQ(String exchange, String routingKey, Object message, CorrelationData correlationData) { try { rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param exchange * @param routingKey * @param message * @param messagePostProcessor */ public void sendDataToRabbitMQ(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor) { try { rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * Convert a Java object to an Amqp Message and send it to a specific exchange with a specific routing key. * 由于配置了JSON转换,这里是将Java对象转换成JSON字符串的形式。 * * @param exchange * @param routingKey * @param message * @param messagePostProcessor * @param correlationData */ public void sendDataToRabbitMQ(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor, CorrelationData correlationData) { try { rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor, correlationData); } catch (AmqpException e) { logger.error(e.getMessage(), e); } } /** * 构建Message对象,进行消息发送 * @param handleObject * @param msgId * @return */ public Message messageBuil(Object handleObject, String msgId) { try { //先转成JSON String objectJSON = JSONObject.toJSONString(handleObject); //再构建Message对象 Message messageBuil = MessageBuilder.withBody(objectJSON.getBytes("UTF-8")).setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) .setCorrelationId(msgId).build(); return messageBuil; } catch (UnsupportedEncodingException e) { logger.error("构建Message出错:" + e.getMessage(),e); return null; } } /** * 生成唯一的消息操作id * @return */ public String getMsgId() { return UUID.randomUUID().toString(); } }

至此就完成了延迟消息队列的所有代码实现。

参考:

rabbitmq的延迟消息队列实现

RabbitMQ 延时消息队列

使用spring-rabbit测试RabbitMQ消息确认(发送确认,接收确认)

你可能感兴趣的:(rabbitmq的延迟消息队列实现)