1、过期队列:

消息如果在队列中一直没有被消费且存在时间超过了ttl,消息就会变成死信,后续无法再消费。设置ttl有两种方式,

1,声明消息队列的时候,这个是全局的,所有发到这个队列的消息的过期时间是一样的

2、发送消息的时候设置属性,可以每条消息设置不同的ttl

假如你两种都设置了,以小的ttl为准。

两者的区别:

queue的全局ttl,消息过期立刻就会被删掉;如果是发送消息时设置的ttl,过期之后并不会立刻删掉,这时候消息是否过期是需要投递给消费者的时候判断的。

原因:

queue的全局ttl,队列的有效期都一样,先入队列的队列头部,头部也是最早过期的消息,rabbitmq会有一个定时任务从队列的头部开始扫描是否有过期消息即可。而每条设置不同的ttl,只有遍历整个队列才可以筛选出来过期的消息,这样的效率实在是太低,而且如果消息量大了根本不可行,所以rabbitmq在等到消息投递给消费者的时候判断当前消息是否过期,虽然删除的不及时但是不影响功能。

注意,ttl队列一般需要设置监听者,因为过期之后我们会有一些通用处理逻辑比如转发到死信队列。

 

2、死信队列

死信队列和普通队列并没有什么特殊之处,它的作用主要是用来接收死信消息(dead message),什么是死信消息呢?一个正常的消息变成死信消息有以下集中情况:

1、消息过期

2、消息被拒绝

3、队列达到最大长度

 

这两个队列可以做什么?

最常见的就是延迟关单,比如下单15分钟没有支付订单关闭。本文将模拟订单来演示这个功能。

 

【rabbitmq】之过期和死信队列_发送消息

原代码: ,我们将在这个代码基础上增加一些功能。

 

 

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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

 

死信队列标识符

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";



}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

 

死信队列配置

@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);
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

 

订单过期队列配置

@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);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

 

发送创建订单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";

    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

 

 

监听订单死信队列

/**
     * 消费订单死信队列
     *
     * @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);
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

 

 

mq管控台

 

【rabbitmq】之过期和死信队列_发送消息_02

 

 

控制输出结果:

 

【rabbitmq】之过期和死信队列_发送消息_03

 

 

 

参考文献:

1、 https://docs.spring.io/spring-amqp/docs/2.1.17.RELEASE/reference/html/#broker-configuration