最近工作突然就忙起来了,忙里偷闲的我偷偷回顾了一下以前面试过程中被问到频率比较高的问题,思来想去,脑海里就对RabbitMq如何实现延迟队列这个问题映像比较深刻,首先是这个问题如果是没有接触过这个中间件的同学来说,一般都会被问懵,其次是一旦你知道了这个问题的内核以后呢,你会不屑一顾,懒得实践和复习,待到下次面试的时候再次碰到此问题时也会手足无措,更甚者更第一次面试一样懵逼状态。
今天,就让我们好好的来复习一下这个伪命题吧,首先,我们先思考场景,方便记忆,延迟队列这个功能到底能用来干嘛呢?就拿我们身边的例子来说吧,比如我们淘宝买东西的时候,是不是在确认订单的时候会有一个跳转到付款界面的操作呢,如果跳转到付款界面后你放弃付款,淘宝会保留你的订单信息一段时间,方便我们想买的时候好给钱,那么这个就是一个很好的延迟队列的场景,这个场景总结就是一句话延迟订单信息一段直到用户付款。
场景有了,那么接下来就是说思路了,在rabbitmq中,有一种特殊的队列,叫死信队列,什么是死信队列,就是消息投放到某个队列一段时间后,如果没有被消费,那么就会被转移到死信队列中,有了这个机制,实现延迟队列那还不是手拿把掐,首先我们发送订单信息到订单队列中,这里我们要设置一个时间段,当订单队列的消息没有被消费时,就会转移到死信队列中,这里的时间段就是上面场景里等待用户付款的时间长度,当时间到时,订单信息就会转移到死信队列中,这里还有一个关键点,那就是在消费方我们不要创建消费订单队列的消费者,而是创建死信队列的消费者,当消息到达死信队列时,死信消费者取消订单信息,这样,上面那个延迟付费的场景基本就现出原型了
思路有了,接下来的时间就是实操阶段了,下面我们上代码
生产者
java
@Configuration
public class DelayQueueConfig {
public static final String JAVABOY_QUEUE_NAME = "javaboy_queue_name";
public static final String JAVABOY_EXCHANGE_NAME = "javaboy_exchange_name";
public static final String JAVABOY_ROUTING_KEY = "javaboy_routing_key";
public static final String DLX_QUEUE_NAME = "dlx_queue_name";
public static final String DLX_EXCHANGE_NAME = "dlx_exchange_name";
public static final String DLX_ROUTING_KEY = "dlx_routing_key";
public static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
/**
* 死信队列
*
* @return
*/
@Bean
public Queue dlxQueue() {
return new Queue(DLX_QUEUE_NAME, true, false, false);
}
/**
* 死信交换机
*
* @return
*/
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(DLX_EXCHANGE_NAME, true, false);
}
/**
* 绑定死信队列和死信交换机
*
* @return
*/
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(DLX_ROUTING_KEY);
}
/**
* 普通交换机
*
* @return
*/
@Bean
public Queue javaBodyQueue() {
Map<String, Object> args = new HashMap<>();
//设置消息过期时间
args.put("x-message-ttl", 1000 * 10);
//设置死信交换机
args.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);
//设置死信 routing_key
args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);
return new Queue(JAVABOY_QUEUE_NAME, true, false, false, args);
}
/**
* 普通交换机
*
* @return
*/
@Bean
DirectExchange javaBodyExchange() {
return new DirectExchange(JAVABOY_EXCHANGE_NAME, true, false);
}
/**
* 绑定普通队列和与之对应的交换机
*
* @return
*/
@Bean
Binding javaBodyBinding() {
return BindingBuilder.bind(javaBodyQueue())
.to(javaBodyExchange())
.with(JAVABOY_ROUTING_KEY);
}
上面这段代码,是我们在springboot中配置rabbitmq的队列相关信息,包括绑定路由和交换机,然后设置多长时间消息未消费时转移到死信队列中,这里我们设置的时长是10s,如果你在rabbitmq提供的操作界面上设置了,那么这些方法就不需要在代码中写了
我们编写一个Controller方法来模拟生产消息
java
@RestController
@Slf4j
public class TestController {
@PostMapping("/sendDlxRabbitmq")
public Result<?> sendDlxRabbitmq() {
System.out.println(new Date());
String message = "消息延迟发送测试";
rabbitTemplate.convertAndSend(DelayQueueConfig.JAVABOY_EXCHANGE_NAME, DelayQueueConfig.JAVABOY_ROUTING_KEY, message);
return Result.success("成功");
}
}
配置信息
这是配置文件中rabbitmq的配置连接,生产者和消费者都用同一份即可
properties
spring.rabbitmq.host= 127.0.0.1
spring.rabbitmq.port= 5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
生产者涉及到的代码信息已经准备就绪,接下来就是消费者了
消费者
java
@Component
@RabbitListener(queues = DelayQueueConfig.DLX_QUEUE_NAME)
public class ReceiveHandler {
@RabbitHandler
public void receive_dlxQueue(Object msg) {
System.out.println(new Date());
System.out.println("接收到私信队列消息:" + msg);
}
}
消费者在springboot注解的帮助下显得什么的精致哈,至于这个队列的名称可以直接写"dlx_queue_name"替代,只要和上面生产者使用的一样就行,以前被项目要求的魔法值检查搞怕了,养成习惯了
做完上面这些,还有一步最重要的那就是安装rabbitmq,这篇文章我就默认你们会安装不细说了,我是在windows上安装的,安装完使用命令:rabbitmq-plugins enable rabbitmq_management 启动rabbitmq可视化插件,然后浏览器访问就ok了哈
最后,我们依次来启动服务,首先是生产者,启动之后查看rabbitmq的队列信息就会多出我们在代码里配置的相关队列
然后是我们的消费者启动,都完成之后,我们利用postman发送一条消息看效果
生产者结果
消费者结果
通过上面的图片可以很直观的看出来效果,我们的消息延迟了十秒后才消费,符合我们的预期,实践完成。
一通操作下来,我们知道了什么是RabbitMq的延迟队列,为什么说它是个伪命题,是因为它的实现思路的精髓在于对消费者的巧妙运用,学习一个知识的时候,最好的命名应该是见闻而知其义,在第一印象中就能找到学习的方向,而不需要去理解,像倒排索引的命名,很容易在第一次接触的时候误以为概念和sql中的倒序一样,但是这个世界其实也是一个草台班子,永远都不要低估自己的能力,哪怕现在真的很水哈哈哈
最后,还是要送上一位名人曾说的一句话:手上没有剑和有剑不用是两回事!