RabbitMQ进阶_延迟消息


  在电商的支付业务中,对于一些库存有限的商品,为了更好的用户体验,通常都会在用户下单时立刻扣减商品库存。例如电影院购票、高铁购票,下单后就会锁定座位资源,其他人无法重复购买。但是这样就存在一个问题,假如用户下单后一直不付款,就会一直占有库存资源,导致其他客户无法正常交易,最终导致商户利益受损!因此,电商中通常的做法就是: 对于超过一定时间未支付的订单,应该立刻取消订单并释放占用的库存

  例如,订单支付超时时间为30分钟,则我们应该在用户下单后的第30分钟检查订单支付状态,如果发现未支付,应该立刻取消订单,释放库存。

  但问题来了:如何才能准确的实现在下单后第30分钟去检查支付状态呢?像这种在一段时间以后才执行的任务,我们称之为延迟任务,而要实现延迟任务,最简单的方案就是利用MQ的延迟消息了。在RabbitMQ中实现延迟消息也有两种方案:

  • 死信交换机+TTL
  • 延迟消息插件

一、 死信交换机和延迟消息

1.1、 死信交换机

什么是死信?当一个队列中的消息满足下列情况之一时,可以称为死信(dead letter):

  • 消费者使用basic.rejectbasic.nack声明消费失败,并且消息的requeue参数设置为false。
  • 消息是一个过期消息,超时无人消费。
  • 要投递的队列消息满了,无法投递。

  如果一个队列中的消息已经成为死信,并且这个队列通过dead-letter-exchange属性指定了一个交换机,那么队列中的死信消息就会投递到这个交换机中,而这个交换机就称为死信交换机。而此时假如有队列与死信交换机绑定,则最终死信消息就会被投递到这个队列中。死信交换机有什么作用呢?

  1. 收集那些因处理失败而被拒绝的消息。
  2. 收集那些因队列满了而被拒绝的消息。
  3. 收集因TTL(有效期)到期的消息。

1.2、 延迟消息

在这里插入图片描述
  如上图所示,有一组绑定的交换机(simple.direct)和队列(simple.queue)。但是simple.queue没有消费者监听,而是设定了死信交换机dlx.direct,而队列simple.queue则与死信交换机绑定。

  1. 假如我们现在发送一条消息到simple.direct,并设置消息的有效期为30s。
  2. 消息肯定会被投递到simple.queue之后,由于没有消费者,因此消息无人消费。30s之后,消息的有效期到期,成为死信。
  3. 死信被再次投递到死信交换机dlx.direct
  4. 最终消息被成功路由到dlx.queue,此时有消费者与dlx.queue绑定, 也就能成功消费消息了。但此时已经是30s钟以后了。

也就是说,publisher发送了一条消息,但最终consumer在30s后才收到消息。我们成功实现了延迟消息

二、 DelayExchange插件

  基于死信队列虽然可以实现延迟消息,但是太麻烦了。因此RabbitMQ社区提供了一个延迟消息插件来实现相同的效果。

1.声明延迟交换机(基于注解方式)

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "delay.queue", durable = "true"),
        exchange = @Exchange(name = "delay.direct", delayed = "true"),
        key = "delay"
))
public void listenDelayMessage(String msg){
    log.info("接收到delay.queue的延迟消息:{}", msg);
}

2.发送延迟消息。发送消息时,必须通过x-delay属性设定延迟时间:

@Test
void testPublisherDelayMessage() {
    // 1.创建消息
    String message = "hello, delayed message";
    // 2.发送消息,利用消息后置处理器添加消息头
    rabbitTemplate.convertAndSend("delay.direct", "delay", message, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 添加延迟消息属性
            message.getMessageProperties().setDelay(5000);
            return message;
        }
    });
}

  延迟消息插件内部会维护一个本地数据库表,同时使用Elang Timers功能实现计时。如果消息的延迟时间设置较长,可能会导致堆积的延迟消息非常多,会带来较大的CPU开销,同时延迟消息的时间会存在误差。因此,不建议设置延迟时间过长的延迟消息

三、 实现时的优化

设置30分钟后检测订单支付状态实现起来非常简单,但是存在两个问题:

  • 如果并发较高,30分钟可能堆积消息过多,对MQ压力很大。
  • 大多数订单在下单后1分钟内就会支付,但是却需要再MQ内等待30分钟,浪费资源。

在这里插入图片描述
  如上图所示,客户下单到交易服务模块,交易服务会发送延迟消息到MQ,延迟时间到,接收MQ的消息,检查这笔订单客户是否已支付,如果未支付,需要取消。但是考虑到上述问题,可以将一个30分钟的大延迟拆分为数个时间较短的小延迟,正常来说,大部分订单在下单后的短时间内都会完成支付,这样随着时间的流动,因未支付而要发MQ的延迟消息越来越少,从而提高性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值