全网最详细RabbitMQ的TTL机制和死信队列详解

845 阅读1分钟

TTL机制

常见的应用场景

  • 订单支付

  • 购物车付款

如何解决相关问题

  • 定期轮询

  • Timer

  • ScheduledExecutorService

  • RabbitMQ的TTL

  • Quartz

  • Redis Zset

  • JCronTab

  • SchedulerX

TTL概念

TTL是Time to Live的简称,就是过期时间。

RabbitMQ可以对消息和队列两个维度来设置TTL。

过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。

  • 1.通过Queue属性设置,队列中所有消息都有相同的过期时间。

  • 2.对消息自身进行单独设置,每条消息的TTL可以不同。

如果两种方法一起使用,则消息的TTL以两者之间较小数值为准。通常来讲,消息在队列中的生存时间一旦超过设置的TTL值时,就会变成“死信”(Dead Message),消费者默认就无法再收到该消息。

原声API案例

public static void main(String[] args) throws IOException {
    Connection connection = RabbitUtils.getConnection();
    try {
        Channel channel = connection.createChannel();
        HashMap<String, Object> arguments = new HashMap<>();
        //消息队列中消息过期时间,30s
        arguments.put("x-message-ttl", 30 * 1000);
        //消息队列没有消费者,则10s消息过期,消息队列也删除
        arguments.put("x-expires", 10 * 1000);
        channel.queueDeclare("queue.ttl.waiting", true, false, false, arguments);

        channel.exchangeDeclare("ex.ttl.waiting", "direct", true, false, null);
        channel.queueBind("queue.ttl.waiting", "ex.ttl.waiting", "key.ttl.waiting");

        String message = "等待的订单号";

        final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().contentEncoding("utf-8").deliveryMode(2).build();

        channel.basicPublish("ex.ttl.waiting", "key.ttl.waiting", properties, message.getBytes("UTF-8"));

        channel.close();
    } catch (IOException | TimeoutException e) {
        e.printStackTrace();
    }
    connection.close();

}

除了代码里面可以设置TTL命令,还可以设置全局TTL

rabbitmqctl set_policy TTL ".*" '{"message-ttl":30000}' --apply -toqueues

TTL默认规则

  • 1.如果不设置TTL,则表示此消息不会过期;

  • 2.如果TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃;

死信队列

DLX,全称为Dead-Letter-Exchange,死信交换器。消息在一个队列中变成死信(Dead Letter)之后,被重新发送到一个特殊的交换器(DLX)中,同时,绑定DLX的队列就称为“死信队列”。

变成死信队列的原因

  • 1.消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false

  • 2.消息过期

  • 3.队列达到最大长度

死信队列的好处

对于RabbitMQ来讲DLX是一个非常有用的特性。它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了Basic.Nack或者Basic.Reject)而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统。

原声API案例

public static void main(String[] args) throws IOException {
    Connection connection = RabbitUtils.getConnection();
    try {
        Channel channel = connection.createChannel();
        //声明死信交换机
        channel.exchangeDeclare("ex.dlx", "direct", true);
        //声明死信队列
        channel.queueDeclare("queue.dlx", true, false, false, null);
        //绑定死信交换器和死信队列
        channel.queueBind("queue.dlx", "ex.dlx", "key.dlx");
        HashMap<String, Object> arguments = new HashMap<>(3);
        //消息队列中消息过期时间,30s
        arguments.put("x-message-ttl", 10 * 1000);
        // arguments.put("x-expires", 60 * 60 * 1000);
        //设置该队列所关联的死信交换器(当队列消息TTL 到期后依然没有消费,则加入死信队列)
        arguments.put("x-dead-letter-exchange", "ex.dlx");
        //设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的 routingKey
        arguments.put("x-dead-letter-routing-key", "key.dlx");
        //正常业务的交换器
        channel.exchangeDeclare("ex.biz", "direct", true);
        //声明正常的队列
        channel.queueDeclare("queue.biz", true, false, false, arguments);
        //绑定业务交换机和正常队列
        channel.queueBind("queue.biz", "ex.biz", "key.biz");
        String message = "orderId.45789987678";
        channel.basicPublish("ex.biz", "key.biz", null, message.getBytes());
       // channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

  //  connection.close();

}