RabbitMQ 实现固定延时等级的延时消息

rabbitmq 实现固定延时等级的延时消息

通过学习rocketmq延时消息的实现(指路:RocketMQ延时消息实现原理探究),我们了解到rocketmq将不同延时等级的消息放入不同queue中,然后转发到目标topic。

对于未使用rocketmq的团队来说,如果只是为了实现固定延时时间而引入一个新的中间件,成本可能比较大,包括学习使用、运维成本等。但我们也可以仿照rocketmq的方式,结合rabbitmq死信队列,使用rabbitmq实现一个类似rocketmq固定延时等级的延时消息功能。

大概介绍

大概实现方式:

  1. 先声明一个topic类型的死信exchange,用来转发死信(延时后)的消息。
  2. 定义一组用来暂存消息的延时delayQueue,并设置死信exchange,死信路由默认为消息原始路由。
  3. 再声明一个topic类型的延时exchange与延时queue绑定,绑定路由为$delayQueueName.#, 即通过routing key前缀识别应转发到哪个延时队列。
  4. 然后声明需要消费的业务队列 bizQueue,再将其绑定到死信exchange上,路由关系为*.$bizQueueName (也可以是正常的routingKey, 这个死信exchange的绑定关系就和普通定义发送消息的定义差不多了)
  5. 最后发送消息时,根据其延时等级,在routingKey前面拼接上对应的 delayQueueName 就可以了。

消息大概流程如下
在这里插入图片描述

代码实现

  1. 先定义一个延时等级的枚举类
    public enum DelayLevelEnum implements DelayLevel {
        SECOND_1(1000, "1s"),
        SECOND_5(5 * 1000, "5s"),
        SECOND_10(10 * 1000, "10s"),
        SECOND_30(30 * 1000, "30s"),
        MINUTE_1(60 * 1000, "1m"),
        MINUTE_2(2 * 60 * 1000, "2m"),
        MINUTE_3(3 * 60 * 1000, "3m"),
        MINUTE_4(4 * 60 * 1000, "4m"),
        MINUTE_5(5 * 60 * 1000, "5m"),
        MINUTE_6(6 * 60 * 1000, "6m"),
        MINUTE_7(7 * 60 * 1000, "7m"),
        MINUTE_8(8 * 60 * 1000, "8m"),
        MINUTE_9(9 * 60 * 1000, "9m"),
        MINUTE_10(10 * 60 * 1000, "10m"),
        MINUTE_20(20 * 60 * 1000, "20m"),
        MINUTE_30(30 * 60 * 1000, "30m"),
        HOUR_1(60 * 60 * 1000, "1h"),
        HOUR_2(2 * 60 * 60 * 1000, "2h")
        ;
    
        private final long delayTimeInMills;
        private final String desc;
    
        DelayLevelEnum(long delayTimeInMills, String desc) {
            this.delayTimeInMills = delayTimeInMills;
            this.desc = desc;
        }
    
        public long getDelayTimeInMills() {
            return delayTimeInMills;
        }
    
        public String getDesc() {
            return desc;
        }
    }
    
    
  2. 为更方便使用,还提供用户自定义延时等级
    public class CustomDelayLevel implements DelayLevel {
    
        private final long delayTimeInMills;
    
        private final String desc;
    
        public CustomDelayLevel(long delayTimeInMills, String desc) {
            this.delayTimeInMills = delayTimeInMills;
            this.desc = desc;
        }
    
        @Override
        public long getDelayTimeInMills() {
            return delayTimeInMills;
        }
    
        @Override
        public String getDesc() {
            return desc;
        }
    }
    
  3. 然后对每个延时等级定义一个延时队列
    private void declareCommonDelayExchangeAndQueues() {
        // 声明 topic exchange 作为延时exchange, 所有消息通过该 exchange 路由到对应延迟的 queue
        TopicExchange delayExchange = new TopicExchange(DELAY_EXCHANGE, true, false);
        rabbitAdmin.declareExchange(delayExchange);
    
        // 延迟后的死信 exchange, 将延迟后的消息转发到对应的业务 queue
        TopicExchange dlxDelayExchange = new TopicExchange(DELAY_EXCHANGE_DLX, true, false);
        rabbitAdmin.declareExchange(dlxDelayExchange);
    
        // 获取用户声明的延时等级bean, 和 默认的延时等级
        Collection<DelayLevel> customDelayLevels = applicationContext.getBeansOfType(DelayLevel.class).values();
        List<DelayLevel> delayLevels = new ArrayList<>(Arrays.asList(DelayLevelEnum.values()));
        delayLevels.addAll(customDelayLevels);
    
        // 声明各个延时等级的 queue, 及绑定 delay_exchange
        for (DelayLevel level : delayLevels) {
            Map<String, Object> params = new HashMap<>();
            params.put("x-message-ttl", level.getDelayTimeInMills());
            params.put("x-dead-letter-exchange", DELAY_EXCHANGE_DLX);       // 指定死信exchange,  不指定死信routingKey
            params.put("x-queue-mode", "lazy");     // 声明为惰性队列, 避免消息积压占用大量内存
            Queue queue = new Queue(getDelayQueueName(level), true, false, false, params);
            rabbitAdmin.declareQueue(queue);
    
            Binding binding = new Binding(queue.getName(), Binding.DestinationType.QUEUE, DELAY_EXCHANGE, queue.getName() + ".#", Collections.emptyMap());
            rabbitAdmin.declareBinding(binding);
        }
    }
    
    public static String getDelayQueueName(DelayLevelEnum delayLevelEnum) {
        return DELAY_QUEUE_PREFIX + delayLevelEnum.getDesc();
    }
    
    
  4. 声明业务队列并绑定死信exchange
    其实这部分也可以自己手动声明和绑定,不过这里也提供一个声明队列并自动绑定死信exchange的方式, 方便使用
    /**
     * 延时后被投递的业务队列, 提供给业务方消费
     */
    public class DelayConsumQueue extends Queue {
    
        /**
         * 与死信exchange 绑定的 routing key, 默认为 queueName
         */
        private String bindRoutingKey;
    
        public DelayConsumQueue(String name) {
            super(name);
        }
    
        public DelayConsumQueue(String name, boolean durable) {
            super(name, durable);
        }
    
        public DelayConsumQueue(String name, boolean durable, boolean exclusive, boolean autoDelete) {
            super(name, durable, exclusive, autoDelete);
        }
    
        public DelayConsumQueue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) {
            super(name, durable, exclusive, autoDelete, arguments);
        }
    
        public String getBindRoutingKey() {
            return bindRoutingKey;
        }
    
        public void setBindRoutingKey(String bindRoutingKey) {
            this.bindRoutingKey = bindRoutingKey;
        }
    }
    
  5. 然后在config中,获取到这些队列的bean,定义queue并绑定死信exchange
    private void declareAndBindingBizQueues() {
        Collection<DelayConsumQueue> bizQueues = applicationContext.getBeansOfType(DelayConsumQueue.class).values();
        
        // 声明业务队列,并绑定
        for (DelayConsumQueue queue : bizQueues) {
    
            rabbitAdmin.declareQueue(queue);
    
            String bindingRoutingKey = queue.getBindRoutingKey();
            if (bindingRoutingKey == null || bindingRoutingKey.isEmpty()) {
                bindingRoutingKey = queue.getName();
            }
            Binding binding = new Binding(queue.getName(), Binding.DestinationType.QUEUE, DELAY_EXCHANGE_DLX, "*." + bindingRoutingKey, Collections.emptyMap());
            rabbitAdmin.declareBinding(binding);
        }
    }
    
  6. 最后定义一个发送延时消息的类就可以了
    public class RabbitDelayMsgSender {
        
        private RabbitTemplate rabbitTemplate;
    
        public RabbitDelayMsgSender(RabbitTemplate rabbitTemplate) {
            this.rabbitTemplate = rabbitTemplate;
        }
    
        /**
         * @param routingKey 被死信exchange转发时的routingKey, 默认为 consumeQueueName
         */
        public void sendDelayMessage(String routingKey, Object message, DelayLevelEnum delayLevelEnum) {
            String delayRoutingKey = getDelayQueueName(delayLevelEnum) + "." + routingKey;
            rabbitTemplate.convertAndSend(DELAY_EXCHANGE, delayRoutingKey, message);
        }
    
    
        public void send(String routingKey, Message message, DelayLevelEnum delayLevelEnum) {
            String delayRoutingKey = getDelayQueueName(delayLevelEnum) + "." + routingKey;
            rabbitTemplate.send(DELAY_EXCHANGE, delayRoutingKey, message);
        }
    }
    

大概就完成了,使用的话,大概如下

/**
 * 定义一个消费队列,用来接收延时后的消息提供业务消费
 */
@Bean("testQueue")
public DelayConsumQueue testQueue(){
    return new DelayConsumQueue("test-delay");
}

/**
 * 自定义一个延时等级 15s
 */
@Bean("delayLevel15s")
public DelayLevel delayLevel15s() {
    return new CustomDelayLevel(15000, "15s");
}


/**
 * 测试发送延时消息
 */

@Resource(name = "delayLevel15s")
private DelayLevel delayLevel15;

@Test
public void testDelaySend() {

    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    String msg = "message === " + sf.format(new Date());

    rabbitDelayMsgSender.sendDelayMessage("test-delay", msg, DelayLevelEnum.SECOND_5);

    rabbitDelayMsgSender.sendDelayMessage("test-delay", msg, delayLevel15);
}

源码地址:https://github.com/wsJava/rabbitmq-delay

简单分析

仿照rocketmq的方式,在发送消息时,先将其转存到对应延时等级的队列中,然后在等待消息到期(死亡)后,经由死信exchange再转发到对应的业务队列中提供消费。

优点:

  • 支持同一业务消息使用不同的延时时间
  • 基于rabbitmq简单封装实现,支持镜像队列复制,实现高可用
  • 且延时等级方便修改增加,以适应业务需求
  • 使用lazy-mode,且消息默认持久化消息,延时队列发生堆积时不会导致磁盘换页。

缺点:

  • 仍旧只支持固定延时等级
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Spring Boot 中,你可以通过 RabbitMQ 的 `x-delayed-message` 插件来实现延时队列,而不需要使用额外的插件或库。下面是实现步骤: 1. 添加依赖 在 `pom.xml` 文件中添加 RabbitMQ 的依赖: ```xml <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>2.3.12.RELEASE</version> </dependency> ``` 2. 配置 RabbitMQ 的 `x-delayed-message` 插件 在 RabbitMQ 中,你需要先安装 `x-delayed-message` 插件。你可以通过 `rabbitmq-plugins` 命令来安装插件: ``` rabbitmq-plugins enable rabbitmq_delayed_message_exchange ``` 或者,你可以在 `rabbitmq.conf` 文件中添加以下配置,然后重启 RabbitMQ: ``` plugins.rabbitmq_delayed_message_exchange = {git, "https://github.com/rabbitmq/rabbitmq-delayed-message-exchange", {branch, "master"}} ``` 3. 配置 RabbitMQ 的连接信息 在 `application.properties` 中添加 RabbitMQ 的连接信息: ```properties spring.rabbitmq.host=your-rabbitmq-host spring.rabbitmq.port=5672 spring.rabbitmq.username=your-rabbitmq-username spring.rabbitmq.password=your-rabbitmq-password ``` 4. 定义队列和交换器 在 Spring Boot 中,你可以使用 `@Configuration` 和 `@Bean` 注解来定义队列和交换器。下面是一个例子: ```java @Configuration public class RabbitConfig { @Bean public Queue delayedQueue() { return QueueBuilder.durable("delayed.queue") .withArgument("x-dead-letter-exchange", "normal.exchange") .withArgument("x-dead-letter-routing-key", "normal.routingkey") .build(); } @Bean public CustomExchange delayedExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange("delayed.exchange", "x-delayed-message", true, false, args); } @Bean public Binding binding() { return BindingBuilder.bind(delayedQueue()) .to(delayedExchange()) .with("delayed.routingkey") .noargs(); } } ``` 在上面的例子中,我们定义了一个 `delayedQueue` 队列,它的死信交换器是 `normal.exchange`,死信路由键是 `normal.routingkey`。我们还定义了一个 `delayedExchange` 交换器,它的类型是 `x-delayed-message`,并将 `x-delayed-type` 属性设置为 `direct`。最后,我们将 `delayedQueue` 队列绑定到 `delayedExchange` 交换器上,并使用路由键 `delayed.routingkey`。 5. 发送延时消息 你可以使用 `RabbitTemplate` 类来发送消息到 `delayedQueue` 队列。在发送消息时,你需要将消息的 `headers` 属性设置为 `x-delay`,并将值设置为消息延时时间(单位为毫秒)。 ```java @Autowired private RabbitTemplate ### 回答2: 在Spring Boot中实现RabbitMQ延时队列需要以下几个步骤: 1. 首先,我们需要定义一个交换机(Exchange),用于将消息发送到延时队列中。可以使用DirectExchange、TopicExchange或FanoutExchange等不同类型的交换机。交换机的类型根据具体的业务需求而定。 2. 接下来,我们需要定义两个队列,一个为延时队列,另一个为业务队列。延时队列用于接收需要延时处理的消息,业务队列用于接收延时队列中处理完成的消息。 3. 创建并配置消息发送和接收的相关组件。使用RabbitTemplate来发送消息延时队列,创建一个消费者来接收延时队列中的消息并处理。 4. 在消息发送时,可以通过给消息设置不同的过期时间来实现延时功能。在发送消息时,将消息携带的延时时间设置为过期时间,然后发送到延时队列中。 5. 在消费者中,监听业务队列,当接收到延时队列中的消息时,进行相应的处理,例如发送邮件、生成报表等。 这样就实现RabbitMQ延时队列的功能。通过设置消息的过期时间,可以控制消息何时被消费。延时队列可以在某个特定的时间点将消息转发到业务队列,完成后续处理。Spring Boot提供了简单而强大的集成,可以轻松实现延时队列的功能。 ### 回答3: 实现RabbitMQ延时队列的核心思想是利用RabbitMQ的插件(x-delayed-message)和Spring Boot的消息中间件(RabbitTemplate)结合使用。 首先,确保在RabbitMQ服务中安装了插件。在RabbitMQ的安装目录下,执行以下命令: ``` rabbitmq-plugins enable rabbitmq_delayed_message_exchange ``` 接下来,在Spring Boot项目的pom.xml文件中添加RabbitMQ的依赖: ```xml <dependencies> <!-- RabbitMQ --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> </dependencies> ``` 然后,创建一个配置类,用于连接RabbitMQ服务和创建延时队列: ```java @Configuration public class RabbitMQConfig { @Autowired private Environment env; @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); connectionFactory.setAddresses(env.getProperty("spring.rabbitmq.addresses")); connectionFactory.setUsername(env.getProperty("spring.rabbitmq.username")); connectionFactory.setPassword(env.getProperty("spring.rabbitmq.password")); return connectionFactory; } @Bean public RabbitTemplate rabbitTemplate() { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); rabbitTemplate.setMessageConverter(jsonMessageConverter()); return rabbitTemplate; } @Bean public MessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } @Bean public Exchange delayedExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange("delayed-exchange", "x-delayed-message", true, false, args); } @Bean public Queue delayedQueue() { return new Queue("delayed-queue", true); } @Bean public Binding delayedBinding() { return BindingBuilder.bind(delayedQueue()).to(delayedExchange()).with("delayed-routing-key").noargs(); } } ``` 在上述代码中,我们创建了一个自定义的Exchange,将其类型设置为"x-delayed-message",并创建了一个延时队列,将其绑定在这个Exchange上。这样,消息发送到这个Exchange时,会根据消息中的延时时间属性进行延时处理。 最后,我们可以通过RabbitTemplate发送延时消息: ```java @Service public class RabbitMQService { @Autowired private RabbitTemplate rabbitTemplate; public void sendDelayedMessage(String message, int delayTime) { rabbitTemplate.convertAndSend("delayed-exchange", "delayed-routing-key", message, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setHeader("x-delay", delayTime); return message; } }); } } ``` 在上述代码中,我们通过rabbitTemplate将消息发送到名为"delayed-exchange"的Exchange上,并设置消息延时时间属性"x-delay"。最后,通过"convertAndSend"方法发送消息。 以上就是使用Spring Boot实现RabbitMQ延时队列的简单示例。通过这种方式,我们可以轻松地实现消息延时处理,使得系统更加灵活和高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值