最近参与了字节跳动青训营寒假专场的后端方向,结营项目 “抖音电商” 订单服务中有一项定时取消订单的功能需求,在这里记录一些我的想法和 demo 实现。
一、前言
为了避免在用户在未确认(支付)的情况下一直占用商品库存,提高整体库存周转率,避免无效订单数据长时间驻留在缓存中,我们需要采用一些机制进行资源回收,在这里即为定时取消机制。我们为订单设置一个过期时间,当订单在过期时间内未确认支付时,自动取消订单释放库存。这一需求可以通过 RabbitMQ 消息队列实现。
二、实现方案
我们先梳理一下在用户创建订单后的整个流程:
我们需要实现的部分即:“定时取消订单” 子图部分,有如下两种实现方案:
1) 使用死信交换机配合死信队列实现
一些介绍
死信交换机(DLX)
死信交换机即一个专门用于接收死信的交换机。当消息成为死信后,它们会被路由到指定的死信交换机。根据配置,交换机可以将这些死信消息路由到一个或多个死信队列中。
工作流程:
- 创建队列时配置交换机及相关属性参数。
- 当消息在正常队列中因为一些原因(消息过期;达到最大重试次数;队列限制)无法被消费时,被标记为死信。
- 标记的死信根据配置发送到设置的死信交换机。
- 死信交换机根据路由规则将死信发送到对应的死信队列。
死信队列(DLQ)
死信队列即一个特殊的队列,用于存储那些不能被正常处理的消息(因为上文描述的原因标记为死信的消息)。
Demo 实现
参照上文我们发现,过期的消息会被标记为死信发送到死信交换机,在轮转到死信队列。
那么我们不难想到这里的第一个方案:创建一个死信交换机,过期的消息被发送到死信交换机,然后创建一个死信队列,接收过期的死信,最后创建一个消费者,消费死信队列中的消息,即可实现定时取消订单的功能。
然后就是 Coding 实现想法了。
这里先使用 Docker Compose 搭建一个 RabbitMQ 环境,方便后续的测试:
|
|
首先定义以下订单结构,方便后续的模拟:
|
|
连接到 RabbitMQ:
|
|
然后按照流程创建交换机、队列:
|
|
|
|
最后即消费者的实现:
|
|
最后在主函数模拟用户创建订单即可完成整个流程:
|
|
其中省略了一些不关键的错误处理和日志输出,最终效果如下:
|
|
可以看到,订单在过期时间内未确认支付时,自动取消订单确实已经完成。
但是我们继续考虑这种实现可能会导致的问题:
- 当订单满足超时条件需要取消时,我们需要轮转消息到死信队列,之后在死信队列中重新处理,这可能会造成延迟,导致实际取消时间超出预设的时间。
- 大量订单消息轮转到死信队列中,造成消息积压,可能导致负载增加,系统性能下降。
- 业务逻辑肉眼可见的变得复杂,不易于维护。
显然需要进行一些优化,这里引出我们的第二种实现方案:
2) 使用延时队列插件实现
RabbitMQ 提供了一个延时队列插件 rabbitmq_delayed_message_exchange
。我们可以依赖这个插件实现定时取消。
一些介绍
延迟消息交换机是一种特殊的交换机,允许用户将消息在指定的时间延迟后再发送到目标队列。这种机制适用于需要在未来某个时刻处理的消息场景,例如定时任务、延迟通知等。
工作流程:
- 创建延迟消息交换机时,指定其类型为 x-delayed-message,并设置所需的参数。
- 发布消息时,可以在消息的属性中设置延迟时间(以毫秒为单位)。
- 消息在指定的延迟时间后,交换机会将其路由到相应的目标队列。
- 消费者从目标队列中获取到延迟后的消息进行处理。
Demo 实现
我们首先在之前开启的 RabbitMQ 服务中引入这个插件:
下载插件:
1
mkdir -p ~/rabbitmq/plugins && wget -P ~/rabbitmq/plugins https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.13.0/rabbitmq_delayed_message_exchange-3.13.0.ez
Copy 到容器:
1
sudo docker cp ~/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.13.0.ez rabbitmq-test:/plugins
启动插件:
1
sudo docker exec -it rabbitmq-test rabbitmq-plugins enable rabbitmq_delayed_message_exchange
重启服务:
1
sudo docker restart rabbitmq-test
然后开始调整一下代码实现:
只需要修改一下创建队列和交换机的部分即可,其他部分不变:
|
|
三、总结
比较项 | rabbitmq_delayed_message_exchange | 死信队列和死信交换机 |
---|---|---|
超时取消延迟 | 无需轮转消息,直接在预设时间后处理,避免延迟,精准控制取消时间 | 需要轮转到死信队列,处理延迟可能导致实际取消时间超出预设时间 |
消息积压 | 消息在延迟交换机中处理,避免积压,系统性能更稳定 | 大量订单消息轮转到死信队列,可能导致消息积压,增加系统负载,影响性能 |
业务逻辑复杂性 | 逻辑简单,易于理解和维护,直观的延迟处理机制 | 业务逻辑复杂,需处理死信队列重试和转发,增加维护成本和复杂度 |
系统性能 | 高效处理,减少资源消耗,优化整体性能 | 可能因消息积压导致系统性能下降,影响其他业务流程 |
配置和管理 | 配置简单,无需额外设置死信队列,减少管理负担 | 需要配置和管理死信队列及交换机,增加运维复杂度 |
灵活性 | 可以灵活设置不同的延迟时间,适应多种业务场景 | 灵活性有限,重试逻辑和延迟处理需要额外设计 |