1、过期队列:
消息如果在队列中一直没有被消费且存在时间超过了ttl,消息就会变成死信,后续无法再消费。设置ttl有两种方式,
1,声明消息队列的时候,这个是全局的,所有发到这个队列的消息的过期时间是一样的
2、发送消息的时候设置属性,可以每条消息设置不同的ttl
假如你两种都设置了,以小的ttl为准。
两者的区别:
queue的全局ttl,消息过期立刻就会被删掉;如果是发送消息时设置的ttl,过期之后并不会立刻删掉,这时候消息是否过期是需要投递给消费者的时候判断的。
原因:
queue的全局ttl,队列的有效期都一样,先入队列的队列头部,头部也是最早过期的消息,rabbitmq会有一个定时任务从队列的头部开始扫描是否有过期消息即可。而每条设置不同的ttl,只有遍历整个队列才可以筛选出来过期的消息,这样的效率实在是太低,而且如果消息量大了根本不可行,所以rabbitmq在等到消息投递给消费者的时候判断当前消息是否过期,虽然删除的不及时但是不影响功能。
注意,ttl队列一般需要设置监听者,因为过期之后我们会有一些通用处理逻辑比如转发到死信队列。
2、死信队列
死信队列和普通队列并没有什么特殊之处,它的作用主要是用来接收死信消息(dead message),什么是死信消息呢?一个正常的消息变成死信消息有以下集中情况:
1、消息过期
2、消息被拒绝
3、队列达到最大长度
这两个队列可以做什么?
最常见的就是延迟关单,比如下单15分钟没有支付订单关闭。本文将模拟订单来演示这个功能。
原代码:https://www.cnblogs.com/gyjx2016/p/13622097.html ,我们将在这个代码基础上增加一些功能。
orderDto
@Data public class OrderDto { private String orderNo; private String title; private String body; public String getOrderNo() { return orderNo; } public void setOrderNo(String orderNo) { this.orderNo = orderNo; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } }
死信队列标识符
public class DlxConstant { /** * dlx 死信交换机标识符 */ public static final String DEAD_LETTER_EXCHANGE = "x-dead-letter-exchange"; /** * dlx 死信路由key标识符 */ public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-routing-key"; }
死信队列配置
@Configuration public class DlxConfig { /*************************ttl dlx 配置 *************************************/ public static final String DLX_TTL_QUEUE = "dlx_ttl_queue"; public static final String DLX_TTL_ROUT_KEY = "dlx.ttl.key"; @Bean("dlxTtlQueue") public Queue dlxTtlQueue() { return new Queue(DLX_TTL_QUEUE, true, false, false); } @Bean("dlxTtlBind") public Binding dlxTtlBind(@Autowired @Qualifier("directExchange") DirectExchange directExchange) { return BindingBuilder.bind(dlxTtlQueue()).to((directExchange)).with(DLX_TTL_ROUT_KEY); } }
订单过期队列配置
@Configuration public class OrderTtlQueueConfig { public static final String TTL_ORDER_QUEUE="ttl_order_queue"; public static final String TTL_ROUT_KEY="#.ttl"; /** * ttlqueue 里的消息过期之后转移到死信队列中 * @return */ @Bean("ttlOrderQueue") public Queue ttlOrderQueue(){ Map<String,Object> params=new HashMap<>(); params.put(DlxConstant.DEAD_LETTER_EXCHANGE,RabbitMQExchangeConfig.DIRECT_EXCHANGE); params.put(DlxConstant.DEAD_LETTER_QUEUE_KEY,DlxConfig.DLX_TTL_ROUT_KEY); return new Queue(TTL_ORDER_QUEUE,true,false,false,params); } @Bean("ttlOrderBind") public Binding ttlOrderBind(@Autowired @Qualifier("topicExchange") TopicExchange topicExchange){ return BindingBuilder.bind(ttlOrderQueue()).to(topicExchange).with(TTL_ROUT_KEY); } }
发送创建订单mq消息
/** * 创建订单 * @return */ @GetMapping("createOrder") public String createOrder(){ OrderDto orderDto=new OrderDto(); orderDto.setOrderNo("123456789"); orderDto.setTitle("小米手机"); orderDto.setBody("黑色的小米手机"); rabbitTemplate.convertAndSend(RabbitMQExchangeConfig.TOPIC_EXCHANGE,"order.ttl",orderDto,message -> { //过期时间10s message.getMessageProperties().setExpiration("10000"); message.getMessageProperties().setMessageId(orderDto.getOrderNo()); message.getMessageProperties().setCorrelationId(orderDto.getOrderNo()); return message; }); log.info("send ok"); return "ok"; }
监听订单死信队列
/** * 消费订单死信队列 * * @param channel * @param orderDto * @param message * @throws Exception */ @RabbitListener(queues = {"#{dlxTtlQueue.name}"}) public void orderDlxTtl(@Header(AmqpHeaders.CHANNEL) Channel channel, OrderDto orderDto, Message message) throws Exception { log.info("orderDlxTtl,orderDto:{},mq.message:{}", orderDto.toString(), message.toString()); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); }
mq管控台
控制输出结果:
参考文献:
1、https://docs.spring.io/spring-amqp/docs/2.1.17.RELEASE/reference/html/#broker-configuration
全文完,感谢您的耐心阅读~
欢迎大家关注我的公众号