RabbitMQ-TTL机制

RabbitMQ的TTL机制

在这里插入图片描述

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

那如何实现呢?

8.1 定期轮询(数据库等)

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

优点:设计实现简单

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

8.2 定时操作-Timer

  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

  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操作

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

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

启动生产者

检查队列的信息

[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 │ 10001000         │
└────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

经过10秒后

[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 │ 0000         │
└────────┴────────────────┴─────────────────────────┴──────────┴───────────┘

再经过10秒后

[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秒后,队列就被清空了。

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

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

rabbitmqctl set_policy TTL ".*" '{"message-ttl": 9000}' --apply-to queues

观察队列的policy信息

未设置策略前

[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 │ 0000         │        │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┴────────┘

设置策略后

[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 │ 0000         │ TTL    │
└───────────────┴────────────────┴─────────────────────────┴──────────┴───────────┴────────┘

默认规则:

  1. 如果不设置TTL,则表示此消息不会过期;
  2. 如果TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃。
<think>我们正在使用RabbitMQ的延时插件`rabbitmq-delayed-message-exchange`,该插件允许我们使用一种新的交换机类型(x-delayed-message)来实现延时队列,从而避免使用死信队列+TTL带来的队头阻塞问题。 在Dubbo环境中安装该插件,实际上与Dubbo本身无关,因为Dubbo是一个RPC框架,而RabbitMQ是独立的消息中间件。因此,安装插件是在RabbitMQ服务端进行的。我们假设RabbitMQ是以Docker容器方式运行的(这是常见部署方式)。 ### 步骤1:下载插件 首先,我们需要下载插件。注意:插件的版本必须与RabbitMQ的版本兼容。可以从以下地址下载: https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases 例如,对于RabbitMQ 3.7.x,可以下载`rabbitmq_delayed_message_exchange-20171201-3.7.x.ez`。 ### 步骤2:将插件复制到容器中 假设RabbitMQ的容器名称为`rabbitmq`(通过`docker ps`查看实际名称),我们将插件文件复制到容器的插件目录中。 ```bash docker cp rabbitmq_delayed_message_exchange-20171201-3.7.x.ez rabbitmq:/plugins ``` ### 步骤3:在容器内启用插件 进入容器内部,启用插件: ```bash docker exec -it rabbitmq bash ``` 在容器内执行: ```bash rabbitmq-plugins enable rabbitmq_delayed_message_exchange ``` ### 步骤4:重启RabbitMQ容器 退出容器并重启: ```bash docker restart rabbitmq ``` ### 步骤5:验证插件 重新进入容器,查看插件列表: ```bash docker exec -it rabbitmq rabbitmq-plugins list ``` 应该能看到`rabbitmq_delayed_message_exchange`插件已启用。 ### 在Dubbo项目中使用延时交换机 安装插件后,我们就可以在Dubbo服务中(生产者或消费者端)通过RabbitTemplate发送延时消息了。注意:这里我们使用`x-delayed-message`类型的交换机。 #### 配置延时交换机(Java Config) ```java @Configuration public class DelayedRabbitConfig { public static final String DELAYED_EXCHANGE = "delayed.exchange"; public static final String DELAYED_QUEUE = "delayed.queue"; public static final String DELAYED_ROUTING_KEY = "delayed.routingkey"; // 声明延时交换机(自定义类型) @Bean public CustomExchange delayedExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); // 底层交换机的类型,可以是direct, topic, fanout等 return new CustomExchange(DELAYED_EXCHANGE, "x-delayed-message", true, false, args); } // 声明队列 @Bean public Queue delayedQueue() { return new Queue(DELAYED_QUEUE); } // 绑定 @Bean public Binding bindingDelayedQueue(Queue delayedQueue, CustomExchange delayedExchange) { return BindingBuilder.bind(delayedQueue) .to(delayedExchange) .with(DELAYED_ROUTING_KEY) .noargs(); } } ``` #### 发送延时消息 ```java rabbitTemplate.convertAndSend(DelayedRabbitConfig.DELAYED_EXCHANGE, DelayedRabbitConfig.DELAYED_ROUTING_KEY, message, msg -> { // 设置延时时间(毫秒) msg.getMessageProperties().setHeader("x-delay", 30 * 60 * 1000); // 30分钟 return msg; }); ``` ### 注意事项 1. **插件兼容性**:确保插件版本与RabbitMQ版本匹配,否则可能导致RabbitMQ无法启动。 2. **交换机类型**:创建交换机时,类型必须为`x-delayed-message`,并且通过参数指定底层交换机类型(如direct)。 3. **延时时间**:发送消息时,通过消息头`x-delay`设置延时时间(毫秒)。 4. **队列声明**:延时交换机的绑定队列无需特殊参数,与普通队列一致。 通过上述步骤,我们在Dubbo环境中集成了RabbitMQ的延时队列插件,可以实现更精确的延时消息发送[^3]。这种方案避免了传统死信队列的队头阻塞问题,每条消息都可以有自己的延时时间。 §§相关问题§§ 1. RabbitMQ延时队列插件与死信队列方案有什么区别? 2. 如何确保RabbitMQ插件安装后的高可用性? 3. 在分布式事务中如何可靠地发送延时消息? </think>### Dubbo 环境中安装 RabbitMQ 延时插件指南 #### 一、安装插件核心步骤 1. **下载插件** 根据 RabbitMQ 版本下载对应插件(需匹配主要版本号): ```bash # RabbitMQ 3.8.x 版本 wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.8.17/rabbitmq_delayed_message_exchange-3.8.17.8f537ac.ez ``` 2. **复制插件到容器** 通过 `docker cp` 将插件复制到容器内: ```bash docker cp rabbitmq_delayed_message_exchange-3.8*.ez rabbitmq-container:/plugins ``` 3. **启用插件** 进入容器执行启用命令: ```bash docker exec -it rabbitmq-container bash rabbitmq-plugins enable rabbitmq_delayed_message_exchange ``` 4. **重启服务** ```bash docker restart rabbitmq-container ``` #### 二、Dubbo 项目集成配置 在 Dubbo 服务中配置延时交换机(XML 方式): ```xml <!-- 1. 声明延时交换机 --> <rabbit:exchange name="delayed.exchange" type="x-delayed-message"> <rabbit:exchange-arguments> <!-- 底层实际交换类型 --> <entry key="x-delayed-type" value="direct" /> </rabbit:exchange-arguments> </rabbit:exchange> <!-- 2. 绑定队列 --> <rabbit:queue name="dubbo.delay.queue" /> <rabbit:binding queue="dubbo.delay.queue" exchange="delayed.exchange" key="delay.key" /> ``` #### 三、Dubbo 服务发送延时消息 ```java @Service public class OrderServiceImpl implements OrderService { @Autowired private RabbitTemplate rabbitTemplate; public void createOrder(Order order) { // Dubbo 服务方法内发送延时消息 rabbitTemplate.convertAndSend("delayed.exchange", "delay.key", order, message -> { // 设置30分钟延迟(单位毫秒) message.getMessageProperties().setHeader("x-delay", 30 * 60 * 1000); return message; }); } } ``` #### 四、验证插件安装成功 ```bash # 查看已启用插件列表 docker exec rabbitmq-container rabbitmq-plugins list # 预期输出应包含 [E*] rabbitmq_delayed_message_exchange ``` #### 五、注意事项 1. **版本兼容性** - RabbitMQ 3.7.x 需使用插件版本 `3.7.*` - RabbitMQ 3.8.x 需使用 `3.8.*` - 版本不匹配会导致启动失败[^3] 2. **集群环境** 集群中所有节点需同步安装插件: ```bash # 将插件复制到所有节点 for node in node1 node2 node3; do docker cp plugin.ez ${node}:/plugins done ``` 3. **消息可靠性** Dubbo 服务需添加生产者确认机制确保消息投递: ```yaml spring: rabbitmq: publisher-confirm-type: correlated publisher-returns: true ``` #### 六、插件原理示意图 ```mermaid graph LR A[Dubbo服务] -->|发送延时消息| B[x-delayed-message交换机] B -->|消息暂存| C[RabbitMQ内部存储] C -->|到期后投递| D[目标队列] D -->|消费| E[Dubbo消费者服务] ``` > 此方案直接使用 RabbitMQ 官方插件,避免了传统 TTL+DLX 方案的队头阻塞问题,每条消息可独立设置延迟时间[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值