V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhangwugui
V2EX  ›  程序员

有一个有关订单自动取消的问题,请大家指教一下

  •  
  •   zhangwugui · 2017-11-20 09:48:43 +08:00 · 9444 次点击
    这是一个创建于 2560 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前提:是这样的,一笔订单下单之后,如果不支付,会处于待支付状态。而一般这个订单会有类似 30 分钟不支付自动取消订单的功能。

    实现:这种功能的实现,可以通过定时任务,每隔 30 秒或一分钟扫描一次。但如果集群环境下,可能定时任务会执行重复。当然我们可以通过类似操作系统 PV 操作限制同一时段只能一个定时任务执行。

    目标:我想知道,这种情况,除了定时任务,还有没有其他好点的实现?

    想法:目前在领导的提醒下,想到了用消息来处理,下完单后延时 30 分钟发送消息来处理,不知还有没有其他的处理方式,多谢大家,请多多指教。

    misaka19000
        1
    misaka19000  
       2017-11-20 09:55:10 +08:00
    为什么在分布式环境中就会重复执行呢?
    Doodlister
        2
    Doodlister  
       2017-11-20 09:58:10 +08:00 via Android   ❤️ 1
    记录下单时间 。在用户访问订单的时候用超时时间和下单时间比较如果超时就取消订单。
    th00000
        3
    th00000  
       2017-11-20 10:01:53 +08:00
    让用户自己触发会好一点吧
    xman99
        4
    xman99  
       2017-11-20 10:02:53 +08:00
    @Doodlister #2 超过时间还是可以支付的, 除非你在 支付宝或者微信平台关闭这个订单
    zhangwugui
        5
    zhangwugui  
    OP
       2017-11-20 10:10:22 +08:00
    @Doodlister 如果用户不访问,那他的订单列表里有将一直有这个待支付的订单喽。
    gone
        6
    gone  
       2017-11-20 10:15:09 +08:00
    cron 脚本就可以,即便是集群环境下 数据库肯定是唯一把 ,检查当前时间之前的正常状态订单,改掉状态.
    YORYOR
        7
    YORYOR  
       2017-11-20 10:15:43 +08:00
    做好幂等处理,这个跟怎么实现没关系吧
    Felldeadbird
        8
    Felldeadbird  
       2017-11-20 10:22:39 +08:00
    cron 脚本去检查 + 用户访问触发 。
    xinyewdz
        9
    xinyewdz  
       2017-11-20 10:23:31 +08:00
    消息分桶处理。每分钟只消费对应桶的消息即可,不必全表扫描。
    Jakesoft
        10
    Jakesoft  
       2017-11-20 10:28:05 +08:00
    每次生成一个但支付订单时,
    同时向 redis setnx 设定该未支付订单,
    每次查询一个待支付订单时须从 redis 中也查一遍,
    如果 redis 不存在该订单,
    则将该订单的(数据库)状态改为已取消。
    freelee
        11
    freelee  
       2017-11-20 10:37:18 +08:00
    如果是 java,可以用 elastic-job,一个分布式定时任务框架
    gcli
        12
    gcli  
       2017-11-20 10:39:28 +08:00
    推荐延时队列。
    可以 rabbitmq 实现的。AB 两个队列,A 队列设置消息过期时间,A 队列没有消费者,A 队列过期后自动转发到 B 队列,B 队列消费者进行取消队列。
    gcli
        13
    gcli  
       2017-11-20 10:39:58 +08:00
    推荐延时队列。
    可以 rabbitmq 实现的。AB 两个队列,A 队列设置消息过期时间,A 队列没有消费者,A 队列过期后自动转发到 B 队列,B 队列消费者进行取消订单操作。
    gcli
        14
    gcli  
       2017-11-20 10:40:29 +08:00
    yuankui
        15
    yuankui  
       2017-11-20 10:50:45 +08:00
    你当前的方案问题是,机器一挂,消息就得丢,然后你就要扫描数据,修数据.

    标准做法就是楼上说的延时队列, 可以谷歌下.
    如果要自己实现,切记一定要是分布式的.
    paragon
        16
    paragon  
       2017-11-20 10:59:04 +08:00
    @Doodlister 主要应该是退库存操作吧
    openbsd
        17
    openbsd  
       2017-11-20 11:05:26 +08:00
    大并发情况下,定时任务会不会产生过大的系统开销 ?
    用户触发开销会稍微小点吧
    用户 再次? 请求订单列表时 做个核对,时间超过 30 分钟直接自动取消,简单粗暴
    openbsd
        18
    openbsd  
       2017-11-20 11:06:22 +08:00
    @paragon 没付款就减库存 ?不符合某宝的 抢单风格啊......
    shawn7
        19
    shawn7  
       2017-11-20 11:08:28 +08:00 via Android
    DelayQueue?
    picone
        20
    picone  
       2017-11-20 11:54:21 +08:00
    延时队列+1
    就算是分布式 消费者很多个也只有一个拿到
    skyFuture
        21
    skyFuture  
       2017-11-20 12:10:23 +08:00
    我觉得你考虑有点多,任务多次执行?执行一次任务和多次任务的效果目标都是会导致订单的取消掉。有何不可呢?而且有木有算过比例呢?值得让整个流程变的更加复杂呢?
    just1
        22
    just1  
       2017-11-20 12:18:11 +08:00 via Android
    用户触发+定时( 1 小时一次),应该是个不错的方案
    zhangwugui
        23
    zhangwugui  
    OP
       2017-11-20 13:47:56 +08:00
    @skyFuture 订单取消我们会涉及到退积分相关的操作,所以只有执行一次。而退积分又是 RPC 操作,我目前控制不了这个。但这个不是主要问题,我是说对于一段时间内自动取消订单这类问题,有没有更好的实现方案。
    zhangwugui
        24
    zhangwugui  
    OP
       2017-11-20 13:51:21 +08:00
    @openbsd 哈哈哈,反正我们是下单减库存,不是扣款减库存。
    zhangwugui
        25
    zhangwugui  
    OP
       2017-11-20 13:55:22 +08:00
    @YORYOR 是和幂等有点关系,不过我这的主要问题是 一段时间内订单的自动取消问题。
    openbsd
        26
    openbsd  
       2017-11-20 13:58:31 +08:00
    @zhangwugui 内部系统么 ?下单前会核实库存数量不 ?
    有人恶意下个 9999999....的单,会怎样?
    好邪恶的想法
    chocotan
        27
    chocotan  
       2017-11-20 13:59:41 +08:00
    我们用的 tbschedule
    zhangwugui
        28
    zhangwugui  
    OP
       2017-11-20 14:00:57 +08:00
    @yuankui 嗯,延时队列可以考虑使用。下次试一试。
    heyang
        29
    heyang  
       2017-11-20 14:00:58 +08:00
    维护一个优先级队列,以超时时间作为优先权,每隔 n 秒扫描一下队头( n 值取决于你们对延迟的敏感程度),只要队头节点大于等于当前时间就把队头节点丢到 worker 里去处理取消订单逻辑,队头节点超时时间小于当前时间,就完成此次扫描。
    如果业务量特别大,队列长度可以做限制,生产节点时如果超长,就把节点持久化从队列删除,消费节点时如果达到阈值,从磁盘读取节点,补充队列。
    Jealee
        30
    Jealee  
       2017-11-20 14:02:31 +08:00
    我写过这种,利用的是 redis 发布订阅(pub/sub)
    weer0026
        31
    weer0026  
       2017-11-20 14:08:59 +08:00
    刚做完这个功能,延时队列+10086,分布式情况下维护一个公共队列。
    owenliang
        32
    owenliang  
       2017-11-20 14:17:13 +08:00
    脚本最靠谱,状态机,乐观锁什么的就不提了。
    MiguelValentine
        33
    MiguelValentine  
       2017-11-20 14:32:50 +08:00
    redis expire - -什么都不用操心
    zhangwugui
        34
    zhangwugui  
    OP
       2017-11-20 15:51:56 +08:00
    @MiguelValentine 放到 redis 缓存里?每看太懂呢。

    @owenliang 哈哈哈,脚本也是一种方式。

    @weer0026 嗯,延时队列我也准备试试。

    @Jealee 这是用 redis 做消息队列么
    owenliang
        35
    owenliang  
       2017-11-20 16:13:07 +08:00
    有兴趣加我 Q 聊:120848369
    Jealee
        36
    Jealee  
       2017-11-20 16:22:19 +08:00
    @zhangwugui reids 2.8 有一种 键空间通知的机制 Keyspace Notifications, 允许客户端去订阅一些 key 的事件,其中就有 key 过期的事件,我们可以把 key 名称设置为 task 的 id 等标识(这种方式 value 的值无法取到,所以只用 key 来识别任务),expire 设置为计划要执行的时间,然后开启一个客户端来订阅消息过期事件,然后处理 task。(网上复制的,但大致思路就是这样)
    stargazer242
        37
    stargazer242  
       2017-11-20 16:34:43 +08:00
    扫描
    SlipStupig
        38
    SlipStupig  
       2017-11-20 16:42:50 +08:00
    @Jealee 这种方法有个不好的地方,就是需要自定义一些 callback 否则涉及一些库存更新,无法直接完成
    SlipStupig
        39
    SlipStupig  
       2017-11-20 16:46:35 +08:00
    @Jealee 还有一个坏处就一旦 hang out 后,可能导致大量消息丢失,跟踪都不好跟踪
    Jealee
        40
    Jealee  
       2017-11-20 16:55:46 +08:00
    @SlipStupig 所以可以再做相对应的一些优化,比如服务异常处理之类的。
    BBCCBB
        41
    BBCCBB  
       2017-11-20 17:00:38 +08:00
    消息队列里的延时队列, 可靠+时延低
    BBCCBB
        42
    BBCCBB  
       2017-11-20 17:03:00 +08:00
    用户自己触发的这种并不能即使恢复库存.
    BBCCBB
        43
    BBCCBB  
       2017-11-20 17:04:47 +08:00
    所以还是推荐延时队列, 只要你的代码没问题, 就能保证被消费掉.
    waczx
        44
    waczx  
       2017-11-20 17:05:10 +08:00
    rabbitmq 有消息延迟处理的机制。
    把订单信息写进一个 queue,并指定消息的过期时间,到期后,rabbitmq 会把这个消息交给另一个 queue 来处理。
    很简单,在 admin 后台配置就可以。
    airyland
        45
    airyland  
       2017-11-20 17:28:22 +08:00
    redis 定时任务 + 补偿任务:扫描过去 n 分钟内订单进行处理,更新时注意检查状态并且加 lock。
    willvvvvv
        46
    willvvvvv  
       2017-11-20 17:48:15 +08:00
    定时任务去扫,操作前加 REDIS 锁
    SlipStupig
        47
    SlipStupig  
       2017-11-20 20:24:27 +08:00
    @MiguelValentine 你这样订单到期了就没有了,但是相关的库存,还有用户历史订单也没有了,你这样还不如直接删除这个用户,然后呢,他到时候账号登陆上去,他会自己重新注册一个的新的账户,以后他们再也不敢随便下单了 23333

    @Doodlister 你这个复杂度有点高了,一个个对比,如果订单量比较大的话,性能下降会很厉害的

    @Jealee 本身 redis 就没有 ACK 机制,你也无法保证你的 producer 不会崩溃,一旦崩溃你如何保证消息不丢失呢,如果有 rdb 和 aof 开启的情况下,我曾经加载过 100g RBD 文件,8 core 32g 内存,差不多要 40 秒才能全部载入完成这个对一些电商场景来说是致命了,如果是 cluster 模式还好一点,但是也会出现短时间的一个数据不一致情况,特殊情况下可能并不能接受这种情况
    zhangwugui
        48
    zhangwugui  
    OP
       2017-11-20 21:11:10 +08:00
    @SlipStupig 感谢建议。想了想,准备用延时队列试试看看。
    guyskk0x0
        49
    guyskk0x0  
       2017-11-20 22:25:45 +08:00 via Android
    起个独立进程(1 个生产者),每秒查一次超时订单
    select * from order where now() > deadline
    ,然后扔(分布式)队列里,由多个消费者进程去关闭订单。
    dawncold
        50
    dawncold  
       2017-11-20 22:35:35 +08:00
    你领导说的那个方法就挺好
    ioth
        51
    ioth  
       2017-11-21 10:40:37 +08:00
    没区别,支付宝的订单状态你去参考一下,有时候要学习马家军
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2682 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 04:16 · PVG 12:16 · LAX 20:16 · JFK 23:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.