一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情。
7.7 RabbitMQ插件实现延迟队列
7.7.1 安装插件
如果不能实现在消息粒度上的 TTL,并使其在设置的TTL 时间及时死亡,就无法设计成一个通用的延时队列。那如何解决呢,接下来我们就去解决该问题。
我们用插件来实现别人写好的功能~
安装后,重启rabbitmq-server即可
安装成功后,交换机会出现新的类型
7.7.2 代码架构图
一个队列delayed.queue,一个自定义交换机 delayed.exchange,绑定关系如下
7.7.3 配置文件类代码
在我们自定义的交换机中,这是一种新的交换类型,该类型消息支持延迟投递机制 消息传递后并不会立即投递到目标队列中,而是存储在 mnesia(一个分布式数据系统)表中,当达到投递时间时,才投递到目标队列中。
package com.caq.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;
@Configuration
public class DelayQueueConfig {
// 交换机
public static final String DELAYED_QUEUE_NAME = "delayed_queue";
// 队列
public static final String DELAYED_EXCHANGE_NAME = "delayed_exchange";
// routingKey
public static final String DELAYED_ROUTING_KEY = "delayed_routingkey";
@Bean
public Queue delayedQueue() {
return new Queue(DELAYED_QUEUE_NAME);
}
// public CustomExchange(String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments)
@Bean("delayedExchange")
public CustomExchange delayedExchange() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message",
true, false, arguments);
}
//绑定
@Bean
public Binding delayedQueueBindingDelayedExchange(
@Qualifier("delayedQueue") Queue delayedQueue,
@Qualifier("delayedExchange") CustomExchange delayedExchange) {
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
}
}
7.7.4 消息生产者代码
package com.caq.controller;
import com.caq.config.DelayQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* 发送延迟消息
* http://localhost:8080/ttl/sendMsg/哈哈哈哈哈哈
*
* @RestController只返回内容,不进行页面跳转
* @RequestMapping请求路径
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMsgController {
/**
* {}是占位符,结果执行后会被后面的所替换
*
* @param message
*/
//通过rabbitTemplate来发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
//开始发消息,基于插件的消息及延迟的时间
@GetMapping("/sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,
@PathVariable Integer delayTime) {
log.info("当前时间:{},发送一条时长{}毫秒信息给延迟队列delayed.queue:{}",
new Date().toString(), delayTime, message);
rabbitTemplate.convertAndSend(DelayQueueConfig.DELAYED_EXCHANGE_NAME,
DelayQueueConfig.DELAYED_ROUTING_KEY, message, msg -> {
msg.getMessageProperties().setDelay(delayTime);
return msg;
});
}
}
7.7.5 消息消费者代码
消费者代码和之前的一样
package com.caq.consumer;
import com.caq.config.DelayQueueConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component
public class DelayQueueConsumer {
//监听消息收消息
@RabbitListener(queues = DelayQueueConfig.DELAYED_QUEUE_NAME)
public void receiveDelayQueue(Message message) {
String msg = new String(message.getBody());
log.info("当前时间:{},收到延迟队列的消息:{}", new Date().toString(), msg);
}
}
发起请求测试:
http://localhost:8080/ttl/sendDelayMsg/come on baby1/20000
http://localhost:8080/ttl/sendDelayMsg/come on baby2/2000
7.8 总结
延时队列在需要延时处理的场景下非常有用,使用 RabbitMQ 来实现延时队列可以很好的利用RabbitMQ 的特性,如:**消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。**另外,通过 RabbitMQ 集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。
当然,延时队列还有很多其它选择,比如利用 Java 的 DelayQueue,利用 Redis 的 zset,利用 Quartz或者利用 ==kafka 的时间轮==,这些方式各有特点,看需要适用的场景