RabbitMQ 死信队列和延时队列
TTL(Time To Live)
- 消息的TTL就是消息的存活时间
- RabbitMQ中对队列、消息都可以设置TTL
- 对队列设置TTL,就是队列没有消费者连着的保留时间;对消息设置TTL,超过了这个时间,消息就死了,称之为死信。
- 如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的
expiration
字段或者x-message-ttl
属性来设置时间,两者是—样的效果。
死信队列
概念
-
死信:顾名思义就是无法被消费的消息,由于特殊的原因和条件下queue中的某些消息无法被消费,进而没有了后续处理变成了死信。
-
死信Exchange:DLX,
dead-letter-exchange
。- 利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。DLX同时也是一个正常的交换机,它们可以像普通的交换机一样使用。
-
死信路由route:队列中的消息过期后会被转发到这个路由键:
x-dead-letter-routing-key
。- 普通的队列设置好自己的Dead Letter Exchange,当此队列中的消息过期后会被转发到这个路由,被称为死信路由。
可以进入死信路由的情况
- TTL过期的消息
- 被consumer拒收的消息,并且reject方法的参数里requeue是false(不会重新入队)
- 队列消息满了,无法再添加数据到 mq 中,排在前面的消息会被丢弃或进入死信路由
延迟队列
概念
- 延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。
用途
- 利用死信可以实现延迟队列。 比如设置队列有限期为60s,到期移动到另一个队列。比如订单有效支付时间30min,30min之后移动到另一个死信队列,我们可以监听另一个死信队列(例如可以根据唯一标识去查库,检验是否完成支付)。
代码实现
-
基于
RabbitMQ
的延迟队列实现可以安装一个官方插件,该插件很好的集成了延迟队列。并且内部有优化机制。-
安装配置步骤
# 1、官网下载 rabbitmq_delayed_message_exchange 插件 # 下载直通车:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases # 2、下载 rabbitmq_delayed_message_exchange-3.10.2.ez 文件 # 3、上传至服务器,并且拷贝该文件到rabbitmq插件目录下或docker容器中(docker安装需要进入docker容器内启动) # 拷贝 docker cp rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq:/opt/rabbitmq/plugins/ # 进入容器内部 docker exec -it rabbitmq bash # 查看插件 rabbitmq-plugins list # 开启死信交换机插件 rabbitmq-plugins enable rabbitmq_delayed_message_exchange # 重启rabbitmq docker restart rabbitmq
-
-
-
-
创建死信交换机和死信队列Bean
- 需要将队列和交换机注入容器中,并且在程序启动和自动配置时就加载。
@Configuration public class DelayedQueueConfig { 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 delayedQueue() { return new Queue(DELAYED_QUEUE_NAME); } @Bean public CustomExchange delayedExchange() { HashMap<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args); } @Bean public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue, @Qualifier("delayedExchange") CustomExchange customExchange){ return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs(); } }
-
生产者Controller编写
@Slf4j @RequestMapping("ttl") @RestController public class SendMsgController { @Autowired private RabbitTemplate rabbitTemplate; // url: /ttl/sendDelayMsg/{message}/{delayTime} @GetMapping("sendDelayMsg/{message}/{delayTime}") public void sendMsg(@PathVariable String message, @PathVariable Integer delayTime) { rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message, correlationData -> { correlationData.getMessageProperties().setDelay(delayTime); return correlationData; }); log.info("当前时间{}, 发送一条延迟{}毫秒的信息给队列 delayed.queue:{}",new Date(), delayTime, message); } }
-
消费者组件编写
@Slf4j @Component public class DeadLetterQueueConsumer { public static final String DELAYED_QUEUE_NAME = "delayed.queue"; @RabbitListener(queues = DELAYED_QUEUE_NAME) public void receiveDelayedQueue(Message message) { String msg = new String(message.getBody()); log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg); } }
-
测试
-
配置文件
spring.rabbitmq.host=192.168.197.133 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest server.port=8081