好家伙,今天来看看这个通过TTL+死信形成的延时队列吧

1 篇文章 0 订阅
1 篇文章 0 订阅
前言:本来在上一篇队列安装文章后我就该写这篇文章的,但是手上事情太多,就一直没提上日程,今天就来小更一下

本次队列主讲RabbitMQ(你没装就去看我上一篇)

什么是TTL?

顾名思义,TTL解释为:生存时间值,也就是说,有一个队列里,里面的消息都会有一个过期时间,或这个队列有一个过期时间,这个概念就不过多赘述,相信各位都能理解。

什么是死信队列?

从字面上来看,就像死掉的信息,由此可见,有一个队列,里面装的都是死掉的消息,那么消息是怎么死掉的呢?

哪些情况下会触发消息进入死信队列

前提条件:原队列存在死信配置

  1. 原本目标队列消息长度已满,无法再继续存储,新增消息进入死信;

  2. 消费者拒接消费消息,并且不把消息重新放入原目标队列,这个消息会进入死信;

  3. 原队列存在消息过期设置,消息到达超时时间未被消费,这个消息会进入死信;

一个消息在一个队列里面由于各种原因没有被消费成功,没有成功从该队列中移除,而导致触发该消息被转发到另外一个队列中存储,这个消息被转移的目标队列即为:死信队列

我相信看到这里,你应该对延时队列有了一定的概念,你可以想象一下:一个过期队列里面有1条消息,这条消息的过期时间是1分钟,当这个消息在过期队列里存活了1分钟后死亡,如果这个队列存在死信配置,那么这个消息会被死信交换机分发给对应的死信队列,然后被死信消费者消费掉。前一个队列是过期的,后一个队列是即时消费的,将这两个队列的特性相结合,就形成了我们说的延时队列。

使用场景:
  1. 支付场景 —— 超时未支付
  2. 金融场景 ——
    <1>爬取用户征信数据;
    <2>自动审核;
    这个里面,如果采用的是异步消息机制处理该业务的话,<2>就需要做延迟消费处理,因为需要尽量保证<1>完成。
  3. 提交一个任务,5分钟后可以开始该任务

图解

在这里插入图片描述

在了解TTL和死信队列的原理后,我们直接上代码吧(基于SpringBoot工程)。

有请pom.xml文件

 <!-- 导入以下依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

上application.yml

以下请改成你自己的配置奥 !!!
spring:
  rabbitmq:
    host: 192.168.1.65
    port: 5672
    username: lk
    password: lk
    virtual-host: /

RabbitMQConfig 文件

package com.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitMQConfig {
    // 过期交换机名称
    public static final String TTL_ORDER_EXCHANGE = "ttl_order_exchange";
    // 过期队列名称
    public static final String TTL_ORDER_QUEUE = "ttl_order_queue";
    // 死信交换机名称
    public static final String ORDER_EXCHANGE_DLX = "order_exchange_dlx";
    // 死信队列名称
    public static final String ORDER_QUEUE_DLX = "order_queue_dlx";


    // 声明过期交换机
    @Bean(TTL_ORDER_EXCHANGE)
    public Exchange order_exchange(){
        return ExchangeBuilder.topicExchange(TTL_ORDER_EXCHANGE).durable(true).build();
    }

    // 声明过期队列
    @Bean(TTL_ORDER_QUEUE)
    public Queue topicQueue(){
        Map<String,Object> params = new HashMap<String, Object>(2);
        params.put("x-dead-letter-exchange",ORDER_EXCHANGE_DLX); // 死亡消息的归宿
        params.put("x-dead-letter-routing-key","del.hehe"); // 死亡消息的路由key
//        params.put("x-max-length",5); // 队列最大消息数 遵循队列的"先进先出(丢)"原则
//        params.put("x-overflow","reject-publish"); // 设置队列中的消息溢出后,该队列的行为:"拒绝接收"(所有消息)
        return QueueBuilder.durable(TTL_ORDER_QUEUE).withArguments(params).build();
    }

    // 绑定过期队列和过期交换机
    @Bean
    public Binding queueBindingExchange(@Qualifier(TTL_ORDER_QUEUE) Queue queue, @Qualifier(TTL_ORDER_EXCHANGE) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("test.#").noargs();
    }

    /**
     * 死信队列,用于接收处理过期队列内的消息
     * @return
     */
    // 声明死信交换机
    @Bean(ORDER_EXCHANGE_DLX)
    public Exchange order_exchange_d(){
        return ExchangeBuilder.topicExchange(ORDER_EXCHANGE_DLX).durable(true).build();
    }

    // 声明死信队列
    @Bean(ORDER_QUEUE_DLX)
    public Queue topicQueue_d(){
        return QueueBuilder.durable(ORDER_QUEUE_DLX).build();
    }

    // 绑定死信队列和死信交换机
    @Bean
    public Binding queueBindingExchange_d(@Qualifier(ORDER_QUEUE_DLX) Queue queue, @Qualifier(ORDER_EXCHANGE_DLX) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("del.#").noargs();
    }
}

消息生产者

这个我给写成了接口

package com.rabbitmq.producer;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.config.RabbitMQConfig;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
@RequestMapping("/lk")
@ResponseBody
public class ProducerRabbitMQ {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 向交换机发送消息,队列会自动从交换机拉取消息
    @RequestMapping("/rabbitmq")
    public String rabbitMQ(@RequestParam("routingKey") String routingKey){
        System.out.println("进入队列:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        rabbitTemplate.convertAndSend(RabbitMQConfig.TTL_ORDER_EXCHANGE,"test.test",routingKey,getMessagePostProcessor("5000")); // 设置5秒过期时间
        return "商品新增,routing key 为" + routingKey;
    }

    // 获取当前队列里的消息条数
    @RequestMapping("/rabbitmqtwo")
    public Integer rabbitMQTwo(){
        ConnectionFactory connectionFactory = rabbitTemplate.getConnectionFactory();
        // 创建连接
        Connection connection = connectionFactory.createConnection();
        // 创建通道
        Channel channel = connection.createChannel(false);
        Integer queueCount = null;
        // 设置消息交换机
        try {
            channel.exchangeDeclare(RabbitMQConfig.TTL_ORDER_EXCHANGE, "topic", true, false, null);
            AMQP.Queue.DeclareOk declareOk = channel.queueDeclarePassive(RabbitMQConfig.TTL_ORDER_QUEUE);
            //获取队列中的消息个数
            queueCount = declareOk.getMessageCount();
            System.out.println(queueCount);
            // 关闭通道和连接
            channel.close();
            connection.close();
        } catch (Exception e) {
            System.out.println("连接rabbitmq异常");
        }
        return queueCount;
    }

    // 设置消息的过期时间
    private MessagePostProcessor getMessagePostProcessor(String time) {
        return new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration(time);
                return message;
            }
        };
    }
}

死信消费者

package com.rabbitmq.consumer;

import com.rabbitmq.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;


@Component
public class ConsumerRabbitMQ  {
    
    @RabbitListener(queues = RabbitMQConfig.ORDER_QUEUE_DLX) // 监听死信队列
    public void myListener1(String message) {
        System.out.println("被消费:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        System.out.println("消息为:" + message);
    }
}

测试结果:

按照我的配置来说,当我启动SpringBoot的启动类,会在rabbitmq的控制台看到两个交换机,两个队列。
在这里插入图片描述
在这里插入图片描述

输入http://127.0.0.1:8080/lk/rabbitmq?routingKey=test.符合routingkey规则,就可以请求到接口,将消息写到过期队列中,5秒后被死信交换机分发到死信队列,然后被死信消费者消费,控制台打印如下:
在这里插入图片描述
有事我忙去了,还没写完,就先放这等我哪天有空了再做补充吧

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值