RabbitMQ高级技巧——过期时间、死信队列

153 阅读1分钟

过期时间

在我们业务开发中有这样一个场景,如果一个订单30分钟未支付则移除该订单。这个场景就可以使用到消息队列中的过期时间来处理。
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接受获取;过了之后消息将自动被删除。
RabbitMQ可以对消息和队列设置TTL,目前有两种方式可以设置:
** 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。**

简单使用

  • 首先创建工程,然后添加相关依赖即可。
  • 编写RabbitMQ配置文件
@Configuration
public class TTLRabbitMQConfiguration {

    @Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange("ttl_direct_exchange", true, false);
    }

    @Bean
    public Queue ttlDirectQueue() {
        // 设置过期时间 从web界面 添加队列出获取 ,单位是毫秒
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000); //这里一定是int类型
        return new Queue("ttl.direct.queue", true, false, false, args);
    }

    @Bean
    public Binding ttlDirectBinding() {
        return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
    }

}
  • 编写业务逻辑
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //给队列设置过期时间
    public void makeOrderTtl() {
        // 保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("下单成功:" + orderId);
        // 通过MQ完成消息的分发
        // 参数1:交换机 ;参数2:路由key/队列名;参数3:消息内容
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttl";
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
    }
    
}
  • 测试
    只需要编写测试类调用业务代码即可。然后可以在web界面查看效果。
    在这里插入图片描述

** 第二种方法是对消息进行单独设置,每条消息TTL可以不同。**

  • 声明新队列
@Configuration
public class TTLRabbitMQConfiguration {
    @Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange("ttl_direct_exchange", true, false);
    }
   
    @Bean
    public Queue ttlMessageDirectQueue() {
        return new Queue("ttl.message.direct.queue", true);
    }

    @Bean
    public Binding ttlMessageDirectBinding() {
        return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttlMessage");
    }

}
  • 业务代码
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //给消息设置过期时间
    public void makeOrderTtlMessage() {
        String orderId = UUID.randomUUID().toString();
        System.out.println("下单成功:" + orderId);
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttlMessage";

        // 给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //这里区别在于参数是字符串
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };

        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId, messagePostProcessor);
    }
}

两者的差异在于:过期队列中的消息过期后会写入死信队列中,而手动设置消息的过期时间不会将消息写入死信队列中。
如果上述两种方法同时使用,则消息的过期时间以两者之间的TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message,会被投递到死信队列,消费者将无法收到该消息。

死信队列

DLX,全程为Dead-Letter-Exchange,也可以称之为死信交换机。当消息在一个队列中变成死信之后,他能被重新发送到一个交换机中,这个交换机就是DLX,绑定DLX的队列就是死信队列。
消息变为死信,可能由于以下几个原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

DLX也是一个正常的交换机,和一般的交换机没有区别,他能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,RabbitMQ就会自动的将这个消息重新发布到设置的DLX 上去,进而被路由到另一个队列,即死信队列。
想要使用死信队列,只需要在定义队列的时候设置队列参数*** x-dead-letter-exchange *** 指定交换机即可。

  • 声明死信交换机及死信队列
@Configuration
public class DeadRabbitMQConfiguration {

    @Bean
    public DirectExchange deadDirectExchange() {
        return new DirectExchange("dead_direct_exchange", true, false);
    }

    @Bean
    public Queue deadDirectQueue() {
        return new Queue("dead.direct.queue", true);
    }

    @Bean
    public Binding deadDirectBinding() {
        return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with("dead");
    }

}
  • 绑定死信队列
    使用第一步测试过期时间队列的代码即可。添加对应的参数,交换机,路由参数,最大长度等。具体可以从web界面查看。
@Configuration
public class TTLRabbitMQConfiguration {

    @Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange("ttl_direct_exchange", true, false);
    }

    @Bean
    public Queue ttlDirectQueue() {
        // 设置过期时间 从web界面 添加队列出获取 ,单位是毫秒
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000); //这里一定是int类型
        args.put("x-dead-letter-exchange", "dead_direct_exchange");
        args.put("x-dead-letter-routing-key", "dead"); //fanout模式不需要配置
        return new Queue("ttl.direct.queue", true, false, false, args);
    }

    @Bean
    public Queue ttlMessageDirectQueue() {
        return new Queue("ttl.message.direct.queue", true);
    }

    @Bean
    public Binding ttlDirectBinding() {
        return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
    }

    @Bean
    public Binding ttlMessageDirectBinding() {
        return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttlMessage");
    }


}
  • 测试
    直接调用过期时间的测试方法即可,需要注意的是如果队列名称一样,会执行失败。所以可以换一个队列名或者在web界面删除相对应的队列即可。
    在这里插入图片描述
    测试之后,可以看到5秒之后,数据会被移到死信队列中。
    在这里插入图片描述