springboot集成RabbitMQ---延迟队列---TTL(队列)---死信队列

本文介绍了springboot集成RabbitMQ三种队列的使用方法,及代码案例展示

  1. 延迟队列
  2. TTL(队列)
  3. 死信队列

1. 延迟队列

要使用延迟队列,需要用到 x-delayed 插件,这里介绍Docker的安装插件方式

1.1 插件下载

Docker安装插件 : Docker安装RabbitMQ延时插件


1.2 延迟队列环境配置

延迟队列非常常用且好用,可以将消息发送后使消费者延迟接收

在这里插入图片描述

application.yml配置

server:
  port: 9001
spring:
  rabbitmq:
    host: 127.0.0.1
    virtual-host: /
    username: guest
    password: guest

RabbitAdmin配置

RabbitAdmin是用于对交换机和队列进行管理,用于创建、绑定、删除队列与交换机,发送消息的组件

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration //告知Spring这是配置类,启动就会加载
public class RabbitAdminConfig {

    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;
    @Value("${spring.rabbitmq.virtualhost}")
    private String virtualhost;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setAddresses(host);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualhost);
        return connectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }
}

封装发送延迟队列工具类

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@Component
public class DelayedQueue {
    // routingKey
    private static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
    // 延迟队列交换机
    private static final String DELAYED_EXCHANGE = "delayed.exchange";
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Resource
    RabbitAdmin rabbitAdmin;


    /**
     * 发送延迟队列
     *
     * @param queueName  队列名称
     * @param params     消息内容
     * @param expiration 延迟时间 毫秒
     */
    public void sendDelayedQueue(String queueName, Object params, Integer expiration) {
        // 创建队列
        Queue queue = new Queue(queueName);
        rabbitAdmin.declareQueue(queue);
        // 创建延迟队列交换机
        CustomExchange customExchange = createCustomExchange();
        rabbitAdmin.declareExchange(customExchange);
        // 将队列和交换机绑定
        Binding binding = BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
        rabbitAdmin.declareBinding(binding);
        // 发送延迟消息 (交换机名称,RoutingKey,参数)
        rabbitTemplate.convertAndSend(DELAYED_EXCHANGE, DELAYED_ROUTING_KEY, params, msg -> {
            // 发送消息的时候 延迟时长
            msg.getMessageProperties().setDelay(expiration);
            return msg;
        });
    }

    public CustomExchange createCustomExchange() {
        Map<String, Object> arguments = new HashMap<>();
        /**
         * 参数说明:
         * 1.交换机的名称
         * 2.交换机的类型
         * 3.是否需要持久化
         * 4.是否自动删除
         * 5.其它参数
         */
        // 参数固定写法 x-delayed-type 延迟插件名称 ,direct 直接模式
        arguments.put("x-delayed-type", "direct");
        // 创建交换机
        return new CustomExchange(DELAYED_EXCHANGE, "x-delayed-message", true, false, arguments);
    }
}

1.3 生产者/消费者

生产者

import com.sunmone.mq.utils.DelayedQueue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqTest {
  
    @Autowired
    private DelayedQueue delayedQueue;

    @Test
    public void testDelayed() {
        Date time = new Date();
        // 发送延迟消息 ,延迟时间单位为毫秒
        delayedQueue.sendDelayedQueue("delayTest", "发送延迟消息的时间: " + time, 5000);
    }
}

消费者

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component //  把消费者类加载到spring中 spring容器启动,那么就启动监听队列
public class DelayedExchangeListener {

    /*
     * 监听delayTest队列,持久化
     * */
    @RabbitListener(queuesToDeclare = @Queue(value = "delayTest", durable = "true"))
    public void DelayedListener(String message) {
        Date time = new Date();
        System.out.println(message);
        System.out.println("delayTest接受消息的时间: " + time );
    }
}

1.4 结果验证

可以看到队列延迟了5秒,测试成功

在这里插入图片描述

2. TTL队列

TTL是 time to live 的缩写,生存时间,RabbitMQ支持消息的过期时间,消息发送时可以指定,从消息入队列开始计算,只要超过队列的超时时间配置,消息没被接收,消息就会自动清除

2.1 封装TTL队列工具类

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@Component
public class TtlQueue {
    private static final String TTL_KEY = "ttl.routingkey";
    private static final String TTL_EXCHANGE = "ttl.exchange";
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Resource
    RabbitAdmin rabbitAdmin;

    /**
     * 创建TTL队列
     *
     * @param queueName  队列名称
     * @param expiration 过期时间 毫秒
     */
    public void creatTtlQueue(String queueName, Integer expiration) {
        /**
         * ----------------------------------先创建一个ttl队列--------------------------------------------
         */
        Map<String, Object> map = new HashMap<>();
        // 队列设置存活时间,单位ms,必须是整形数据
        map.put("x-message-ttl", expiration);
        /*
        参数1:队列名称
        参数2:持久化
        参数3:是否排他
        参数4:自动删除队列
        参数5:队列类型参数*/
        Queue queue = new Queue(queueName, true, false, false, map);
        // 声明队列
        rabbitAdmin.declareQueue(queue);
        /**
         * ---------------------------------创建交换机---------------------------------------------
         */
        // 创建交换机 名称 是否持久 是否自动删除
        DirectExchange directExchange = new DirectExchange(TTL_EXCHANGE, true, false);
        // 声明交换机
        rabbitAdmin.declareExchange(directExchange);
        /**
         * ---------------------------------队列绑定交换机---------------------------------------------
         */
        // 将队列和交换机绑定
        Binding binding = BindingBuilder.bind(queue).to(directExchange).with(TTL_KEY);
        rabbitAdmin.declareBinding(binding);
    }
}

生产者

import com.sunmone.mq.Application;
import com.sunmone.mq.utils.TtlQueue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqTest {

    // 注入 rabbitmq
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private TtlQueue ttlQueue;
    
    private static final String TTL_KEY = "ttl.routingkey";
    private static final String TTL_EXCHANGE = "ttl.exchange";

    @Test
    public void ttlTest() {
        // 创建TTL队列,队列中消息存活时间为5S
//        ttlQueue.creatTtlQueue("ttlQueue", 5000);
        // 发送消息
        rabbitTemplate.convertAndSend(TTL_EXCHANGE, TTL_KEY, "TTL队列生产消息... ");
    }
}

消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

//@Component
public class TtlListener {
    @RabbitListener(queues = "ttlQueue")
    public void ttlQueue(String message){
        System.out.println("TTL message = " + message);
    }
}

2.2 结果验证

首先注释掉消费者的 component 注解

启动Application启动类,执行 ttlTest 测试方法,创建队列并发送消息

在这里插入图片描述
可以看到TTL队列中产生了一条消息,并在5S后过期被销毁

在这里插入图片描述

2.3 单条消息设置TTL

创建队列绑定交换机

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TtlConfiguration {
    private static final String TTL_KEY = "ttl.oneKey";
    private static final String TTL_EXCHANGE = "ttl.exchange";

    // 1. 创建交换机 ,这里和上面的TTL队列共用一个交换机 ttl.exchange
    @Bean
    public DirectExchange getDirectExchange() {
        /** @params1 : 交换机名称
         *  @params2 : 是否持久化(是,重启后不会消失)
         *  @params3 : 是否自动删除(交换机无消息可投递,则自动删除交换机)
         */
        return new DirectExchange(TTL_EXCHANGE, true, false);
    }

    // 2.创建普通队列
    @Bean
    public Queue getTTL_Queue1() {
        /** @params1 : 队列名称
         *  @params2 : 是否持久化(true:重启后不会消失)
         *  @params3 : 是否独占队列(true:仅限于此连接使用)
         *  @params4 : 是否自动删除(队列内最后一条消息被消费后,队列将自动删除)
         */
        return new Queue("ttlQueue1", true, false, false);
    }

    // 3.绑定交换机与队列的关系,并设置交换机与队列之间的BindingKey
    @Bean
    public Binding getBinding_TTL() {
        // 投递消息时指定的RoutingKey与此BindingKey(ttl)匹配上,消息才会被投递到ttlQueue1队列
        return BindingBuilder.bind(getTTL_Queue1()).to(getDirectExchange()).with(TTL_KEY);
    }
}

生产者

import com.sunmone.mq.Application;
import com.sunmone.mq.utils.TtlQueue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqTest {

    // 注入 rabbitmq
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    private static final String TTL_KEY = "ttl.oneKey";
    private static final String TTL_EXCHANGE = "ttl.exchange";

    @Test
    public void ttl_1_Test() {
        MessagePostProcessor mp = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 设置消息格式为 utf-8
                message.getMessageProperties().setContentEncoding("utf-8");
                // 设置消息过期时间 单位毫秒
                message.getMessageProperties().setExpiration("20000");
                return message;
            }
        };
        rabbitTemplate.convertAndSend(TTL_EXCHANGE, TTL_KEY, "TTL队列单条信息设置过期时间... ",mp);
    }
}

消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

//@Component
public class TtlListener {
    @RabbitListener(queues = "ttlQueue1")
    public void ttlQueue(String message){
        System.out.println("TTL message = " + message);
    }
}

2.4 结果验证

启动Application,启动测试类 ttl_1_Test,消费者不要启动

在这里插入图片描述
队列中有一条消息,20秒后消失

在这里插入图片描述

3. 死信队列

死信:无法被消费方消费掉的消息,称为死信
如果死信一直留在队列中,会导致一直被消费,却从不消费成功。所以 rabbitmq 专门开辟了一个来存放死信的队列,叫死信队列(DLX,dead-letter-exchange)

3.1 死信从何而来

  1. 消息消费方调用了 basicNack()basicReject(),并且参数都是 requeue = false,则消息会路由进死信队列
  2. 消息过期,过了 TTL 存活时间,就是消费方在 TTL 时间之内没有消费,则消息会路由进死信队列
  3. 队列设置了 x-max-length 最大消息数量且当前队列中的消息已经达到了这个数量,再次投递,消息将被挤掉,被挤掉的消息会路由进死信队列

有一种场景需要注意下:消费者设置了自动 ACK,当重复投递次数达到了设置的最大 retry 次数之后,消息也会投递到死信队列,但是内部的原理还是调用了 basicNack()basicReject()

死信流程图

在这里插入图片描述

3.2 创建死信队列

这里使用 @Configuration 注解创建TTL队列和死信队列并绑定,交给Spring来管理

DLXConfiguration 配置类

package com.sunmone.mq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration // 配置类
public class DLXConfiguration {
    // 死信队列
    private static final String DEAD_EXCHANGE = "dead.exchange";
    private static final String DEAD_ROUTING_KEY = "dead.routingKey";
    private static final String dlxQueueName = "deadQueue";
    // ttl队列
    private static final String EXCHANGE = "common.exchange";
    private static final String ROUTING_KEY = "routingKey";
    private static final String ttlQueueName = "commonQueue";

    // 过期时间 毫秒
    private static final Integer expiration = 5000;

    /**
     * 创建TTL队列并绑定死信交换机
     */
    @Bean
    public Queue ttlQueue() {
        // 创建map,设置TTL队列参数
        Map<String, Object> map = new HashMap<>();
        // 队列设置存活时间,单位ms, x-message-ttl为固定写法
        map.put("x-message-ttl", expiration);
        // TTL队列绑定死信交换器,名称为 dead.exchange
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // TTL绑定死信交换器并设置routingKey为 dead.routingKey
        map.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        /**
         * 参数1:队列名称 commonQueue
         * 参数2:持久化 是
         * 参数3:是否排他 否
         * 参数4:自动删除队列 否
         * 参数5:队列参数*/
        return new Queue(ttlQueueName, true, false, false, map);
    }

    /**
     * 创建TTL交换机
     */
    @Bean
    public DirectExchange ttlExchange() {
        return new DirectExchange(EXCHANGE, true, false);
    }

    /**
     * 绑定TTL队列与交换机
     */
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with(ROUTING_KEY);
    }

    /**
     * 创建死信队列
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(dlxQueueName, true, false, false);
    }

    /**
     * 创建死信交换机
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(DEAD_EXCHANGE, true, false);
    }

    /**
     * 绑定死信队列与交换机
     */
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(DEAD_ROUTING_KEY);
    }

    /**
    * 创建连接
    * */
    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;
    @Value("${spring.rabbitmq.virtualhost}")
    private String virtualhost;
    /**
    * 创建连接
    * */
    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setAddresses(host);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualhost);
        return connectionFactory;
    }
    /**
    * 创建rabbitAdmin
    * */
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        // 声明ttl队列
        rabbitAdmin.declareQueue(ttlQueue());
        // 声明创建ttl交换机
        rabbitAdmin.declareExchange(ttlExchange());
        // 声明ttl队列与交换机绑定
        rabbitAdmin.declareBinding(ttlBinding());
        // 声明创建死信队列
        rabbitAdmin.declareQueue(dlxQueue());
        // 声明创建死信交换机
        rabbitAdmin.declareExchange(dlxExchange());
        // 声明死信队列与交换机绑定
        rabbitAdmin.declareBinding(dlxBinding());
        return rabbitAdmin;
    }

生产者

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqTest {
    // 注入 rabbitmq
    @Autowired
    private RabbitTemplate rabbitTemplate;
	@Test
    public void dlx_Test() {
        rabbitTemplate.convertAndSend("common.exchange","routingKey","向TTL队列发送消息...");
    }
}

消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class TtlListener {
    @RabbitListener(queues = "commonQueue")
    public void ttlQueue(String message){
        System.out.println("TTL message = " + message);
    }
}

3.3 结果验证

不需要启动 Application 启动类,执行生产者 dlx_Test ,观察管理控制台

此时 commonQueue 队列中产生一条消息

在这里插入图片描述

队列TTL为5S,过了5S没消费掉会进入死信队列 deadQueue

在这里插入图片描述

https://blog.csdn.net/qq_48721706/article/details/125194646
https://blog.csdn.net/weixin_38192427/article/details/120479192

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三胖哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值