前言
基于死信队列和插件的延迟队列
延迟队列
延迟队列是一种特殊的队列,用于延迟消息的投递。在RabbitMQ中,延迟队列通过死信队列或者插件实现。
当你需要在未来的某个时间点投递消息时,你可以将消息发送到延迟队列中,并设置一个延迟时间。在这段时间内,消息将被存储在队列中,直到延迟时间到达。一旦到达延迟时间,消息将被从延迟队列中取出,并发送到它的目标队列,以供消费者使用。
使用RabbitMQ的延迟队列,你可以更加灵活地控制消息的投递时间,避免消息在不合适的时间被投递,从而提高了系统的稳定性和可靠性。同时,延迟队列也可以用于处理一些特殊的业务场景,比如重试机制、定时任务等。
通过死信队列实现
声明我们的队列跟交换机,通过配置类实现
之前都是调用函数来声明,现在时通过Bean来声明
@Configuration
public class TTLQueueConfig {
public static final String NORMAL_EXCHANGE = "normalExchange";
public static final String NORMAL_QUEUE_10S = "normalQueue10s";
public static final String NORMAL_QUEUE_20S = "normalQueue20s";
public static final String DEAD_EXCHANGE = "deadExchange";
public static final String DEAD_QUEUE = "deadQueue";
public static final String N_Q_10S_ROUTING_KEY = "normalQueue10sK";
public static final String N_Q_20S_ROUTING_KEY = "normalQueue20sK";
public static final String D_Q_ROUTING_KEY = "deadQueueK";
/**
* 声明普通交换机
* @return
*/
@Bean("normalExchange")
public DirectExchange getDirectExchange(){
return new DirectExchange(NORMAL_EXCHANGE);
}
/**
* 声明延迟队列10s
* @return
*/
@Bean("normalQueue10s")
public Queue getQueue10s(){
Map<String, Object> arguments = new HashMap<>();
// 声明死信交换机和死信路由键和ttl
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key",D_Q_ROUTING_KEY);
arguments.put("x-message-ttl",10000);
return QueueBuilder.durable(NORMAL_QUEUE_10S).withArguments(arguments).build();
}
/**
* 声明延迟队列20s
* @return
*/
@Bean("normalQueue20s")
public Queue getQueue20s(){
Map<String, Object> arguments = new HashMap<>();
// 声明死信交换机和死信路由键和ttl
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key",D_Q_ROUTING_KEY);
arguments.put("x-message-ttl",20000);
return QueueBuilder.durable(NORMAL_QUEUE_20S).withArguments(arguments).build();
}
/**
* 绑定ttl10s关系
* @return
*/
@Bean
public Binding Binding10s(@Qualifier("normalQueue10s") Queue normalQueue10s,
@Qualifier("normalExchange") DirectExchange normalExchange){
return BindingBuilder.bind(normalQueue10s).to(normalExchange).with(N_Q_10S_ROUTING_KEY);
}
/**
* 绑定ttl20s关系
* @return
*/
@Bean
public Binding Binding20s(@Qualifier("normalQueue20s") Queue normalQueue20s,
@Qualifier("normalExchange") DirectExchange normalExchange){
return BindingBuilder.bind(normalQueue20s).to(normalExchange).with(N_Q_20S_ROUTING_KEY);
}
/**
* 声明死信交换机
* @return
*/
@Bean("deadExchange")
public DirectExchange getDeadDirectExchange(){
return new DirectExchange(DEAD_EXCHANGE);
}
/**
* 声明死信队列
* @return
*/
@Bean("deadQueue")
public Queue getDeadQueue(){
return new Queue(DEAD_QUEUE);
}
/**
* 绑定死信关系
* @return
*/
@Bean
public Binding BindingDead(@Qualifier("deadQueue") Queue deadQueue,
@Qualifier("deadExchange") DirectExchange deadExchange){
return BindingBuilder.bind(deadQueue).to(deadExchange).with(D_Q_ROUTING_KEY);
}
}
生产者代码
通过RabbitTemplate类发送消息到rabbitmq服务,该类类似于RedisTemplate,供我们操作rabbitmq
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping
public void sendMessage(String msg){
rabbitTemplate.convertAndSend(TTLQueueConfig.NORMAL_EXCHANGE,
TTLQueueConfig.N_Q_10S_ROUTING_KEY,
"消息来自ttl10s的队列"+msg.getBytes());
rabbitTemplate.convertAndSend(TTLQueueConfig.NORMAL_EXCHANGE,
TTLQueueConfig.N_Q_20S_ROUTING_KEY,
"消息来自ttl20s的队列"+msg.getBytes());
}
消费者代码
通过该注解@RabbitListener实现,被注解的方法就类似之前的回调方法
@RabbitListener(queues = "deadQueue")
public void consumerMsg(Message message){
String msg = new String(message.getBody());
System.out.println("收到消息:"+msg);
}
弊端
通过死信队列实现的延迟队列,如果ttl是由发送者解决的话,假设我先发送一条A消息ttl为10s,再发送一条B消息2s的消息,那么由于队列先进先出的特性2s到后A消息并不会被队列弹出处理,而是等到A消息ttl到期后把A消息弹出队列后才会检查B消息ttl是否到期
为了解决这个弊端我们需要通过插件的方式来实现延迟队列
RabbitTemplate类
RabbitTemplate是Spring AMQP(高级消息队列协议)框架中的一个关键类,用于将Spring应用程序与RabbitMQ消息系统集成。 RabbitTemplate类提供了一个高级API,用于与RabbitMQ发送和接收消息。
@RabbitListener
@RabbitListener是Spring AMQP的注解,用于指示Spring应用程序侦听RabbitMQ队列的消息。使用@RabbitListener可以将一个方法标记为消息处理器,使得该方法能够接收特定队列上的消息,并对其进行处理。
@RabbitListener注解有多种属性,其中key是用于指定监听哪个队列的属性。可以通过在key属性中指定队列名称或绑定键来将消息路由到不同的处理器方法。
通过插件实现
在使用RabbitMQ延迟队列插件时,通常需要创建一个特殊的延迟交换机,用于将延迟消息路由到对应的延迟队列。这个延迟交换机通常被称为"延迟交换机"或"延迟路由器"。
与常规交换机不同: 延迟交换机不会立即将消息发送到与之绑定的队列中,而是将其缓存在内存中,并在延迟时间到达后再发送到队列中。
当一个消息被发送到延迟交换机时: 延迟交换机会将消息缓存起来,并在指定的延迟时间后才将其发送到与之绑定的队列中。
需要注意的是: 延迟交换机并不是RabbitMQ的标准交换机类型,而是延迟队列插件提供的一种自定义交换机类型。在创建延迟交换机时,需要指定其类型为"x-delayed-message",该类似由插件提供,并设置一个"x-delayed-type"参数,用于指定延迟消息投递到队列的策略跟,就是设置自带的交换机类型
因此,使用RabbitMQ延迟队列插件时,通常需要先安装插件并配置延迟交换机,才能开始使用延迟队列。
总之,延迟交换机是一种特殊类型的交换机,用于实现延迟队列。它的特点是可以将消息缓存在内存中,并在指定的延迟时间后才将其发送到与之绑定的队列中。这使得实现延迟队列变得更加容易。
安装延时队列插件
在官网上下载 www.rabbitmq.com/community-p…,下载
rabbitmq_delayed_message_exchange 插件,然后解压放置到 RabbitMQ 的插件目录。
rabbitmq_delayed_message_exchange-3.8.0.ez将该文件移到到/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
执行rabbitmq-plugins enable rabbitmq_delayed_message_exchange启动插件
声明我们需要的队列和交换机
/**
* 声明延迟交换机
* @return
*/
@Bean("delayExchange")
public CustomExchange delayExchange(){
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-delayed-type",ExchangeTypes.DIRECT);
return new CustomExchange(DELAY_EXCHANGE,"x-delayed-message",true,false,arguments);
}
/**
* 声明普通队列
* @return
*/
@Bean("normalQueue")
public Queue getQueue(){
return QueueBuilder.durable(NORMAL_QUEUE).build();
}
/**
* 绑定普通队列到延迟交换机
* @return
*/
@Bean
public Binding BindingNormalQueue(@Qualifier("normalQueue") Queue normalQueue ,
@Qualifier("delayExchange") CustomExchange delayExchange){
return BindingBuilder.bind(normalQueue).to(delayExchange).with(N_Q_ROUTING_KEY).noargs();
}
消费者代码
@RabbitListener(queues = "normalQueue")
public void consumerNormalQueueMsg(Message message){
String msg = new String(message.getBody());
System.out.println("收到消息:"+msg);
}
生产者代码
@GetMapping("/{message}/{ttl}")
public void sendMessage(@PathVariable("message") String msg,
@PathVariable("ttl") Integer ttl){
rabbitTemplate.convertAndSend(TTLQueueConfig.DELAY_EXCHANGE,
TTLQueueConfig.N_Q_ROUTING_KEY,
"消息来自普通队列:"+msg,message->{
message.getMessageProperties().setDelay(ttl);
return message;
});
}