这里以direct类型为例
application.yml
server:
port: 8000
#session-timeout: 1800
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rabbitmq</groupId>
<artifactId>confirm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>confirm</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--// 新加的jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.7.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
常量类:
public interface RabbitmqConstant {
String QUEUE_NAME = "QUEUE_DEMO_DIRECT";
String EXCHANGE_NAME = "direct_exchange";
String ROUTING_KEY = "ROUTING_KEY_01";
}
config类: 绑定队列,交换机和路由key
@Configuration
public class RabbitmqDirectConfig {
@Bean("bootDirectExchange")
public Exchange bootDirectExchange() {
return
ExchangeBuilder.directExchange(RabbitmqConstant.EXCHANGE_NAME).durable(true).build();
}
@Bean("bootDirectQueue")
public Queue bootDirectQueue() {
return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).build();
}
@Bean
public Binding bindDirectQueueExchange(@Qualifier("bootDirectQueue") Queue queue,
@Qualifier("bootDirectExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.ROUTING_KEY).noargs();
}
}
监听类:当该列队有消息时自动消费
@Slf4j
@Component
public class RabbitmqListener {
@RabbitListener(queues = RabbitmqConstant.QUEUE_NAME)
public void ListenerQueue01(Message message, Channel channel) {
System.out.println("mess====" + message.toString());
log.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
}
}
消息回调类:注意这一层是交换机有没有收到消息的回调
@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {
/**
* 交换机确认回调方法
* 1.交换机接收到了,回调
*
* @param correlationData 保存回调消息的ID及相关消息
* @param ack 交换机收到消息ack=true
* @param cause null
*
* 2.交换机接收失败,回调
* @param correlationData 保存回调消息的ID及相关消息
*@param ack 交换机没收到消息ack=false
*@param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack) {
log.error("消息发送异常!");
} else {
log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
}
}
}
回退类: 注意这里是当不可到达目的地的时候才回调,例如路由key错了,无法把消息传递给队列
@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {
/**
* 当消息传递过程中不可达目的地时将消息返回给生产者
* 只有不可达目的地的时候,才执行这里(是路由到队列那一层发生错误的时候执行这里)
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("到了回退方法");
log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
}
}
controller类:
@Slf4j
@RestController
@RequestMapping("/confirm")
public class ConfirmController {
@Resource
private RabbitTemplate rabbitTemplate;
@Autowired
private ConfirmCallbackService confirmCallbackService;
@Autowired
private ReturnCallbackService returnCallbackService;
@GetMapping("/sendConfirm/{msg}")
public void sendConfirmMessage(@PathVariable("msg")String msg){
/**声明回调的形参*/
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.setConfirmCallback(confirmCallbackService);
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String mess=msg+"=="+format.format(new Date());
rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME,
RabbitmqConstant.ROUTING_KEY,mess);
log.info("发送信息为:" + mess);
}
@GetMapping("/sendReturn/{msg}")
public void sendReturnMessage(@PathVariable("msg")String msg){
/**声明回调的形参*/
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(returnCallbackService);
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String mess=msg+"=="+format.format(new Date());
rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME, RabbitmqConstant.ROUTING_KEY+"222",
mess);
log.info("发送信息为:" + mess);
}
}
还可以把消息确认类和回退类写一起:如下
@Component
@Slf4j
public class MyCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
private void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
/**
* 交换机确认回调方法
* 1.交换机接收到了,回调
*
* @param correlationData 保存回调消息的ID及相关消息
* @param ack 交换机收到消息ack=true
* @param cause null
*
* 2.交换机接收失败,回调
* @param correlationData 保存回调消息的ID及相关消息
*@param ack 交换机没收到消息ack=false
*@param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack) {
log.error("消息发送异常!");
} else {
log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, 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) {
System.out.println("到了回退方法");
log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
}
}
controller类:
@Slf4j
@RestController
@RequestMapping("/confirm")
public class ConfirmController {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendConfirm/{msg}")
public void sendConfirmMessage(@PathVariable("msg")String msg){
/**声明回调的形参*/
CorrelationData correlationData = new CorrelationData("1");
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String mess=msg+"=="+format.format(new Date());
rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME,
RabbitmqConstant.ROUTING_KEY,
mess);
log.info("发送信息为:" + msg);
}
@GetMapping("/sendReturn/{msg}")
public void sendReturnMessage(@PathVariable("msg")String msg){
/**声明回调的形参*/
CorrelationData correlationData = new CorrelationData("1");
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String mess=msg+"=="+format.format(new Date());
rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME,
RabbitmqConstant.ROUTING_KEY+"222",
mess);
log.info("发送信息为:" + msg);
}
}
浏览器执行:http://127.0.0.1:8000/confirm/sendReturn/heheheeh222
http://127.0.0.1:8080/confirm/sendConfirm/heheheeh
注意:在springboot的测试类中不会执行回调和回退方法,要以这种接口请求方式
如果 在测试类就用内部类吧:
rabbitTemplate.setConfirmCallback((correlation, ack, cause) -> {
System.out.println("correlationData--->" + correlation);
System.out.println(ack);
if (ack) {
System.out.println("正常投递回复...");
//后续执行其他业务...
} else {
System.out.println("投递异常....");
//后续记录等操作...
}
});
消费ack机制:
场景:在消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,RabbitMQ 认为你消费了,这数据就丢了。这个时候得用 RabbitMQ 提供的 ack 机制,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢。
代码实现:
首先配置添加listener:
server:
port: 8000
#session-timeout: 1800
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制
# 设置消费端手动 ack
listener:
simple:
default-requeue-rejected: false #false不丢弃时需要写相应代码将该消息加入死信队列
acknowledge-mode: manual
# 是否支持重试
retry:
enabled: true
监听队列:
@RabbitListener(queues = RabbitmqConstant.QUEUE_NAME)
public void ListenerQueue01(String msg,Channel channel,Message message) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try{
System.out.println("收到消息=="+msg);
double a=1/0;
channel.basicAck(deliveryTag,false);
}catch (Exception e){
if(message.getMessageProperties().getRedelivered()){
log.error("消息已重复处理失败,拒绝再次接收。。");
channel.basicReject(deliveryTag,false);
}else{
// 第一个参数 deliveryTag:该消息的index
// 第二个参数 multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
// 第三个参数 requeue:true则重新入队列,否则丢弃或者进入死信队列
log.error("重新入队,发送到正常队列。。");
channel.basicNack(deliveryTag,true,true);
// log.error("不重新入队,发送到死信队列。。");
// channel.basicNack(deliveryTag,true,false);
}
}
}
------------- ----------------- 注意这里我遇到一个bug --------------------------------------
因为该队列和交换机和路由key 都是从之前的demo搬过来的,之前的工程的idea已经关了,但是我在rabbitmq控制台看到队列QUEUE_DEMO_DIRECT 有两个消费者,我一开始也没在意,直到我测试这个ack,重新入队列时没有调用,然后我测试如果消费的话会不会到死信队列,我把监听这个队列消费去掉了,还是不到死信队列,然后我注意到rabbitmq控制台这个队列还有一个消费者,我就意识到可能那个工程虽然idea关了但可能还在消费,所以我就把队列换了个名字,就OK了
如果是重新到正常队列,那么该方法会一直执行,如果是发送到死信队列,那该正常队列绑定的列信队列会收到消息。
正常队列绑定死信交换机实现:
public interface RabbitmqConstant {
String QUEUE_NAME = "QUEUE_DEMO_DIRECT_2";
String EXCHANGE_DIRECT_NAME = "direct_exchange_2";
String ROUTING_KEY = "ROUTING_KEY_02";
//死信交换机
String EXCHANGE_DEAD_NAME= "deadletter_exchange";
//死信队列
String QUEUEA_DEAD_NAME_A = "QUEUE_DEAD_A";
//死信路由
String DEAD_ROUTING_KEY_A = "dead_routingkey_a";
}
死信队列绑定:
@Configuration
public class RabbitmqDeadConfig {
// 声明死信Exchange
@Bean("deadLetterExchange")
public DirectExchange deadLetterExchange() {
return new DirectExchange(RabbitmqConstant.EXCHANGE_DEAD_NAME);
}
// 声明死信队列
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_A,true);
}
// 声明死信队列绑定关系
@Bean
public Binding deadLetterBindingA(@Qualifier("deadLetterQueue") Queue queue, @Qualifier("deadLetterExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_A);
}
正常队列和死信交换机和路由key的绑定:
@Bean("bootDirectQueue")
public Queue bootDirectQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_A);
/**设置过期时间*/
// args.put("x-message-ttl", 10000);
return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).withArguments(args).build();
}
监听死信队列:
//监听 死信队列A
@RabbitListener(queues = RabbitmqConstant.QUEUEA_DEAD_NAME_A)
public void ListenDeadA(Message message,Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date=format.format(new Date());
System.out.println("mess====" + message.toString());
log.info("[onMessage][线程编号:{} 消息内容:{},当前时间:{}]", Thread.currentThread().getId(), message,date);
channel.basicAck(deliveryTag,false);
}
消息成为死信的三种情况:
1.队列消息长度到达限制
2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新入列,requeue=false
3.原队列存在消息过期设置,消息到达设置时间未被消费
demo:
1.队列长度到达限制是否到了死信队列:设置x-max-length=5
@Bean("bootDirectQueue")
public Queue bootDirectQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_A);
/**设置过期时间*/
args.put("x-message-ttl", 10000);
args.put("x-max-length", 5);
return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).withArguments(args).build();
}
、、、、、、、 发现 问题 ,,,,,,,,,,
这个队列的设置从第一开始就要设置好,中途不能在改,不然会报错
所以我重新加了一个队列(在config里的代码如下):
//另外一个队列
@Bean("directExchange")
public Exchange directExchange(){
return ExchangeBuilder.directExchange(RabbitmqConstant.EXCHANGE_MAX_NAME).durable(true).build();
}
@Bean("directQueue")
public Queue directQueue(){
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
//RabbitmqConstant.DEAD_ROUTING_KEY_A之前已经绑定了,这里在绑定,超过了5个竟然不到该
//路由A绑定的队列A了,又重新加了一个DEAD_ROUTING_KEY_B,然后把队列手动删除在运行的
args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_B);
/**设置过期时间*/
args.put("x-message-ttl", 60000);
args.put("x-max-length", 5);
return QueueBuilder.durable(RabbitmqConstant.QUEUE_MAX_NAME).withArguments(args).build();
}
@Bean
public Binding bindDirectQueueExchange2(@Qualifier("directQueue") Queue queue, @Qualifier("directExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.ROUTING_MAX_NAME).noargs();
}
常量类RabbitmqConstant新加代码:
//死信交换机
String EXCHANGE_DEAD_NAME= "deadletter_exchange";
//死信队列
String QUEUEA_DEAD_NAME_A = "QUEUE_DEAD_A";
String QUEUEA_DEAD_NAME_B = "QUEUE_DEAD_B";
//死信路由
String DEAD_ROUTING_KEY_A = "dead_routingkey_a";
String DEAD_ROUTING_KEY_B = "dead_routingkey_b";
//新队列
String QUEUE_MAX_NAME="queue-maxlength";
String EXCHANGE_MAX_NAME="exchange-maxlength";
String ROUTING_MAX_NAME="routing-maxlength";
config代码如下:
// 声明死信Exchange
@Bean("deadLetterExchange")
public DirectExchange deadLetterExchange() {
return new DirectExchange(RabbitmqConstant.EXCHANGE_DEAD_NAME);
}
// 声明死信队列A
@Bean("deadLetterQueueA")
public Queue deadLetterQueueA() {
return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_A,true);
}
// 声明死信队列A绑定关系
@Bean
public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue,
@Qualifier("deadLetterExchange") DirectExchange exchange) {
return
BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_A);
}
//新加的队列和绑定,交换机还是用之前那个
// 声明死信队列B
@Bean("deadLetterQueueB")
public Queue deadLetterQueueB() {
return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_B,true);
}
// 声明死信队列B绑定关系
@Bean
public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue, @Qualifier("deadLetterExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_B);
}
测试如下发送了七条数据,queue-maxlength只有五条,另外两条到了死信队列:
2.消息过期没有被消费,消息会到死信队列,过了60秒后所有消息到了死信队列:
3. 监听队列然后拒收:发送了7条消息,死信队列里多了7条,因此成功了
//监听正常队列 queue-maxlength 然后拒收,测试是否到死信队列
@RabbitListener(queues = RabbitmqConstant.QUEUE_MAX_NAME)
public void ListenerQueue02(String msg,Channel channel,Message message) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("收到消息=="+msg);
channel.basicReject(deliveryTag,false);
log.error("不重新入队,发送到死信队列。。");
// channel.basicNack(deliveryTag,true,false);
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date=format.format(new Date());
System.out.println("mess====" + message.toString());
log.info("[onMessage][线程编号:{} 消息内容:{},当前时间:{}]", Thread.currentThread().getId(), message,date);
}
消费者限流:
1.确保ack机制为手动确认
2.listener-container配置属性 perfetch=1表示消费端每次从mq拉取一条消息来消费,直到手动确认消费完毕后,才会继续拉取下一条消息
代码实现: 先在配置类里加上prefetch=2 代表每次消费2条数据
server:
port: 8000
#session-timeout: 1800
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
#host: 192.168.19.128 # RabbitMQ 服务的地址
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
publisher-returns: true # 发送者开启 return 确认机制
publisher-confirm-type: correlated # 发送者开启 confirm 确认机制
# 设置消费端手动 ack
listener:
simple:
default-requeue-rejected: false #false不丢弃时需要写相应代码将该消息加入死信队列
acknowledge-mode: manual
# 是否支持重试
retry:
enabled: true
prefetch: 2 #每次消费者拉取二条消息进行消费
如下图,共发送7条消息,有2条待拉取,有5条待消费。
TTL特性:
1.当消息到达存活时间后,还没有被消费,会被自动清除
2.可以对消息设置过期时间,也可以对整个队列设置过期 时间
3.如果设置了消息的过期时间也设置了队列的过期时间,它以时间短的为准
4.对队列设置TTL,队列过期后,会将队列所有消息全部移除
5.对消息设置TTL,消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉),就是说如果设置过期的消息在队列的中间,那到达过期时间后,在控制后台不会减1,因为它不在顶端。还没有被移除掉
延迟队列:
1. 用TTL + 死信队列 实现,比如淘宝下了订单过24小时还没有支付就取消订单,库存回滚,对订单是否支付的队列设置24小时的过期时间,到了这个时间没有消费的话,到死信队列,死信队列做逻辑判断,如果没支付就回滚库存和取消订单
2.安装插件实现延时队列
2.1下载rabbitmq_delayed_message_exchange插件,下载前请确认自己的 RabbitMQ 版本,下载对应版本的插件。
官网地址 https://www.rabbitmq.com/community-plugins.htm
2.2 安装插件
#拿到CONTAINER ID
docker ps
#宿主机上传文件到容器
docker cp /Users/yz/Downloads/rabbitmq_delayed_message_exchange-3.8.0.ez 7bc42bc3bf07:/plugins
#进入容器
docker exec -it 7bc42bc3bf07 bash
#进入文件夹
cd plugins
#安装插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Enabling plugins on node rabbit@7bc42bc3bf07:
rabbitmq_delayed_message_exchange
The following plugins have been configured:
rabbitmq_delayed_message_exchange
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@7bc42bc3bf07...
Plugin configuration unchanged.
#退出
exit
#重启
docker restart 7bc42bc3bf07
容器启动成功之后,登录 RabbitMQ
的管理界面,找到Exchanges Tab
页。点击 add a new...
,在 Type
里面查看是否有x-delayed-message
选项,如果存在就代表插件安装成功。
代码实现:
// RabbitmqConstant 添加
String DELAYED_QUEUE_NAME = "delay.queue.demo.delay.queue";
String DELAYED_EXCHANGE_NAME = "delay.queue.demo.delay.exchange";
String DELAYED_ROUTING_KEY = "delay.queue.demo.delay.routingkey";
// RabbitMqConfig 添加
@Bean
public Queue immediateQueue() {
return new Queue(DELAYED_QUEUE_NAME);
}
@Bean
public CustomExchange customExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
@Bean
public Binding bindingNotify(@Qualifier("immediateQueue") Queue queue,
@Qualifier("customExchange") CustomExchange customExchange) {
return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
}
// 消息生产者
@GetMapping("delayMsg")
public void delayMsg2(String msg, Integer delayTime) {
log.info("当前时间:{},收到请求,msg:{},delayTime:{}", new Date(), msg, delayTime);
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, msg, a ->{
a.getMessageProperties().setDelay(delayTime);
return a;
});
}
@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveD(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("当前时间:{},延时队列收到消息:{}", new Date(), msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
这里和TTL方式有个很大的不同就是TTL存放消息在死信队列(delayqueue)里,二基于插件存放消息在延时交换机里(x-delayed-message exchange)。