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

商城订单超过 30 分钟未支付自动取消订单,最佳解决方案是怎样?

  •  
  •   herozw · 2017-10-13 16:16:37 +08:00 · 33854 次点击
    这是一个创建于 2579 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如果用定时任务设置一个每秒执行的脚本,那样可能影响服务器性能。

    73 条回复    2019-06-03 16:29:44 +08:00
    Azmeont
        1
    Azmeont  
       2017-10-13 16:17:18 +08:00
    查询订单状态时检查是否超时
    hging
        2
    hging  
       2017-10-13 16:18:25 +08:00   ❤️ 1
    创建订单的时候 创建一个 30 分钟后执行的异步任务 如果支付成功取消这个任务 如果没有支付 就会自动执行。
    whileFalse
        3
    whileFalse  
       2017-10-13 16:19:10 +08:00
    定时任务每分钟呢?
    reus
        4
    reus  
       2017-10-13 16:22:25 +08:00
    怎么影响性能?
    WuwuGin
        5
    WuwuGin  
       2017-10-13 16:23:41 +08:00 via Android
    提出一个推测的建议:因为最早之前 steam 上支付卡单,支付后显示 pending。最终都是整点到库(听说是 union pay 的锅)。所以我感觉都是整点检测吧,或者定一个时间段。
    herozw
        6
    herozw  
    OP
       2017-10-13 16:26:20 +08:00
    @whileFalse 因为倒计时是按每秒计算。
    herozw
        7
    herozw  
    OP
       2017-10-13 16:27:31 +08:00
    @Azmeont 我也是这么考虑的
    Felldeadbird
        8
    Felldeadbird  
       2017-10-13 16:29:22 +08:00
    1.cron
    2.用户进入订单或者后台客服进行订单操作时进行过期处理。

    不要担心服务器性能。服务器性能是用来消耗的。
    wangxn
        9
    wangxn  
       2017-10-13 16:29:25 +08:00 via Android
    不用定时啊,只要客户重新查看这个订单,假如超时了,那么就做相应处理,假如一年都不访问,那就一直保持未超时的状态。
    Millyn
        10
    Millyn  
       2017-10-13 16:29:50 +08:00   ❤️ 1
    生成订单时就创建一个过期时间的字段,根据这个字段来判断是否过期。
    kaka826
        11
    kaka826  
       2017-10-13 16:30:03 +08:00   ❤️ 2
    用 redis 被动过期,客户端查询订单就从 redis 里取判断,redis 里的 key 过期,就取消订单
    holystrike
        12
    holystrike  
       2017-10-13 16:30:38 +08:00
    首先取消时业务逻辑做好判断
    然后另外有个轮询取消任务就可以了
    domty
        13
    domty  
       2017-10-13 16:31:00 +08:00
    做过类似的。
    做个队列一个个按时间丢进去进行,然后开个定时器每隔一段时间从队列头部拿出来一个检查是否到时间,倒了就拿出来改状态更新到数据库。
    检查前先去库里检查是否到手动被取消,若已被取消从队列头推出。
    hinate
        14
    hinate  
       2017-10-13 16:31:07 +08:00
    设计一个任务中心,使用延时任务。
    b821025551b
        15
    b821025551b  
       2017-10-13 16:31:10 +08:00
    要留记录就定时每秒执行,不留记录直接 30 分钟的缓存
    justfindu
        16
    justfindu  
       2017-10-13 16:35:15 +08:00
    每分钟执行就可以了 反正都是取消订单, 你还在乎时间延时么. 即使最后一秒他付款了, 那不就是目的么 , 而且退款流程加入队列
    zhea55
        17
    zhea55  
       2017-10-13 16:39:47 +08:00
    @Azmeont

    我来抬杠了。

    一般订单失败,是会给用户发邮件的。如果状态迟迟不更新,用户邮件功能如何实现?
    chairuosen
        18
    chairuosen  
       2017-10-13 16:40:44 +08:00   ❤️ 1
    记得微信高可用公众号分享过一个类似文章。
    1,每个商品一个定时器还是全局一个定时器。每个商品一个的话,太耗性能。所以全局一个。
    2,全局一个定时器怎么知道谁到了时间谁没到。遍历一遍页耗性能。
    方案是如果倒计时 30 分钟,精确到 1 分钟,就建 30 个队列,有个游标,1 分钟换下一个队列,新插的放这个队列,然后失效再下一个队列并清空。
    qiujin
        19
    qiujin  
       2017-10-13 16:42:01 +08:00
    使用 beanstalkd 这种有延迟任务功能的队列
    lepig
        20
    lepig  
       2017-10-13 16:48:31 +08:00
    @wangxn
    1. 客户进入到自己的订单列表,岂不是很多未支付订单
    2. 如果客户下了 10 个单超时未支付,那么我们后台管理系统中-订单列表查看的话还要处理一次。
    lepig
        21
    lepig  
       2017-10-13 16:49:15 +08:00
    @zhea55 +1
    imn1
        22
    imn1  
       2017-10-13 16:49:50 +08:00
    如果不需要推送通知的话,有必要搞定时器么?
    判定时间状态不允许操作就是了
    zhea55
        23
    zhea55  
       2017-10-13 16:52:31 +08:00   ❤️ 1
    Redis 里面有个 expire 的功能,时间到期了,会删除对应的 key

    google:redis expire callback

    貌似过期了可以得到通知。


    性能问题,第三方去考虑。
    pubby
        24
    pubby  
       2017-10-13 16:52:45 +08:00
    @qiujin +1 beanstalkd 好用
    tagtag
        25
    tagtag  
       2017-10-13 16:53:31 +08:00
    被动的超时处理应该没什么疑问,主动的应该只能轮询了吧。
    gamexg
        26
    gamexg  
       2017-10-13 17:03:01 +08:00 via Android
    订单本身有超时时间字段,需要关注超时时设置这个字段。

    每次客户查看订单时检查下是否超时来显示订单状态。

    另外后台有个每分钟或更长时间的定时任务处理超时订单,直接查数据库,这个任务主要负责回滚库存或发邮件通知等操作。

    除非很大的量,不然太建议过早优化上队列之类的。
    即使上专门的队列也需要一个直接查数据库的兜底机制来防止队列丢失数据或执行错误等造成超时订单长时间未处理。
    qq316107934
        27
    qq316107934  
       2017-10-13 17:08:08 +08:00 via Android
    大家都不考虑订单超时之后商品回库的问题吗?感觉还是要用定时器啊
    Patrick95
        28
    Patrick95  
       2017-10-13 17:10:58 +08:00
    对啊楼上好多人提出的解决方案都没有考虑过订单支付超时商品回库的问题。
    cowpea
        29
    cowpea  
       2017-10-13 17:11:23 +08:00
    @whileFalse 我也觉得分钟就行了,没有观测者的话,差个几十秒也没啥(业务特别严谨就另说,再说回来,能被超时取消的单子,用户也不会在乎那几十秒,右侧取整分肯定会多几十秒)。有观测的话,判断超时做处理就好了。
    linxl
        30
    linxl  
       2017-10-13 17:13:37 +08:00
    redis.
    每秒都去查询 redis 总不会有啥问题了吧.
    linpf
        31
    linpf  
       2017-10-13 17:16:19 +08:00
    我是做了定时任务,同时每次访问该数据的时候也判断一下。
    lilixiang999
        32
    lilixiang999  
       2017-10-13 17:21:36 +08:00
    python 的话 celery 有一个 countdown 的功能很合适,也能精确到订单维度,在处理 task 的时候订单支付超时 然后商品回库等一系列操作
    UnPace
        33
    UnPace  
       2017-10-13 17:24:07 +08:00
    用户主动触发改变订单状态肯定不可行,最合理的就是定时任务扫。
    ixixixe2
        34
    ixixixe2  
       2017-10-13 17:25:16 +08:00
    @qq316107934 搞个 0 点自动检测不就好了
    cxbig
        35
    cxbig  
       2017-10-13 17:30:52 +08:00
    Cronjob 每分钟跑一次可以了
    gcli
        36
    gcli  
       2017-10-13 17:32:54 +08:00
    定时任务,每分钟执行一次,超时的订单,更改订单状态,释放资源
    orderc
        37
    orderc  
       2017-10-13 17:36:50 +08:00
    google 延迟队列
    dullwit
        38
    dullwit  
       2017-10-13 17:42:09 +08:00
    DelayQueue,支持延时获取元素的无界阻塞队列
    gcli
        39
    gcli  
       2017-10-13 17:46:06 +08:00
    好吧,涨知识了,延迟队列
    接下来准备把定时任务替换为延迟队列
    minotaur
        40
    minotaur  
       2017-10-13 17:46:35 +08:00
    延迟消息队列 简单点就 redis
    upupxjg
        41
    upupxjg  
       2017-10-13 17:53:02 +08:00
    记录创建时间,再次操作的时候检查超时,超时了有后续处理的话(比较麻烦的情况)设置状态+进队列,怕太多再搞个定期 dump 归档之类的
    upupxjg
        42
    upupxjg  
       2017-10-13 17:54:24 +08:00
    “最优解”要看你超时了有什么业务什么处理
    honeycomb
        43
    honeycomb  
       2017-10-13 17:57:03 +08:00 via Android
    guava 里有一个 cache,可以参考它的原理。
    ipconfiger
        44
    ipconfiger  
       2017-10-13 18:02:17 +08:00
    首先你的订单在失效的时候需要有两个状态 一个是 失效 另外一个是完成失效, 失效可以用失效时间在记录中提现, 小于当前时间的都是属于失效, 然后在后台任务中慢慢处理失效的订单的其他回退的操作, 比如退款, 库存之类的, 都处理完后再设置状态为完成失效.
    frazy
        45
    frazy  
       2017-10-13 18:05:16 +08:00
    延迟 ( dead )队列 + Job 补充 解决问题
    zjqzxc
        46
    zjqzxc  
       2017-10-13 18:20:30 +08:00
    前后台分别进行
    前台 js 负责倒计时,倒计时结束后向后台发送一个倒计时结束的请求,此时后台检查是否确实已经超时了
    后台把这个计时加入延迟队列,采用 redis 的话就把检查实际间隔设置断些,几秒几十秒;磁盘数据库的话就 1~2 分钟甚至 3~5 分钟都可以。
    twogoods
        47
    twogoods  
       2017-10-13 18:22:50 +08:00
    movistar
        48
    movistar  
       2017-10-13 18:34:56 +08:00
    有的解决方案能解决问题,有的就是小玩具
    这种东西肯定得依赖中间件的,不管是分布式存储,分布式消息队列还是分布式锁
    难道一个商城只有一台服务器一个服务么......
    就算最简单的每秒扫一次库更新状态,N 台服务器之间怎么分配任务...
    这些都是线上产品需要解决的问题
    玩具无视,单机就太简单了......
    orderc
        49
    orderc  
       2017-10-13 18:37:43 +08:00
    orvice
        50
    orvice  
       2017-10-13 18:39:18 +08:00   ❤️ 2
    不知道大家有没有注意到
    cloudxns 每次登陆,有时候能收到一些域名取消托管的邮件。

    所以,你可以用户进行某些操作后,去触发一些任务。
    liteyou
        51
    liteyou  
       2017-10-13 18:42:49 +08:00 via Android
    定时任务总还是蛮担心的,一旦启动,就像是开闸放出来的猛兽。要么出现队列任务卡住的情况,本该清理的数据堆积如山,;要么干了不该干的事情。总之,完全无人值守的任务,还是定期去喵一眼,比较放心(❁´ω`❁)
    petelin
        52
    petelin  
       2017-10-13 19:13:53 +08:00   ❤️ 1
    我的高可靠性解决方案:
    创建订单之后, 写到延迟队列里一个任务, 同时记录数据库, 失效时间

    兜底轮训, 每一分钟轮训一遍数据库, 根据订单状况+失效时间, 这两个字段加个索引, 如果觉得处于代付款的订单不多的话,可以加在订单状态上.

    成功之后, 改订单状况.

    对于我们的业务是够了, 30 分钟能有多少个未付款的订单 ?? k 级别的轮训一遍小事呀~
    bobuick
        53
    bobuick  
       2017-10-13 19:16:39 +08:00
    python 的话,celery 可以做到。以前做电商做过类似的,可以用 celery 配合 rabbitmq 做成延迟队列的形式。
    不过目前市面上也有不少延迟队列服务,可直接使用
    refear99
        54
    refear99  
       2017-10-13 19:29:36 +08:00
    创建订单的时候就插入延迟队列,设置 30 分钟延迟
    worker 长轮训这个队列,30 分钟自动可见,消费的时候开启事务锁订单,检查是否已支付,如果未支付就关单退优惠券,已支付的话就删除消息
    fortunezhang
        55
    fortunezhang  
       2017-10-13 20:31:26 +08:00
    @wangxn 一般我也是这么做,等他查询的时候更改状态。但是自从有了微信模板消息,就 tm 不好用了。客户说 30 分钟没付款,你给他发个模板消息,这就尴尬了
    wangxiaoer
        56
    wangxiaoer  
       2017-10-14 09:16:08 +08:00 via Android
    @hging 我觉得这种方式成本高,你需要考虑异步任务执行意外的情况,比如异常中止怎么办,重启服务器怎么办等,如果考虑异步任务恢复就要面临集群,调度等。
    killerv
        57
    killerv  
       2017-10-14 10:25:33 +08:00
    这个不用走队列吧,只需要在操作订单相关的时候对比一下时间就行了吧
    jsrgqinbin
        58
    jsrgqinbin  
       2017-10-14 10:35:26 +08:00
    延迟队列
    扫描太耗性能了
    sweelia
        59
    sweelia  
       2017-10-14 11:00:01 +08:00 via Android
    rabbitmq 延时队列,定时任务扫全库扑杀漏网之鱼
    Reign
        60
    Reign  
       2017-10-14 11:29:25 +08:00 via iPhone
    楼上又是 crontab 又是队列的真是服了你们了,至于这么复杂么?生成订单写一个时间戳和 ID,下次用户再访问“我的订单”时取消这个超时的订单就行了,如果用户再也不访问这个订单了,你还关心这个 ID 搞毛线啊?
    anewg
        61
    anewg  
       2017-10-14 12:12:26 +08:00
    @Reign 商品要回库,再也不访问会影响库存
    0915240
        62
    0915240  
       2017-10-14 13:06:30 +08:00
    @Reign #60 那这样我能把你家的库存占用完,订单失效库存要回库啊。
    SlipStupig
        63
    SlipStupig  
       2017-10-14 14:10:08 +08:00
    https://github.com/ouqiang/delay-queue, 完全符合楼主要求...
    Reign
        64
    Reign  
       2017-10-14 17:31:37 +08:00
    @0915240
    @anewg 我理解成了给钱才发货
    zhx1991
        65
    zhx1991  
       2017-10-14 17:57:39 +08:00
    定时任务啊

    这种基础组件应该有做一个吧.
    abccccabc
        66
    abccccabc  
       2017-10-15 08:50:14 +08:00
    @herozw  可以使用 jobcenter 一类的以秒为单位的计划任务。
    runningman
        67
    runningman  
       2017-10-15 11:15:57 +08:00
    @SlipStupig 这个确实符合,但是这个是 go 写的,有 php 写的没,
    0915240
        68
    0915240  
       2017-10-15 11:20:01 +08:00
    @Reign #64 给钱了没货了怎么办,大家都来订单你都说好好好给钱就发货,然后你没那么多货,不就 gg 了。有一个商品下单商品占位的过程。
    lishunli
        69
    lishunli  
       2017-10-15 17:59:00 +08:00
    @petelin 延迟队列 + 兜底定时任务,不过需要考虑队列和任务对同一个订单同时取消的问题
    Winny
        70
    Winny  
       2017-10-16 08:53:49 +08:00
    1.订单表增加过期时间字段,用在查询页面展示并在用户试图操作的时候拒绝(同时触发回库逻辑)

    2.同时跑一个固定间隔的计划任务,每隔一段时间(根据业务和负载决定),将订单表中的过期记录关闭(避免 1 没有触发导致回库的问题)
    mingyun
        71
    mingyun  
       2018-01-21 11:13:45 +08:00
    延迟队列 +1
    go_starter
        72
    go_starter  
       2019-05-07 17:46:22 +08:00
    不用轮训的方式如何实现?轮训比较消耗服务器资源,如果订单量不多还好说,电商这种订单很大的情况不会采用轮训的方法的。初步想法是在订单提交后注册一个异步任务,比如 30 分钟后执行。如果订单状态是未支付,就取消订单。如果已支付,啥都不用做。最后删除任务。至于任务的中断、异常、恢复、持久化等,需要任务系统做好服务的 QOS 即可。
    huangke
        73
    huangke  
       2019-06-03 16:29:44 +08:00
    生成订单的时候把订单号存入 Redis 作为 key,按订单有效时间设置失效时间,当监听到键失效的时候就可以执行代码将订单标记为过期,关键词:notify-keyspace-events
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5502 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 56ms · UTC 07:50 · PVG 15:50 · LAX 23:50 · JFK 02:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.