RabbitMQ-TTL机制

RabbitMQ的TTL机制

在支付类的场景中,创建订单成功后,一般会给30分钟左右的等待时间,如果在这段时间内用户没有支付,则默认订单取消。

那如何实现呢?

8.1 定期轮询(数据库等)

用户下单成功,将订单信息放入数据库,同时将支付状态放入数据库,用户付款更改数据库状态。定期轮询数据库支付状态,如果超过30分钟就将该订单取消。

优点:设计实现简单

缺点:需要对数据库进行大量的IO操作,效率低下。

8.2 定时操作-Timer

java 复制代码
  public void timerTask() throws Exception {
    Timer timer = new Timer();

    TimerTask task =
        new TimerTask() {
          @Override
          public void run() {
            LocalDateTime outTime = LocalDateTime.now();
            System.out.println("用户没有付款,交易取消:" + outTime);
            timer.cancel();
          }
        };

    System.out.println("等待用户付款:" + LocalDateTime.now());
    // 在20秒后执行定时调用
    timer.schedule(task, 5 * 1000);

    Thread.sleep(7000);
  }

缺点:

  1. timers没有持久化机制。
  2. timers不灵活(只可以设置开始时间与重复间隔,但对于等待支付的场景是够用的)
  3. timers不能够利用池,一个timer一个线程。
  4. times没有真正的管理计划。

8.3 使用ScheduledExecutorService

java 复制代码
  public void scheduleExecute() throws Exception {
    ThreadFactory factory = Executors.defaultThreadFactory();
    ScheduledExecutorService service = new ScheduledThreadPoolExecutor(10, factory);

    System.out.println("等待用户付款:" + LocalDateTime.now());

    service.schedule(
        new Runnable() {
          @Override
          public void run() {
            LocalDateTime outTime = LocalDateTime.now();
            System.out.println("用户没有付款,交易取消:" + outTime);
          }
        },
        // 等待5秒
        5,
        TimeUnit.SECONDS);

    Thread.sleep(7000);
  }

优点:可以多线程执行,一定程序上避免任何任务间的想到影响,单个任务异常不影响其他任务。

缺点:在高并情况下,不建议使用定时任务去做,因为太浪费服务器性能。不推荐。

8.4 RabbitMQ的TTL机制

TTL,time to live 的简称,即过期时间。

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

任何消息中间件的容量和堆积都是有限的,如果有一些消息总是不被消费掉,那么需要有一种过期的机制来做兜底。

目前有两种方法可以设置消息的TTL。

  1. 通过Queue属性设置,队列中所有的消息使用相同的过期时间。
  2. 对消息自身进行单独的设置。每条消息的TTL可以不同。

如果两种方法一起使用,则消息的TTL以两者之间较小数值为准,通常来说,消息在队列中的生存时间一旦超过设置的TTL值时,就会变更"死信(Dead Message)",消费者默认就无法再收到该消息。当然,"死信"也是可以被取出来消费的。

使用原生API操作

在队列上设置消息的过期时间

java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class ProductTTL {
  public static void main(String[] args) throws Exception {

    ConnectionFactory factory = new ConnectionFactory();

    factory.setUri("amqp://root:123456@node1:5672/%2f");

    try (Connection connection = factory.newConnection();
        Channel channel = connection.createChannel(); ) {
      // 定义相关的参数
      Map<String, Object> param = new HashMap<>();

      // 设置队列的TTL,即此消息10秒后过期,
      param.put("x-message-ttl", 10000);
      // 设置队列的空闲时间,(如果队列没有消费者或者一直没有使用,队列可存活的时间)
      // 可以理解为没有消费者时,消息队列20秒后删除。
      param.put("x-expires", 20000);

      channel.queueDeclare("ttl.qu", false, false, false, param);

      for (int i = 0; i < 100; i++) {
        String sendMsg = "this is test msg :" + i;
        channel.basicPublish("", "ttl.qu", null, sendMsg.getBytes(StandardCharsets.UTF_8));
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

启动生产者

检查队列的信息

sh 复制代码
[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name   │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ttl.qu │ 100            │ 0                       │ 100      │ 0         │
└────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

经过10秒后

sh 复制代码
[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌────────┬────────────────┬─────────────────────────┬──────────┬───────────┐
│ name   │ messages_ready │ messages_unacknowledged │ messages │ consumers │
├────────┼────────────────┼─────────────────────────┼──────────┼───────────┤
│ ttl.qu │ 0              │ 0                       │ 0        │ 0         │
└────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

再经过10秒后

sh 复制代码
[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...

经过操作后可以发现,此消息队列中的消息存活了10秒,然后消息就被清空了,由于队列的存活时间设置为20秒,没有消费者,所以经过了20秒后,队列就被清空了。

还可以将消息指定在消息中,效果相同

java 复制代码
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class ProductTTLMsg {
  public static void main(String[] args) throws Exception {

    ConnectionFactory factory = new ConnectionFactory();
    factory.setUri("amqp://root:123456@node1:5672/%2f");
    try (Connection connection = factory.newConnection();
        Channel channel = connection.createChannel(); ) {
      // 定义相关的参数
      Map<String, Object> param = new HashMap<>();
      // 设置队列的空闲时间,(如果队列没有消费者或者一直没有使用,队列可存活的时间)
      // 可以理解为没有消费者时,消息队列20秒后删除。
      param.put("x-expires", 20000);
      channel.queueDeclare("ttl.qu", false, false, false, param);

      for (int i = 0; i < 100; i++) {
        String sendMsg = "this is test msg :" + i;
        // 在消息上指定存活时间为8秒
        AMQP.BasicProperties properties =
            new AMQP.BasicProperties().builder().expiration("8000").build();
        channel.basicPublish("", "ttl.qu", properties, sendMsg.getBytes(StandardCharsets.UTF_8));
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

还可以通过命令设置全局的TTL

sh 复制代码
rabbitmqctl set_policy TTL ".*" '{"message-ttl": 9000}' --apply-to queues

观察队列的policy信息

未设置策略前

sh 复制代码
[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers,policy  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┬────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │ policy │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┼────────┤
│ ttl.tmp.queue │ 0              │ 0                       │ 0        │ 0         │        │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┴────────┘

设置策略后

sh 复制代码
[root@nullnull-os rabbitmq]# rabbitmqctl list_queues name,messages_ready,messages_unacknowledged,messages,consumers,policy  --formatter pretty_table
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
┌───────────────┬────────────────┬─────────────────────────┬──────────┬───────────┬────────┐
│ name          │ messages_ready │ messages_unacknowledged │ messages │ consumers │ policy │
├───────────────┼────────────────┼─────────────────────────┼──────────┼───────────┼────────┤
│ ttl.tmp.queue │ 0              │ 0                       │ 0        │ 0         │ TTL    │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┴────────┘

默认规则:

  1. 如果不设置TTL,则表示此消息不会过期;
  2. 如果TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃。
相关推荐
2501_9411474213 分钟前
基于 Kotlin 与 Ktor 构建高并发微服务与异步分布式系统实践分享
rabbitmq
z***897124 分钟前
【分布式】Hadoop完全分布式的搭建(零基础)
大数据·hadoop·分布式
隐语SecretFlow1 小时前
【隐语Serectflow】基于隐私保护的分布式数字身份认证技术研究及实践探索
分布式
回家路上绕了弯2 小时前
支付请求幂等性设计:从原理到落地,杜绝重复扣款
分布式·后端
小马爱打代码2 小时前
SpringBoot + Quartz + Redis:分布式任务调度系统 - 从架构设计到企业级落地
spring boot·redis·分布式
debug骑士5 小时前
面向云原生微服务的Go高并发架构实践与性能优化工程化经验分享案例研究
rabbitmq
无心水5 小时前
【分布式利器:限流】3、微服务分布式限流:Sentinel集群限流+Resilience4j使用教程
分布式·微服务·架构·sentinel·分布式限流·resilience4j·分布式利器
2501_941802486 小时前
Java高性能微服务架构与Spring Boot实战分享:分布式服务设计、负载均衡与优化经验
rabbitmq
一起学开源6 小时前
分布式基石:CAP定理与ACID的取舍艺术
分布式·微服务·架构·流程图·软件工程
雁于飞6 小时前
分布式基础
java·spring boot·分布式·spring·wpf·cloud native