Bootstrap

Spring Boot利用RabbitMQ的死信队列实现定时任务,如超时未支付,自动取消订单等等......

RabbitMQ死信队列

本文不介绍如何整合消息队列,有需要请参考我之前发布:https://blog.csdn.net/a870368162/article/details/99566685

git链接:https://github.com/a870368162/SpringBoot-RabbitMQ

RabbitMQ要实现延时任务,需要使用RabbitMQ的死信交换机(Exchange)和消息的存活时间TTL(Time To Live)来实现

死信交换机

死信交换机跟普通交换机一样,只是这个交换机用来存放过期的消息

  1. 当一个消息没有相对应的消费者对其进行消费, 并且消息设置了TTL,消息过期后会进入死信交换机。
  2. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
  3. 当消息进入死信交换机后,死信交换机在把消息转发给专门处理死信消息的消费者,及可实现定时任务

设置消息TTL(消息存活时间)

  1. 消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
  2. 可以通过设置消息的expiration字段属性来设置时间,代码如下:
	MessageProperties messageProperties = new MessageProperties();
	//这里设置消息过期的时间
	messageProperties.setExpiration(60+ "000");
	messageProperties.setCorrelationIdString(serialClient.getGlobalSerial().toString());
	Message message = new Message(JSONObject.toJSONString(这里写你要发送给队列的消息).getBytes(), messageProperties);
	// 消息持久化
	message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
	//发送消息给死信队列
	this.send.beadSend(RabbitMQConstant.TOPIC_ROUTINGKEY1, message);

当上面的消息扔到队列中后,过了60秒,如果没有被消费,它就会发送到死信交换机。
在这里插入图片描述

创建死信交换机, 死信队列以及处理死信消息的消费者

实现代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;

/**
 * @消息队列配置
 * @Autor zxf
 * @Date 2019/8/15
 */
@Configuration
public class QueueConfig {

    private  static final Logger logger = LoggerFactory.getLogger(QueueConfig.class);


    /**
     *  创建订阅模式交换机
     *  @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(RabbitMQConstant.FANOUT_EXCHANGE,true, false);
    }

    /**
     * 创建路由模式交换机
     * @return
     */
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(RabbitMQConstant.DIRECT_EXCHANGE,true, false);
    }

    /**
     * 创建主题模式交换机
     * @return
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(RabbitMQConstant.TOPIC_EXCHANGE,true, false);
    }

    /**
     * 创建死信交换机,跟普通交换机一样,只是死信交换机只用来接收过期的消息
     * @return
     */
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange(RabbitMQConstant.DEAD_EXCHANGE, true, false);
    }

    /**
     * 创建死信队列,该队列没有消费者,消息会设置过期时间,消息过期后会发送到死信交换机,在由死信交换机转发至处理该消息的队列中
     * @return
     */
    @Bean
    public Queue BeadQueue() {
        Map<String, Object> arguments = new HashMap<>(2);
        // 死信路由到死信交换器DLX
        arguments.put("x-dead-letter-exchange", RabbitMQConstant.DEAD_EXCHANGE);
        arguments.put("x-dead-letter-routing-key", RabbitMQConstant.ROUTING_KEY2);
        return new Queue(RabbitMQConstant.DEAD_QUEUE, true, false, false, arguments);

    }

    /**
     * 处理死信队列的消费队列
     *
     */
    @Bean
    public Queue consumerBeadQueue() {
        return new Queue(RabbitMQConstant.CONSUMER_BEAD_QUEUE, true); // 队列持久

    }

    /**
     * 创建队列1
     * @return
     */
    @Bean
    public Queue Queue1() {
        //队列持久化
        return new Queue(RabbitMQConstant.QUEUE_1, true);
    }

    /**
     * 创建队列2
     * @return
     */
    @Bean
    public Queue Queue2() {
        return new Queue(RabbitMQConstant.QUEUE_2, true);
    }

    /**
     * 订阅模式队列1绑定交换机
     * @return
     */
    @Bean
    public Binding fanoutBinding1() {
        return BindingBuilder.bind(Queue1()).to(fanoutExchange());
    }

    /**
     * 订阅模式队列2绑定交换机
     * @return
     */
    @Bean
    public Binding fanoutBinding2() {
        return BindingBuilder.bind(Queue2()).to(fanoutExchange());
    }

    /**
     * 路由模式队列1绑定交换机,通过key1发送
     * @return
     */
    @Bean
    public Binding directBinding1() {
        return BindingBuilder.bind(Queue1()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY1);
    }

    /**
     * 路由模式队列2绑定交换机,通过key2发送
     * @return
     */
    @Bean
    public Binding directBinding2() {
        return BindingBuilder.bind(Queue2()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY2);
    }

    /**
     * 主题模式队列1绑定交换机
     * 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
     * @return
     */
    @Bean
    public Binding topicBinding1() {
        return BindingBuilder.bind(Queue1()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY1);
    }

    /**
     * 主题模式队列1绑定交换机
     * 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
     * @return
     */
    @Bean
    public Binding topicBinding2() {
        return BindingBuilder.bind(Queue2()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY2);
    }


    /**
     * 将死信队列与死信交换机绑定,key1
     *
     * @return
     */
    @Bean
    public Binding beadQueuebinding() {
        return BindingBuilder.bind(BeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY1);
    }

    /**
     * 将处理死信队列的消费队列与死信交换机绑定 key2
     *
     * @return
     */
    @Bean
    public Binding consumerBeadQueuebinding() {
        return BindingBuilder.bind(consumerBeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY2);
    }
}

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


/**
 * @消息队列发送工具类
 * @Autor zxf
 * @Date 2019/8/15
 */
@Component
public class Send implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private  static final Logger log = LoggerFactory.getLogger(Send.class);

    private RabbitTemplate rabbitTemplate;

    @Autowired
    public Send(RabbitTemplate rabbitTemplate) {
        super();
        this.rabbitTemplate = rabbitTemplate;
        this.rabbitTemplate.setMandatory(true);
        this.rabbitTemplate.setReturnCallback(this);
        this.rabbitTemplate.setConfirmCallback(this);
    }

    /**
     * 发布/订阅模式发送
     * @param json
     */
    public void routeSend(String json) {
        Message message = this.setMessage(json);
        //在fanoutExchange中在绑定Q到X上时,会自动把Q的名字当作bindingKey。
        this.rabbitTemplate.convertAndSend(RabbitMQConstant.FANOUT_EXCHANGE, "", message);
    }

    /**
     * 简单模式发送
     * @param json
     */
    public void simplSend(String json) {
        Message message = this.setMessage(json);
        this.rabbitTemplate.convertAndSend(RabbitMQConstant.QUEUE_1, message);
    }

    /**
     * 路由模式发送
     *  @param routingKey
     * @param json
     */
    public void routingSend(String routingKey, String json) {
        Message message = this.setMessage(json);
        this.rabbitTemplate.convertAndSend(RabbitMQConstant.DIRECT_EXCHANGE, routingKey, message);
    }

    /**
     * 主题模式发送
     *
     * @param routingKey
     * @param json
     */
    public void topicSend(String routingKey, String json) {
        Message message = this.setMessage(json);
        this.rabbitTemplate.convertAndSend(RabbitMQConstant.TOPIC_EXCHANGE, routingKey, message);
    }

    /**
     * 死信模式发送,用于定时任务处理
     *  @param routingKey
     * @param message
     */
    public void beadSend(String routingKey, Message message) {
        this.rabbitTemplate.convertAndSend(RabbitMQConstant.DEAD_EXCHANGE, routingKey, message);
    }

    /**
     * 设置消息参数
     * @param json
     * @return
     */
    private Message setMessage(String json){
        MessageProperties messageProperties = new MessageProperties();
        Message message = new Message(json.getBytes(), messageProperties);
        //消息持久化
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        return message;
    }

    /**
     * 消息确认
     * @param correlationData
     * @param ack
     * @param cause
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送确认成功");
        } else {
            log.info("消息发送失败:" + cause);
        }
    }

    /**
     * 消息发送失败回传
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.info("return--message:" + new String(message.getBody()) + ",replyCode:" + replyCode + ",replyText:"
                + replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
        try {
            Thread.sleep(10000L);
            // TODO 重新发送消息至队列,此处应写一套重发机制,重发多少次结束,否则如果消息如果一直发送失败,则会一直发下去!
            this.rabbitTemplate.convertAndSend(exchange, routingKey, message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


超时未支付取消订单步骤如下:

  1. 创建订单入库后发送该订单的id到死信队列,设置过期时间(该队列不能有消费者)
  2. 消息过期后会通过死信交换机发送到相对应的处理队列中
  3. 从处理队列中获得订单id,修改订单状态为取消
;