Springboot集成rabbitmq 实现延时队列

        延时队列的需求出现在消息需要延时处理的场景。比如下单之后在30分钟内不支付,超过时间,我们应该去修改订单的状态变成已取消,收回库存;或者设置了某个事情不需要现在处理,要一定时间段后去处理,这种情况都需要用到延时队列;

        延时队列的实现方式有很多中,这里只介绍rabbitmq的实现方式;之所以rabbitmq可以做为延时队列是跟他的两个特性分不开的;

1>Time To Live(TTL)

rabbitmq可以针对队列或者消息设置过期时间,如果超过时间,消息变为dead letter(死信);如果两者同时设置,按照先到期的时间为准;

有两种方式,针对队列设置,那么队列中的消息的超时时间都是一样的;另外一种可以针对每条消息设置,每条消息的ttl值都不相同;

2>Dead Letter Exchanges(DLX)

RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。

x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

因此可以综合 这两个特点,设置了TTL规则之后当消息在一个队列中变成死信时,利用DLX特性它能被重新转发到另一个Exchange或者Routing Key,这时候消息就可以重新被消费了。流程如图所示

Springboot集成rabbitmq 实现延时队列_第1张图片

 

下面,结合Springboot来具体实现这两种方式;

1>先介绍针对队列设置ttl值的情况

首先定义配置类,在里面定义两个队列,一个是ttl过期的队列,一个是过期后转发到exchange然后处理过期消息的队列;代码如下所示

@Configuration
public class DelayQueueConfig {
     public final static String DELAY_QUEUE_PER_QUEUE_TTL_NAME = "delay_queue_per_queue_ttl";

    public final static String DELAY_PROCESS_QUEUE_NAME = "delay_process_queue";

    public final static String DELAY_EXCHANGE_NAME = "delay_exchange";

    public final static int QUEUE_EXPIRATION = 4000;

    @Bean
    Queue delayQueuePerQueueTTL() {
        return QueueBuilder.durable(DELAY_QUEUE_PER_QUEUE_TTL_NAME)
            .withArgument("x-dead-letter-exchange", DELAY_EXCHANGE_NAME) // DLX
            .withArgument("x-dead-letter-routing-key", DELAY_PROCESS_QUEUE_NAME) // dead letter携带的routing key
            .withArgument("x-message-ttl", QUEUE_EXPIRATION) // 设置队列的过期时间
            .build();
    }

    @Bean
    Queue delayProcessQueue() {
        return QueueBuilder.durable(DELAY_PROCESS_QUEUE_NAME)
            .build();
    }

    @Bean
    DirectExchange delayExchange() {
        return new DirectExchange(DELAY_EXCHANGE_NAME);
    }

    @Bean
    Binding dlxBinding(Queue delayProcessQueue, DirectExchange delayExchange) {
        return BindingBuilder.bind(delayProcessQueue)
            .to(delayExchange)
            .with(DELAY_PROCESS_QUEUE_NAME);
    }
}

通过x-dead-letter-exchange设置队列的死信路由,那么出现dead letter之后将dead letter重新发送到指定exchange;

通过x-dead-letter-routing-key设置路由键:出现dead letter之后将dead letter重新按照指定的routing-key发送;

通过x-message-ttl设置队列的过期时间;

后面设置了处理队列,并且将路由和队列绑定起来;

下面是消息的发送和接收处理消息的代码

Springboot集成rabbitmq 实现延时队列_第2张图片

这里sleep的目的是单元测试,防止消息发送完,线程结束了,后面收不到消息;

接收方代码

Springboot集成rabbitmq 实现延时队列_第3张图片

下面是打印的日志:

通过日志可以看到,确实是4s之后队列接收并处理了消息;

2>针对每条消息不同的过期时间的情况

首先还是队列的配置,这里定义了一个新的队列,跟上面比较相似,只是少了过期的时间,代码如下所示

Springboot集成rabbitmq 实现延时队列_第4张图片

然后是消息的发送方

Springboot集成rabbitmq 实现延时队列_第5张图片

这里自定义了ExpirationMessagePostProcessor,来设置消息属性的ttl值,如下所示

Springboot集成rabbitmq 实现延时队列_第6张图片

上面的代码发送4条消息,消息的过期时间分别是0,1s,2s,3s;接收方不变,依然是负责处理DelayQueueConfig.DELAY_PROCESS_QUEUE_NAME的消息;运行单元测试,结果如下所示

Springboot集成rabbitmq 实现延时队列_第7张图片

根据时间,可以发现消息的过期时间分别是0,1s,2s,3s;

综上,有两种设置消息过期时间的方式;一种是队列统一设置过期时间,另外一种是针对每个消息单独的过期时间,相对灵活一些;

在生产中还有一种情况就是延时重试,比如有时候因为网络的波动一些操作没有成功,那么不会立刻重试,这样一般来说还是失败的,我们希望可以过段时间进行重试,那么这时候就需要延时重试;流程如下图所示

Springboot集成rabbitmq 实现延时队列_第8张图片

这里消费者处理失败之后重新发送到缓冲队列里面,时间到期之后放到消费队列里面去重新执行;代码实现如下

这里首先消息发送到正常的消息处理队列中,消息体是自定义的一个字符;下面是消息的接受方

Springboot集成rabbitmq 实现延时队列_第9张图片

消息接收方根据消息体的内容,发送到延时队列中;超过时间之后又重新发送到正常的队列中去处理;下面是执行效果

Springboot集成rabbitmq 实现延时队列_第10张图片

首先发送了3条消息,消费者在同一时间收到之后处理异常了,过了指定的时间4s之后,统一又处理一次;这样就达到延时重试的目的;跟上面延时处理的区别是:

延时处理是消息发送到一个延时队列里面,超过时间,转发到正常队列去处理;

延时重试是消息发送到正常队列处理,然后处理失败,又发到延时队列里面,这个延时时间过了,又流转到正常队列中了;

 

 

你可能感兴趣的:(基础知识,springboot)