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,修改订单状态为取消
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是一个简单的Spring Boot RabbitMQ应用程序的完整application.yml配置代码示例: ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: / listener: simple: acknowledge-mode: manual concurrency: 1 max-concurrency: 1 template: exchange: example.exchange routing-key: example.routingKey ``` 解释: - `spring.rabbitmq.host`:RabbitMQ服务器的主机名或IP地址。 - `spring.rabbitmq.port`:RabbitMQ服务器的端口号。 - `spring.rabbitmq.username`:连接RabbitMQ服务器的用户名。 - `spring.rabbitmq.password`:连接RabbitMQ服务器的密码。 - `spring.rabbitmq.virtual-host`:连接RabbitMQ服务器的虚拟主机。 - `spring.rabbitmq.listener.simple.acknowledge-mode`:确认模式,这里是手动确认模式。 - `spring.rabbitmq.listener.simple.concurrency`:消费者线程数。 - `spring.rabbitmq.listener.simple.max-concurrency`:最大消费者线程数。 - `spring.rabbitmq.template.exchange`:消息发送时使用的交换机名。 - `spring.rabbitmq.template.routing-key`:消息发送时使用的路由键。 你也可以通过application.yml文件配置RabbitMQ的其他参数,例如: ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: / listener: simple: acknowledge-mode: manual concurrency: 1 max-concurrency: 1 template: exchange: example.exchange routing-key: example.routingKey cache: channel: size: 100 checkout-timeout: 5000 connection: mode: connection size: 10 validated: true ``` 解释: - `spring.rabbitmq.cache.channel.size`:通道缓存的大小。 - `spring.rabbitmq.cache.channel.checkout-timeout`:通道缓存中的通道检出超时时间(毫秒)。 - `spring.rabbitmq.cache.connection.mode`:缓存模式,这里是连接模式。 - `spring.rabbitmq.cache.connection.size`:连接缓存的大小。 - `spring.rabbitmq.cache.connection.validated`:连接是否需要验证。 请注意,以上参数仅为示例,具体配置可能因应用程序需求而异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值