Springboot中使用RabbitMQ的延时队列的实例
一、Springboot中RabbitMQ的配置文件
PS:说明
-
设计思路(发布订阅模式为例):
- 定义一个交换机:my-dlx-exchange
- 定义一个队列:my-dlx-Queue(死信队列)
- 把这个队列和交换机绑定到一起,并定义好路由key,此时该队列能够接收交换机的消息
- 定义一个队列:delayQueue(延时队列),并指定他关联的交换机、路由key
- ps:延时队列的消息过期后,会由关联的交换机,根据路由key,将消息发送到绑定的私信队列中。
-
1、定义一个队列作为延时队列,
//定义延时队列
@Bean("delayQueue")
public Queue delayQueue(){
//使用QueueBuilder构建完整队列,durable:持久化方法,参数:队列名称
return QueueBuilder.durable("delayQueue")
//1、设置死信交换机:如果消息过时,消息会被投递到当前对应的my-dlx-exchange
.withArgument("x-dead-letter-exchange","my-dlx-exchange")
//2、设置交换机的路由key:交换机(my-dlx-exchange)会根据路由key(routing-key-delay)投递消息到对应私信队列
.withArgument("x-dead-letter-routing-key","routing-key-delay").build();
}
- 2、定义一个队列作为死信队列,用于存放过期的消息
//定义死信队列
@Bean("dlxQueue")
public Queue dlxQueue(){
return QueueBuilder.durable("my-dlx-Queue").build();
}
- 3、定义交换机、绑定私信队列、设置好路由key
- 普通的发布订阅模式可以不设置路由key
- 路由模式需要设置路由key,实现根据key订阅指定队列
- 通配符模式交换机设置为topicExchange即可
- DIRECT:定向(使用路由key指定私信队列)
- FANOUT:扇形,发送消息到每一个私信队列
- TOPIC: 通配符方式
- HEADERS:参数匹配方
//定义死信交换机
@Bean("dlxExchange")
public Exchange dlxExchange(){
/**
* DIRECT:定向(使用路由key指定私信队列)
* FANOUT:扇形,发送消息到每一个私信队列
* TOPIC: 通配符方式
* HEADERS:参数匹配方式
* */
return ExchangeBuilder.directExchange("my-dlx-exchange").build();
}
//路由:绑定死信交换机与死信队列,并设置路由key
@Bean("dlxBinding")
public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, @Qualifier("dlxQueue") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("routing-key-delay")
.noargs();
// noargs();是不需要指定参数
}
- 5、完整的配置文件
package com.zydl.yjalarmapi.common.RabbitMQ;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//@Configuration
public class RabbitMqConfig {
//定义延时队列
@Bean("delayQueue")
public Queue delayQueue(){
//设置死信交换机及路由key
return QueueBuilder.durable("delayQueue")
//如果消息过时,则会被投递到当前对应的my-dlx-exchange
.withArgument("x-dead-letter-exchange","my-dlx-exchange")
//如果消息过时,my-dlx-exchange会根据routing-key-delay投递消息到对应队列
.withArgument("x-dead-letter-routing-key","routing-key-delay").build();
}
//定义死信队列
@Bean("dlxQueue")
public Queue dlxQueue(){
return QueueBuilder.durable("my-dlx-Queue").build();
}
//定义死信交换机
@Bean("dlxExchange")
public Exchange dlxExchange(){
/**
* DIRECT:定向(使用路由key指定私信队列)
* FANOUT:扇形,发送消息到每一个私信队列
* TOPIC: 通配符方式
* HEADERS:参数匹配方式
* */
return ExchangeBuilder.directExchange("my-dlx-exchange").build();
}
//路由:绑定死信交换机与死信队列
@Bean("dlxBinding")
public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, @Qualifier("dlxQueue") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("routing-key-delay")
.noargs();
/*
* noargs();是不需要指定参数
* */
}
}
二、定义生产者
- 配置文件中添加配置
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
- 生产者
package com.zydl.yjalarmapi.common.RabbitMQ;
import com.zydl.yjalarmapi.entity.Approvalprocess;
import com.zydl.yjalarmapi.entity.Eventreport;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class RabbitProducter {
@Autowired
private RabbitTemplate rabbitTemplate;
/*
* 1、这个方法的作用就是将消息添加到延时队列中
* 2、等消息过期后会由交换机:将消息推送到死信队列中
* 3、监听器会监听死信队列中的消息并进行处理
* */
public void Producter(Map<String,Object> map) {
/**
* 参数1:延时队列名称
* 参数2:发送的消息(map)
* 参数3:消息处理器(MessagePostProcessor):如果不需要设置延时时间,第三个参数省略即可。
* */
rabbitTemplate.convertAndSend("delayQueue", map, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//消息后处理对象MessagePostProcessor 来设置过期时间
//设置消息过期时间
message.getMessageProperties().setExpiration("10000");
return message;
}
});
}
}
三、定义消费者监听器
PS:说明
-
1、普通模式
- 生产者生产消息:rabbitTemplate.convertAndSend(“my_queue”, message),
- 消费者直接绑定到生产者发消息的队列中即可:@RabbitListener(queues = “my_queue”)
-
2、延时队列
- 生产者生成消息,设定好过期时间,消息过期后会被交换机发送到死信队列
-
消费者监听死信队列即可。
-
配置文件中添加配置
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
- 普通消费者
@Component
//绑定私信队列
@RabbitListener(queues = "my-dlx-Queue")
public class Messgelistener {
@RabbitHandler
public void recevice(Map<String,Object> map){
// 业务逻辑
}
}
-
Work模型:多个消费者
- 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量
@Component
public class Messgelistener {
//绑定死信队列
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(20);
}
//绑定死信队列
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(200);
}
}
MQ的可靠性机制:confirm、return、ACK
- 消息投递可靠性:confirm、return模式:
- confirm模式:生产者发送消息到交换机的时机上使用
- return模式:交换机转发给queue的时机上使用
一、confirm示例:
- 起步依赖
<!--rabbitmq起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 配置文件
spring:
rabbitmq:
port: 5672
host: localhost
username: guest
password: guest
virtual-host: /
#开启confirms这个模式
#springboot2.2.0.RELEASE支持这个
#publisher-confirm-type: correlated
publisher-confirms: true
- 实现ConfirmCallback接口,回调函数
package com.example.confirm;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
/**
* 回调方法,发送消息后都会调用该方法
* @param correlationData 数据
* @param ack true发送成功 ,false发送失败
* @param cause 如果是失败,那么返回这个失败原因的字符串。如果成功 原因就是null
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
//模拟在这里减100
System.out.println("发送成功,减100成功");
}else{
System.out.println("发送失败,失败原因是:"+cause);
}
}
}
- 创建队列、交换机、绑定队列与交换机
//省略
- 创建生产者
package com.example.controller;
import com.example.confirm.MyConfirmCallBack;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/testsend")
public class TestSendController {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
//或者用这个
//private MyConfirmCallBack myConfirmCallBack;
private RabbitTemplate.ConfirmCallback confirmCallback;
@GetMapping ("/send1")
public String send1(){
//设置回调函数-------绑定确认回调方法
rabbitTemplate.setConfirmCallback(confirmCallback);
//发送消息
rabbitTemplate.convertAndSend("exchange_test6","test6.insert","消息本身");
return "ok";
}
}
二、return模式
- 起步依赖
- 配置文件
spring:
rabbitmq:
port: 5672
host: localhost
username: guest
password: guest
virtual-host: /
#开启confirms这个模式
#springboot2.2.0.RELEASE支持这个
#publisher-confirm-type: correlated
publisher-confirms: true
#开启return模式
publisher-returns: true
- 实现ReturnCallback,回调方法
package com.example.confirm;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyReturnCallBack implements RabbitTemplate.ReturnCallback {
/**
*
* 交换机转发消息到队列时调用 , 如果成功的时候就不调用该方法了,只有转发失败的时候才调用
* @param message 消息内容本身
* @param replyCode 响应状态码
* @param replyText 响应内容
* @param exchange 交换机
* @param routingKey routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
//获取到消息本身
System.out.println(new String(message.getBody()));
//message.getMessageProperties()可以获取到其他的别的信息
//状态码
System.out.println(replyCode);
System.out.println(replyText);
//exchange
System.out.println(exchange);
//routingKey
System.out.println(routingKey);
}
}
- 定义队列、交换机、绑定队列与交换机
//省略
- 实现生产者测试
package com.example.controller;
import com.example.confirm.MyConfirmCallBack;
import com.example.confirm.MyReturnCallBack;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/testsend")
public class TestSendController {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
//或者用这个:confirm回调函数
//private MyConfirmCallBack myConfirmCallBack;
private RabbitTemplate.ConfirmCallback confirmCallback;
@Autowired
//private MyReturnCallBack myReturnCallBack;
//或者用这个:return回调函数
private RabbitTemplate.ReturnCallback returnsCallback;
@GetMapping ("/send1")
public String send1(){
//设置confirm回调函数
rabbitTemplate.setConfirmCallback(confirmCallback);
//设置return回调函数
rabbitTemplate.setReturnCallback(returnsCallback);
//发送消息
rabbitTemplate.convertAndSend("exchange_test6","test6.insert","消息本身");
return "ok";
}
}
-
消息接收可靠性:ACK有三种方式
- 自动确认:acknowledge=“none”
- 手动确认 acknowledge=“manual”
- 根据异常情况来确认(暂时不怎么用) acknowledge=“auto”
三、ACK手动确认示例
- 配置文件
spring:
rabbitmq:
port: 5672
host: localhost
username: guest
password: guest
virtual-host: /
#开启confirms这个模式
#springboot2.2.0.RELEASE支持这个
#publisher-confirm-type: correlated
publisher-confirms: true
#开启return模式
publisher-returns: true
listener:
direct:
#开启手动签收
acknowledge-mode: manual
- 测试
package com.example.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
@RabbitListener(queues = "queue_test6")
public class MyRabbitListener {
/**
*
* @param message 消息封装的对象,(包括了消息的序号,消息本身,消费者名称等)
* @param channel 链接的通道
* @param msg 消息本身
*/
@RabbitHandler //用于处理具体类型的消息,会自动把消息转换成对应的对象
public void receiveMessage(Message message, Channel channel,String msg){
//接收消息
System.out.println(msg);
MessageProperties messageProperties = message.getMessageProperties();
try {
//模拟业务-100
System.out.println("消费后减100元");
//模拟出问题
int i = 10/0;
//如果正常就签收消息
//参数1,消息的序号
//参数二,是否批量签收 true是批量签收
channel.basicAck(messageProperties.getDeliveryTag(),true);
} catch (Exception e) {
e.printStackTrace();
//不正常就拒收消息(丢弃了)
try {
//如果该消息重回过队列就不投递了,避免死循环
if(messageProperties.getRedelivered()){
System.out.println("已经重新投递过一次了");
}else{
//****三种方式,用一种即可************
//同时支持拒绝多个消息,可以拒绝该消费者先前接收未ack的所有消息。拒绝后的消息也会被自己消费到。
//参数1 消息序号
//参数2 是否批量拒绝消息
//参数3 是否把消息重新回到队列中:requeue
channel.basicNack(messageProperties.getDeliveryTag(),true,true);
//basicReject处理拒绝消息。(不支持批量拒绝)
//参数1 消息序号
//参数2 true:重新放回队列,false:否则丢弃或者进入死信队列。
//该方法reject后,该消费者还是会消费到该条被reject的消息。
channel.basicReject(deliveryTag, true);
//basicRecover是否恢复消息到队列,
//参数1 true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。
// false则消息会重新被投递给自己
channel.basicRecover(true);
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}