RabbitMQ延迟队列

  • 什么是延时队列
  • 延时队列的使用场景
  • RabbitMQ实现延时队列
  • 1. 利用TTL DLX实现延时队列
  • 2. 利用插件(rabbitmq_delayed_message_exchange)实现延时队列
  • 1. 引入依赖
  • 2. 创建配置类
  • 3. 消费端代码
  • 4. 生产端代码


什么是延时队列

延时队列是用于存放需要在指定时间被处理的元素的队列,简单来说,就是放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。

延时队列的使用场景

  1. 订单在三十分钟之内未支付则自动取消;
  2. 用户发起退款,如果一定时间内没有处理,则发送通知给相关人员;
  3. 预定会议后,需要再会议前三十分钟通知与会人员;

这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务。

RabbitMQ实现延时队列

RabbitMQ 实现延时队列一般而言有两种方式:

  1. 利用两个特性: Time To Live(TTL)、Dead Letter Exchange(DLX)
  2. 利用 RabbitMQ 中的插件 rabbitmq_delayed_message_exchange

1. 利用TTL DLX实现延时队列

  1. Time To Live(TTL):RabbitMQ 可以针对 Queue 设置 x-expires 或者 针对 Message 设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最小时间为准),则消息变为dead letter(死信)。
  2. Dead Letter Exchanges(DLX):通过为队列添加 x-dead-letter-exchange 属性,指定死信交换机,还可通过 x-dead-letter-routing-key 属性配置需要路由到死信队列的Routing key。如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。

2. 利用插件(rabbitmq_delayed_message_exchange)实现延时队列

插件下载地址:https://www.rabbitmq.com/community-plugins.html

延迟队列redis怎么使用 延迟队列使用场景_使用场景


注意下载插件要和安装的rabbitmq版本一致

下载解压后,得到一个 .ez 的压缩文件,找到 rabbitmq 安装目录下的 plugins,将解压的文件复制到该目录下。输入命令启动该插件:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

然后重启 RabbitMQ。

1. 引入依赖

<dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.7.3</version>
</dependency>

2. 创建配置类

public class RabbitMQConfig {
    private RabbitMQConfig() {
    }

    // 主机IP
    public static final String HOST = "127.0.0.1";
    // 主机port
    public static final Integer PORT = 5672;
    // 主机port
    public static final String VHOST = "/";
    // 主机port
    public static final String USERNAME = "admin";
    // 主机port
    public static final String PASSWORD = "admin";
    // 交换机名称
    public static final String DIRECT_EXCHANGE = "delayed_direct_exchange";
    // 队列名称
    public static final String QUEUE_NAME = "delayed_direct_queue";
    // Routing key
    public static final String ROUTING_KEY = "kiss";
}

3. 消费端代码

import com.rabbitmq.client.*;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class MessageConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        // 设置主机IP
        factory.setHost(RabbitMQConfig.HOST);
        // 设置端口
        factory.setPort(RabbitMQConfig.PORT);
        // 设置 Vhost
        factory.setVirtualHost(RabbitMQConfig.VHOST);

        // 设置访问用户
        factory.setUsername(RabbitMQConfig.USERNAME);
        factory.setPassword(RabbitMQConfig.PASSWORD);
        // 建立连接
        Connection connection = factory.newConnection();
        // 创建Channel消息通道
        Channel channel = connection.createChannel();

        // 声明 x-delayed-message 类型的Exchange
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type", "direct");
        channel.exchangeDeclare(RabbitMQConfig.DIRECT_EXCHANGE, "x-delayed-message", false, false, arguments);

        /**
         * 声明队列,参数String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
         * String queue:指定队列名称
         * boolean durable:是否持久化
         * boolean exclusive:是否排他,既是否创建者私有,如果为true,会对当前队列加锁,其他通道不能访问,并且
         * 					 在连接关闭时会自动删除,不受持久化和自动删除限制
         * boolean autoDelete:是否自动删除
         * Map<String, Object> arguments:其他参数
         */
        channel.queueDeclare(RabbitMQConfig.QUEUE_NAME, false, false, false, null);

        /**
         * 绑定交换和队列,参数String queue, String exchange, String routingKey, Map<String, Object> arguments
         */
        channel.queueBind(RabbitMQConfig.QUEUE_NAME, RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, null);

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 获取消息
                String msg = new String(body, "UTF-8");
                System.out.println("message:" + msg + " 消息接收时间" +
                        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
            }
        };

        // 开始获取消息String queue, boolean autoAck, Consumer callback
        channel.basicConsume(RabbitMQConfig.QUEUE_NAME, true, consumer);
    }
}

4. 生产端代码

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 生产者
 */
public class MessageProvider {
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        // 设置主机IP
        factory.setHost(RabbitMQConfig.HOST);
        // 设置端口
        factory.setPort(RabbitMQConfig.PORT);
        // 设置 Vhost
        factory.setVirtualHost(RabbitMQConfig.VHOST);

        // 设置访问用户
        factory.setUsername(RabbitMQConfig.USERNAME);
        factory.setPassword(RabbitMQConfig.PASSWORD);
        try (
                // 建立连接
                Connection connection = factory.newConnection();
                // 创建Channel消息通道
                Channel channel = connection.createChannel();
        ) {
            Map<String, Object> headers = new HashMap<>();
            headers.put("x-delay", 1000);// 延迟的时间间隔
            // 设置属性,消息5秒后过期
            AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                    .headers(headers)
                    .build();

            String msg = "阁下可是常山赵子龙,发送时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
            channel.basicPublish(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, properties, msg.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}