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

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

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

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

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

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

    我来抬杠了。

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

    google:redis expire callback

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


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

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

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

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

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

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

    成功之后, 改订单状况.

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

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

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