RabbitMq介绍
Spring boot 相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Spring boot的yml配置
server:
port: 8080
spring:
application:
name: rabbitmq-provider
rabbitmq:
host: ip地址
port: 5672 #注意这个是Java访问rabbit的端口与后台管理的端口不一样
username:
password:
virtual-host: /
virtual-host:每一个Rabbit都可以创建很多歌 vhost ,就相当于虚拟主机一样,下面都是一个独立的迷你版的RabbitMq.
Direct类型交换机
Direct类型
Direct类型的Exchange路由规则很简单,它会把消息路由到哪些binding key与routing key完全匹配的Queue中。最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取 轮询的方式进行消息发送。
创建一个Direct消息配置类
package com.config.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:
* @Author:GJM
* @Date:2023/3/2 15:32
*/
@Configuration
public class DirectRabbitConfig {
// 创建一个队列
@Bean
public Queue TestDirectQueue(){
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认fals
return new Queue("TestDirectQueue",true);
}
@Bean
public Queue TestDirectQueue2(){
return new Queue("hellorabbitMq",false);
}
/*
* 创建一个Direct交换机
* @Author:Gjm
* @Date :2023/3/2 15:39
* @return
*/
@Bean
public DirectExchange TestDirectExchange(){
return new DirectExchange("TestDirectExchange",true,false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
@Bean
Binding queuebindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
}
service实现发送消息
package com.config.service;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
/**
* @Description:发送消息服务类
* @Author:GJM
* @Date:2023/3/2 15:44
*/
@Service
public class SendMessageService {
@Autowired
public RabbitTemplate rabbitTemplate;
public boolean sendMessage(String messageId, String message, String createDate,String routingKey,String exchange){
HashMap<Object, Object> params = new HashMap<>();
params.put("messageId",messageId);
params.put("message",message);
params.put("createDate",createDate);
// 发送到哪个交换机,将数据与routingKey绑定
try {
rabbitTemplate.convertAndSend(exchange,routingKey,params);
}catch (Exception e){
e.printStackTrace();
}
return true;
}
}
创建监听Direct消息队列消费
package com.config.ConsumerListener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Description:消费者监听消息
* @Author:GJM
* @Date:2023/3/2 17:01
*/
@Component
public class ConsumerListener {
@RabbitListener(queues = "TestDirectQueue")
@RabbitHandler
public void ConsumerMessage(Map testMessage){
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
}
}
Controller接口
package com.config.Controller;
import com.config.service.SendMessageService;
import org.springframework.amqp.core.Correlation;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.stream.Stream;
/**
* @Description:
* @Author:GJM
* @Date:2023/3/2 15:53
*/
@RestController
public class RabbitDirectSendController {
@Autowired
private SendMessageService sendMessageService;
@PostMapping("/send")
public void sendRabbitMessage(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条消息",
createTime,"TestDirectRouting","TestDirectExchange");
}
}
主程序启动成功之后,Rabbit后台管理系统会创建一个交换机和消息队列
postman 进行测试
测试结果
ConsumerListener监听打印的消息体
Topic类型交换机
该类型的交换器将所有发送到Topic Exchange的消息被转发到所有RoutingKey中指定的Topic的队列上面。
Exchange将RoutingKey和某Topic进行模糊匹配,其中“”用来匹配一个词,“#”用于匹配一个或者多个词。例如“com.#”能匹配到“com.rabbitmq.oa”和“com.rabbitmq”;而"login."只能匹配到“com.rabbitmq”。
创建Topic消息配置类
package com.config.Topic.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:主题交换机
* @Author:GJM
* @Date:2023/3/15 16:26
*/
@Configuration
public class TopicRabbitConfig {
public final static String TQ1 = "TopicQueue.A";
public final static String TQ2 = "TopicQueue.B";
public final static String TE = "TopicExchange";
// TQ1私有的绑定key
public final static String TR = "topic.A";
// Tq1 /Tq2 共有的绑定key,#号匹配所有前缀相同的
public final static String TRP = "topic.#";
@Bean
public Queue Tq1(){
return new Queue(TQ1);
}
@Bean
public Queue Tq2(){
return new Queue(TQ2);
}
@Bean
public TopicExchange Te(){
return new TopicExchange(TE,true,false);
}
/*
* 队列与交换机绑定,设置绑定key
* @Author:Gjm
* @Date :2023/3/15 17:07
* @return
*/
@Bean
public Binding bindingTqe(){
return BindingBuilder.bind(Tq1()).to(Te()).with(TR);
}
/*
* 队列与交换机绑定,设置绑定key
* 只要是topic.开头的rontingKey,这个队列都能接收到消息
* @Author:Gjm
* @Date :2023/3/15 17:07
* @return
*/
@Bean
public Binding bindingTqe2(){
return BindingBuilder.bind(Tq2()).to(Te()).with(TRP);
}
}
创建监听Topic消息队列消费者
package com.config.ConsumerListener;
import com.config.Topic.config.TopicRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Description:监听Topic消息队列并消费
* @Author:GJM
* @Date:2023/3/15 17:14
*/
@Component
public class TopicConsumerListener {
@RabbitListener(queues = TopicRabbitConfig.TQ1)
@RabbitHandler
public void getTq1(Map message){
System.out.println("这是TQ1的消息:" + message.toString());
}
@RabbitListener(queues = TopicRabbitConfig.TQ2)
@RabbitHandler
public void getTq2(Map message){
System.out.println("这是TQ2的消息:" + message.toString());
}
}
Controller接口
这里的SendMessageService类在Direct下已经创建
package com.config.Controller;
import com.config.service.SendMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* @Description:Topic发送消息
* @Author:GJM
* @Date:2023/3/15 17:19
*/
@RestController
@RequestMapping("/topic")
public class RabbitTopicSendController {
@Autowired
private SendMessageService sendMessageService;
@GetMapping("/send1")
public void sendMessage1(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
createTime,"topic.A","TopicExchange");
System.out.println("发送成功");
}
@GetMapping("/send2")
public void sendMessage2(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
createTime,"topic.EEE","TopicExchange");
System.out.println("发送成功");
}
}
postman 进行测试
调用localhost:8080/topic/send1 接口
预期结果应该是TQ1、TQ2都能收到消息。符合预期
调用localhost:8080/topic/send2 接口
预期结果应该是TQ2能收到消息。符合预期
Fanout类型交换机
扇形类型不处理路由键,会把所有发送到交换器的消息路由到所有绑定的队列中。优点是转发消息最快,性能最好。
创建Fanout消息配置类
package com.config.Fanout.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:扇形交换机
* @Author:GJM
* @Date:2023/3/15 18:27
*/
@Configuration
public class FanoutRabbitConfig {
// 定义3个消息队列
public final static String FQ1 = "FanoutQueue.A";
public final static String FQ2 = "FanoutQueue.B";
public final static String FQ3 = "FanoutQueue.C";
// 定义一个交换机
public final static String FE = "FanoutExchange";
@Bean
public Queue fq1(){
return new Queue(FQ1,true);
}
@Bean
public Queue fq2(){
return new Queue(FQ2,true);
}
@Bean
public Queue fq3(){
return new Queue(FQ3,true);
}
@Bean
public FanoutExchange fe(){
return new FanoutExchange(FE,true,false);
}
/*
* 将三个消息队列绑定到一个交换机上,不用设置RoutingKey
* @Author:Gjm
* @Date :2023/3/15 18:35
* @return
*/
@Bean
public Binding bindingAE(){
return BindingBuilder.bind(fq1()).to(fe());
}
@Bean
public Binding bindingBE(){
return BindingBuilder.bind(fq2()).to(fe());
}
@Bean
public Binding bindingCE(){
return BindingBuilder.bind(fq3()).to(fe());
}
}
创建监听Fanout消息队列的消费者
package com.config.ConsumerListener;
import com.config.Fanout.config.FanoutRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @Description:监听Fanout消息队列并消费
* @Author:GJM
* @Date:2023/3/15 18:36
*/
@Configuration
public class FanoutConsumerListener {
@RabbitListener(queues = FanoutRabbitConfig.FQ1)
@RabbitHandler
public void getMessageq1(Map message){
System.out.println("这是FQ1的消息: " + message.toString());
}
@RabbitListener(queues = FanoutRabbitConfig.FQ2)
@RabbitHandler
public void getMessageq2(Map message){
System.out.println("这是FQ2的消息: " + message.toString());
}
@RabbitListener(queues = FanoutRabbitConfig.FQ3)
@RabbitHandler
public void getMessageq3(Map message){
System.out.println("这是FQ3的消息: " + message.toString());
}
}
Controller接口
package com.config.Controller;
import com.config.service.SendMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* @Description:Fanout发送消息
* @Author:GJM
* @Date:2023/3/15 17:19
*/
@RestController
@RequestMapping("/fanout")
public class RabbitFanoutSendController {
@Autowired
private SendMessageService sendMessageService;
@GetMapping("/send1")
public void sendMessage1(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
createTime,"","FanoutExchange");
System.out.println("发送成功");
}
}
postman进行测试
调用localhost:8080/fanout/send1 接口
预期结果应该是3个消息队列都能收到消息。符合预期
创建一个Direct延时队列
延时队列的用处有很多,订单关闭,当达到过期时间才会监听到消息,从而实现业务逻辑。
创建延时消息配置类
package com.config.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import static com.config.config.TtlRabbitConfig.QUEUE_B;
/**
* @Description:
* @Author:GJM
* @Date:2023/3/3 15:19
*/
@Configuration
public class MyTtlQueueConfig {
//死信队列配置
public static final String QUEUE_G = "AG";
public static final String EXCHANGE_G = "G";
//声明QG死信队列
@Bean
public Queue queueG() {
return new Queue(QUEUE_G, true);
}
// 声明G交换机
@Bean
public DirectExchange exchangeG() {
return new DirectExchange(EXCHANGE_G);
}
//死信QG与死信交换机G绑定
@Bean
public Binding queueGBindChangeG() {
return BindingBuilder.bind(queueG()).to(exchangeG()).with("AEG");
}
//-----普通队列配置
public static final String QUEUE_AE = "AE";
public static final String EXCHANGE_E = "E";
// 声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间
@Bean
public Queue queueAE(){
HashMap<String, Object> arguments = new HashMap<>(3);
arguments.put("x-dead-letter-exchange", EXCHANGE_G);
// 设置死信路由键
arguments.put("x-dead-letter-routing-key", "AEG");
// 设置过期时间
arguments.put("x-message-ttl", 20000);
return new Queue(QUEUE_AE,true,false,false,arguments);
}
//灵活设置过期时间
@Bean
public DirectExchange exchangeE(){
return new DirectExchange(EXCHANGE_E,true,false);
}
@Bean
public Binding queueAEBingExchangeE(){
return BindingBuilder.bind(queueAE()).to(exchangeE()).with("AEE");
}
}
注意设置死信路由键必须与 死信和死信交换机绑定的路由键一致,不然死信消息队列无法获取消息
arguments.put("x-dead-letter-routing-key", "AEG");
监听消息
package com.config.ConsumerListener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Description:消费者监听消息
* @Author:GJM
* @Date:2023/3/2 17:01
*/
@Component
public class ConsumerListener {
@RabbitListener(queues = "TestDirectQueue")
@RabbitHandler
public void ConsumerMessage(Map testMessage){
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
}
@RabbitListener(queues = "AG")
@RabbitHandler
public void ConsumerTtlAGMessage(Message message){
System.out.println("AG队列的死信消息 :" + message);
}
}
Controller 接口
package com.config.Controller;
import com.config.service.SendMessageService;
import org.springframework.amqp.core.Correlation;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* @Description:
* @Author:GJM
* @Date:2023/3/2 15:53
*/
@RestController
public class RabbitDirectSendController {
@Autowired
private SendMessageService sendMessageService;
@PostMapping("/send")
public void sendRabbitMessage(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条消息",
createTime,"TestDirectRouting","TestDirectExchange");
}
@PostMapping("/sendDieMessage")
public void sendRabbitDieMessage3(){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条死信消息",
createTime,"AEE","E");
System.out.println("发送成功");
}
}
注意
如果要是改变原有队列的属性,会导致启动时报错,Java设置的属性与MQ已存在的属性不一致错误。需要在后台管理系统中删除。
postman进行接口测试
测试结果
20后收到一条消息,测试成功。
优化延时队列,实现自定义过期时间
MyTtlQueueConfig.class声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间改为手动配置
package com.config.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import static com.config.config.TtlRabbitConfig.QUEUE_B;
/**
* @Description:
* @Author:GJM
* @Date:2023/3/3 15:19
*/
@Configuration
public class MyTtlQueueConfig {
//死信队列配置
public static final String QUEUE_G = "AG";
public static final String EXCHANGE_G = "G";
//声明QG死信队列
@Bean
public Queue queueG() {
return new Queue(QUEUE_G, true);
}
// 声明G交换机
@Bean
public DirectExchange exchangeG() {
return new DirectExchange(EXCHANGE_G);
}
//死信QG与死信交换机G绑定
@Bean
public Binding queueGBindChangeG() {
return BindingBuilder.bind(queueG()).to(exchangeG()).with("AEG");
}
//-----普通队列配置
public static final String QUEUE_AE = "AE";
public static final String EXCHANGE_E = "E";
// 声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间
/*@Bean
public Queue queueAE(){
HashMap<String, Object> arguments = new HashMap<>(3);
arguments.put("x-dead-letter-exchange", EXCHANGE_G);
// 设置死信路由键
arguments.put("x-dead-letter-routing-key", "AEG");
// 设置过期时间
arguments.put("x-message-ttl", 20000);
return new Queue(QUEUE_AE,true,false,false,arguments);
}*/
//灵活设置过期时间
@Bean
public Queue queueAE(){
HashMap<String, Object> arguments = new HashMap<>(3);
arguments.put("x-dead-letter-exchange", EXCHANGE_G);
// 设置死信路由键
arguments.put("x-dead-letter-routing-key", "AEG");
return new Queue(QUEUE_AE,true,false,false,arguments);
}
@Bean
public DirectExchange exchangeE(){
return new DirectExchange(EXCHANGE_E,true,false);
}
@Bean
public Binding queueAEBingExchangeE(){
return BindingBuilder.bind(queueAE()).to(exchangeE()).with("AEE");
}
}
Service实现发送接口增加一种发送类型
package com.config.service;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
/**
* @Description:发送消息服务类
* @Author:GJM
* @Date:2023/3/2 15:44
*/
@Service
public class SendMessageService {
@Autowired
public RabbitTemplate rabbitTemplate;
public boolean sendMessage(String messageId, String message, String createDate,String routingKey,String exchange){
HashMap<Object, Object> params = new HashMap<>();
params.put("messageId",messageId);
params.put("message",message);
params.put("createDate",createDate);
// 发送到哪个交换机,将数据与routingKey绑定
try {
rabbitTemplate.convertAndSend(exchange,routingKey,params);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
public boolean sendMessage(String messageId, String message, String createDate, String routingKey, String exchange, MessagePostProcessor messagePostProcessor){
HashMap<Object, Object> params = new HashMap<>();
params.put("messageId",messageId);
params.put("message",message);
params.put("createDate",createDate);
// 发送到哪个交换机,将数据与routingKey绑定
try {
rabbitTemplate.convertAndSend(exchange,routingKey,params,messagePostProcessor);
}catch (Exception e){
e.printStackTrace();
}
return true;
}
}
Controller进行代码优化,自定义设置过期时间
// 优化自己设置过期时间灵活
@PostMapping("/sendTtl")
public void sendRabbitDieMessage4(@Param("ttl") String ttl,@Param("messages") String messages){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),messages,
createTime,"AEE","E" ,(message)->{
message.getMessageProperties().setExpiration(ttl);
return message;
});
System.out.println("发送成功");
}
postmen测试即可。
RabbitMq延时队列的缺点
如果同事设置2条消息,第一条设置30s,第二条设置10s,na那么则会在30s后获取到两天条数据。
因为myq只会检查第一条消息的过期时间。
localhost:8080/sendDieMessage?messages=这条消息过期时间是20s&ttl=20000
localhost:8080/sendDieMessage?messages=这条消息过期时间是10s&ttl=10000
测试结果:发现20s,10s的一起消费啦。
使用x-delayed-message插件弥补缺点
将插件上传到服务器/usr/lib/rabbitmq/lib/rabbitmq_server-3.10.0/plugins目录下。
运行rabbitmq-plugins list,可以查看当前rabbitMq的插件列表。
root@VM-4-4-centos plugins]# rabbitmq-plugins list
Listing plugins with pattern ".*" ...
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@VM-4-4-centos
|/
[ ] rabbitmq_amqp1_0 3.10.0
[ ] rabbitmq_auth_backend_cache 3.10.0
[ ] rabbitmq_auth_backend_http 3.10.0
[ ] rabbitmq_auth_backend_ldap 3.10.0
[ ] rabbitmq_auth_backend_oauth2 3.10.0
[ ] rabbitmq_auth_mechanism_ssl 3.10.0
[ ] rabbitmq_consistent_hash_exchange 3.10.0
[ ] rabbitmq_delayed_message_exchange 3.10.2
[ ] rabbitmq_event_exchange 3.10.0
[ ] rabbitmq_federation 3.10.0
[ ] rabbitmq_federation_management 3.10.0
[ ] rabbitmq_jms_topic_exchange 3.10.0
[E*] rabbitmq_management 3.10.0
[e*] rabbitmq_management_agent 3.10.0
[ ] rabbitmq_mqtt 3.10.0
[ ] rabbitmq_peer_discovery_aws 3.10.0
[ ] rabbitmq_peer_discovery_common 3.10.0
[ ] rabbitmq_peer_discovery_consul 3.10.0
[ ] rabbitmq_peer_discovery_etcd 3.10.0
[ ] rabbitmq_peer_discovery_k8s 3.10.0
[ ] rabbitmq_prometheus 3.10.0
[ ] rabbitmq_random_exchange 3.10.0
[ ] rabbitmq_recent_history_exchange 3.10.0
[ ] rabbitmq_sharding 3.10.0
[ ] rabbitmq_shovel 3.10.0
[ ] rabbitmq_shovel_management 3.10.0
[ ] rabbitmq_stomp 3.10.0
[ ] rabbitmq_stream 3.10.0
[ ] rabbitmq_stream_management 3.10.0
[ ] rabbitmq_top 3.10.0
[ ] rabbitmq_tracing 3.10.0
[ ] rabbitmq_trust_store 3.10.0
[e*] rabbitmq_web_dispatch 3.10.0
[ ] rabbitmq_web_mqtt 3.10.0
[ ] rabbitmq_web_mqtt_examples 3.10.0
[ ] rabbitmq_web_stomp 3.10.0
[ ] rabbitmq_web_stomp_examples 3.10.0
[root@VM-4-4-centos plugins]#
启动插件rabbitmq-plugins enable rabbitmq_delayed_message_exchange
[root@VM-4-4-centos plugins]# rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Enabling plugins on node rabbit@VM-4-4-centos:
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@VM-4-4-centos...
The following plugins have been enabled:
rabbitmq_delayed_message_exchange
started 1 plugins.
[root@VM-4-4-centos plugins]#
在RabbitMq的后台页面可以看到插件,说明成功啦。升级RabbitMQ时,必须冲新安装该插件,也就是要安装它们的新版本;或者,可以在升级之前或升级期间禁用它们。
下面是Java代码实现,
定义交换机,队列
package com.config.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;
/**
* @Description:引用RabbitMq的rabbitmq_delayed_message_exchange插件实现延时队列
* @Author:GJM
* @Date:2023/3/9 18:45
*/
@Configuration
public class MyTtlQueueConfig2 {
// 延时队列名称
public static final String DELAYED_QUEUE_NAME = "delayed.queue";
// 延时队列交换机
public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
// 延时路由
public static final String DELAYED_ROUTING_KEY = "delayed.routingKey";
@Bean
public Queue delayedQueueA(){
return new Queue(DELAYED_QUEUE_NAME,true);
}
/**
* 自定义交换机 定义一个延迟交换机
* 不需要死信交换机和死信队列,支持消息延迟投递,消息投递之后没有到达投递时间,是不会投递给队列
* 而是存储在一个分布式表,当投递时间到达,才会投递到目标队列
* @return
*/
@Bean
public CustomExchange delayedExchangeA(){
Map<String, Object> args = new HashMap<>(1);
// 自定义交换机的类型
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
@Bean
public Binding delayedQueueBindExchange(){
return BindingBuilder.bind(delayedQueueA()).to(delayedExchangeA()).with(DELAYED_ROUTING_KEY).noargs();
}
}
消费者监听配置
package com.config.ConsumerListener;
import com.config.config.MyTtlQueueConfig2;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
/**
* @Description:消费者监听消息
* @Author:GJM
* @Date:2023/3/2 17:01
*/
@Component
@Slf4j
public class ConsumerListener {
/*
* 使用延时插件优化后的消息队列
* @Author:Gjm
* @Date :2023/3/9 19:10
* @return
*/
@RabbitListener(queues = "delayed.queue")
public void ConsumerTtlMessage3(Message message){
System.out.println("使用延时插件优化后的消息队列 :" + message);
}
}
消息生产者
/*
* 使用延时插件的消息队列发送消息
* @Author:Gjm
* @Date :2023/3/9 19:12
* @return
*/
@PostMapping("/sendDelayedMessage")
public void sendDelayedMessage(@Param("ttl") Integer ttl){
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
StringBuilder messages = new StringBuilder("这条消息过期时间是");
messages.append(ttl).append("秒");
sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),messages,
createTime,"delayed.routingKey","delayed.exchange" ,(message)->{
message.getMessageProperties().setDelay(ttl);
return message;
});
System.out.println("发送成功");
}
重新启动,postman多次设置不一样的过期时间测试。
测试结果符合预期,先到时间先消费。
实现消息手动确认
手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。
yml配置改动
rabbitmq:
host:
port: 5672
username:
password:
virtual-host: /
listener:
simple:
# 表示消费者消费成功消息以后需要手工的进行签收(ack确认),默认为 auto
acknowledge-mode: manual
default-requeue-rejected: true
消费者监听改动
/**
* @Description:消费者监听消息
* @Author:GJM
* @Date:2023/3/2 17:01
*/
@Component
@Slf4j
public class ConsumerListener {
@RabbitListener(queues = "TestDirectQueue")
@RabbitHandler
public void ConsumerMessage(Message msg, Channel channel){
System.out.println("DirectReceiver消费者收到消息 : " + msg.toString());
Map<String,Object> message = msg.getMessageProperties().getHeader("param");
long deliverTag = msg.getMessageProperties().getDeliveryTag();
try {
//业务逻辑代码...
//消费成功,确认消息,
// 第一个参数:deliveryTag:该消息的index
// 第二个参数:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
channel.basicAck(deliverTag, true);
}catch (Exception e) {
try {
//出现异常用的,
//第一个参数:deliveryTag:该消息的index
// 第二个参数:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
//第三个参数:被拒绝的是否重新入队列
// 但是一定要有一个拒绝策略,比如重试5次,记录到Redis,后期进行补偿,然后丢掉这个消息,不然会一直循环这个消费
channel.basicNack(deliverTag, false, true);
} catch (IOException ioException) {
log.error("重新放入队列失败,失败原因:{}", e.getMessage(), e);
}
}
}
如果异常导致的拒绝,设置重新入队之后,异常没有解决,会一直循环错误。建议对异常错误有补偿策略。比如重试5次,记录到Redis,后期进行补偿,然后丢掉这个消息。