求一个定时取消订单的解决方案

2020-05-05 10:23:23 +08:00
 yangyuhan12138
目前我们的方案是 rabbitmq 的死信队列,并发不高还好,但是我们最近会搞一波促销活动,订单超时时间是 35 分钟,意思就是有 35 分钟时间窗口的订单都会进入死信队列,害怕把 mq 弄挂了...或者说 rabbitmq 有没有啥水平扩容的方案,我们现在的模式为镜像模式,三台机器存的是一样的内容,所以消息的上限还是为单台机器的容量

还有一个方案是刚刚搜到的,就是 redis 的 zset,用时间戳当 score,这个方案感觉还挺不错呢,有没有人用过

大佬们多多给点建议 谢谢大家了
12840 次点击
所在节点    程序员
90 条回复
realpg
2020-05-05 15:40:01 +08:00
@yangyuhan12138 #40
其实,这点量,我们都没高度依赖 redis 的特性

当时是用阻塞语言写的,redis 就缓冲一下快速给确认,完事都怼 MYSQL 里去了

在前端和后端都做了特殊的优化,适应比较低性能的硬件解决问题 甚至很多逻辑都不依赖后端

跟你这个有关的,比如超时不能支付这个事,都不依赖后端。限时秒杀的接口是有验证码,有反秒杀器的逻辑,跟正常下单入口不一样,这里会生成特殊的订单号,自身验证支付有效期,能不能跳转支付网关选择,能不能拉起支付接口

而清理订单,依赖一个 delete from orders where STH limit 1,这个功能调用一次删 10 个过期订单,这个异步功能集成到了没一个非秒杀逻辑请求里,就是说,只要有一个人访问一次整个系统,就会删掉一条没用记录
SjwNo1
2020-05-05 15:46:41 +08:00
看你对超时状态的实时性要求高不高 ,不高的话每隔一段时间跑一个 update status 的定时任务
dooonabe
2020-05-05 16:10:55 +08:00
Kafka+MySQL 就可以了
ty89
2020-05-05 16:22:53 +08:00
@yangyuhan12138 不需要修改状态字段啊, 生成订单的时候计算好超时时间,把这个时间点记住就可以了, 服务器返回订单信息的时候,根据超时时间判断是否已经超时未支付,如果是,就返回超时未支付订单已取消的状态。不需要时间到了做 update 操作。你这样把超时修改状态交给队列去做,如果队列服务挂了,岂不是你超时的订单都没法变更状态了。类似的应用场景还有优惠券,比如一张优惠券有效期是 2 年之后,总不可能往队列里插一条任务,2 年后再执行,更新一下优惠券状态吧?
weiqk
2020-05-05 16:35:11 +08:00
我觉得可以在代码里面判断下单时间,我一直在小公司技术不行,你们说的方案我都不懂
niubee1
2020-05-05 16:37:17 +08:00
明明多一个过期时间字段就能解决的问题,非要跑什么队列
romisanic
2020-05-05 16:37:22 +08:00
delay mq , rabbit mq 持久化 安全方面没啥问题
延迟消息消费的时候单独定义一个 group,所有订单都发一个 mq,在这个时间点消费消息查询订单,满足取消条件就去取消,其他情况就直接消费完成丢掉 mq 。
量比较大的话,初期只需要考虑 mq 集群和 consumer 集群是否能够撑得住就行了,在查询订单这里你的 mysql 撑不住之前,rabbit mq 本身应该不会是你的瓶颈。
yangyuhan12138
2020-05-05 17:25:07 +08:00
@niubee1 @ty89 这个在正向流程确实没毛病 但是我们需要有个异步去还原未支付的库存呀...我们在创建订单的时候就把库存扣了
beidounanxizi
2020-05-05 18:11:37 +08:00
我觉得代码是不是有问题 而不是 mq 扛不住?或者姿势不对?
Vedar
2020-05-05 18:47:35 +08:00
还是定时任务好,还能保证事务完整
foam
2020-05-05 19:10:51 +08:00
1. @niubee1 #46 @yangyuhan12138 #48,楼主,你在 48L 提的“我们需要有个异步去还原未支付的库存” 与 46L 说的加一个过期字段并不冲突。举个例子,每分钟跑一次,将过期的订单 select 出来,逐个处理。

2. 说下我的想法
- 2.1 轮询(定时):最简单做就是加一个字段,每隔一段时间轮询一次数据库,逐个处理(弊端:符合条件的订单数太多,处理比较慢)
- 2.2 用延时队列:直接放到队列里,多个消费者并行处理(弊端:订单数太多,mq 积压)
- 2.3 轮询 + 延时队列:就是 39L 提到的。不多余好吗,楼主你不是担心消息数太多,MQ 内存顶不住吗。这个方案就是把即将过期的提前一点入队列,解决了 2.2 和 2.3 的弊端
以上三种方法都没毛病,按实际需求选择。个人建议不要过度设计,怎么简单怎么来,提前优化就提前提高了系统复杂度。如果是我选择,就用 2.2 。

针对楼主的主题内容再聊聊我的一些看法:
3. rmq 的水平扩容?多加机器节点不就是水平扩容吗。楼主提到现在 3 个节点,且队列都设置为镜像模式。看样子架构师很没有安全感呢。一般来说一主一从就可以了,即镜像数量设置为 1 个节点,我认为是 ok 的。这样,随着节点的数的增加,就分摊到不同节点了。因此,楼主提到的“上限还是为单台机器的容量”这个麻烦,实则是自己给自己找的。

4. redis zset 方案。这个方案实则是自己实现了一套不怎么靠谱的延时队列,有现成的 delayQueue 不用,折腾这个是干啥?是有别的考虑吗。

5. RMQ 的延时队列,可以用插件实现,不需要自己用死信队列实现了。
Aoang
2020-05-05 19:18:56 +08:00
你从业务上分析就好了啊,秒杀活动,35 分钟超时。

从最坏的情况来考虑,所有订单都不支付。
流量就只有活动开始的一瞬间,然后每 35 分钟订单超时、继续下单。

流量最大的时候就是订单超时之后重复下单的那个时间段。按照几十万的订单量来计算的话,那个时间段会有几十万的订单被取消,同时又下了几十万的单。

这里就可以先拆分一下,订单被取消之后,库存异步入库,不实时入库可以降低极端情况下的压力。
订单超时之后并不一定要立刻取消的,只要检测一下超时之后不允许支付即可。

异步也可以采用削峰填谷的方式,一下子处理几十万订单受不了的话,每次只处理一部分就好了,处理完的订单再把库存入库。
Wuxj
2020-05-05 20:02:48 +08:00
其实定时任务应该就可以解决吧~ 如果要用 mq 又怕撑爆就加个手动补偿兜底呗。主要是你没给具体的量,很难具体去分析。数据库分批 update 走乐观锁,应该处理个几十万的订单还是很小 case 的,别把数据库想得那么不堪~~
hangszhang
2020-05-06 00:33:23 +08:00
想推荐一波我司的 qmq
gaius
2020-05-06 00:53:41 +08:00
秒杀不都是扣完钱才减实际库存的吗?
lihongming
2020-05-06 01:51:05 +08:00
@gaius 你这是完全说反了,秒杀最怕的是超卖,必须下单即扣库存。
killerv
2020-05-06 09:06:32 +08:00
感觉并不需要队列,根据下单时间和支付状态就能确定是否为有效订单。
yangyuhan12138
2020-05-06 09:35:07 +08:00
@foam
2.1 这里比较担心的是 假设我每分跑一次定时,查询之前所有超时的订单 比如我查到了 3w 条,然后逐条处理假如处理到 1.5w 条的时候就已经一分钟了 下个定时任务又开始跑了 怎么避免重复处理的问题
2.2 现在担心的就是消息积压的问题 35 分钟可能会堆很多消息
2.3 的问题在极端情况下和 2.1 一样 不过确实要优于前两种方案, 这种方式进入 mq 的消息应该是少数,并且定时任务执行起来应该会挺快的 把复杂的逻辑部分交给 mq 那边异步处理 并不在定时任务里处理逻辑,但是应该在定时任务中更新订单状态,不然受限于 mq 的处理速度,可能第二个定时任务开始时,会将已经入队但是还未处理的消息再入队一次
yangyuhan12138
2020-05-06 09:35:40 +08:00
@killerv 需要异步还原库存
yangyuhan12138
2020-05-06 09:42:06 +08:00
@foam 再说说水平扩容 你的意思应该是采用集群模式, 然后集群中的每个节点还有个镜像是吧...比如 3 台 mq 集群 然后再开三台镜像模式的节点 ,但是我看了 mq 的集群模式 他是只同步元数据 就队列,交换机什么的.. 队列的数据还是只有一台机器会保存,其他的机器如果收到该队列的请求只会转发到对应的机器,不知道 rmq 有没有把一个队列分开存的模式...

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/668600

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX