文章目录
本文介绍了springboot集成RabbitMQ三种队列的使用方法,及代码案例展示
- 延迟队列
- TTL(队列)
- 死信队列
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 死信从何而来
- 消息消费方调用了
basicNack()
或basicReject()
,并且参数都是requeue = false
,则消息会路由进死信队列 - 消息过期,过了 TTL 存活时间,就是消费方在 TTL 时间之内没有消费,则消息会路由进死信队列
- 队列设置了
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