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

脑补了一个微服务场景, 请问解决方案是什么?

  •  
  •   chaleaochexist · 2023-06-06 23:45:36 +08:00 · 4124 次点击
    这是一个创建于 536 天前的主题,其中的信息可能已经有所发展或是发生改变。

    还是说没有解决方案

    卖苹果, 库存 3 个. 有人买了, 先减库存(苹果-1)因为支付失败,需要逆操作补库存(苹果+1).

    但是苹果+1 在先 -1 在后. 理论上某个时刻苹果的库存变成 4 了.

    请问如何解决?

    54 条回复    2023-06-08 13:36:22 +08:00
    hhjswf
        1
    hhjswf  
       2023-06-06 23:52:30 +08:00 via Android
    为什么+1 会在先?
    要是说某个时刻库存是 2 ,人间蒸发了 1 个,怎么保证强一致性,那还有的讨论
    potatowish
        2
    potatowish  
       2023-06-07 01:14:02 +08:00 via iPhone   ❤️ 4
    苹果-1 发了一条消息,苹果+1 又发了一条消息,消息在投递和消费的过程如果不能保证顺序性就可能出现你说的这种问题。
    jdOY
        3
    jdOY  
       2023-06-07 03:09:19 +08:00
    1.幂等
    2.先扣减再补
    3.补库后的总数小于等于原始的总数
    ltkun
        4
    ltkun  
       2023-06-07 05:06:49 +08:00 via Android
    i++和++i
    lsk569937453
        5
    lsk569937453  
       2023-06-07 08:25:05 +08:00
    这就是分布式事务啊。。。。。。
    chunworkhard
        6
    chunworkhard  
       2023-06-07 08:53:29 +08:00
    学习学习~
    asssfsdfw
        7
    asssfsdfw  
       2023-06-07 08:57:16 +08:00
    减库存的时候发两个时间,一个用来减库存,一个用来补库存。 补库存的时间晚于减库存的时间。
    asssfsdfw
        8
    asssfsdfw  
       2023-06-07 08:57:57 +08:00
    打错了, 是购买的时候发两个时间
    asssfsdfw
        9
    asssfsdfw  
       2023-06-07 08:59:36 +08:00
    也可以不发时间,打两个标记。 补库存的时候发现减库存的标记还在,那就清除两个标记,不增不减。
    JYii
        10
    JYii  
       2023-06-07 09:08:20 +08:00
    好像没问题啊。下订单,库存-1 ,生成交易单。交易失败(立即或一段时间后自动失败),库存+1 。
    bthulu
        11
    bthulu  
       2023-06-07 09:11:57 +08:00
    支付失败是不补库存的, 要客户取消订单才补库存.
    li746224
        12
    li746224  
       2023-06-07 09:13:32 +08:00
    实际场景中前台销售库存 不等于 仓库库存,即使真出现销售库存变多了,问题也不大的
    realpg
        13
    realpg  
       2023-06-07 09:15:23 +08:00
    关系数据库就事务行锁锁住就完了

    非关系数据库,就自己实现个事务锁机制
    b1t
        14
    b1t  
       2023-06-07 09:22:32 +08:00 via iPhone
    支付失败有必要扣库存么
    hyqCrystal
        15
    hyqCrystal  
       2023-06-07 09:26:41 +08:00
    根据业务场景 要发生加一的场景一定是支付失败后库存+1 ,此时前面业务一定发生了商品减库存这个业务。那么业务上再做+1 的时候可以强制校验前面业务是否成功。可以使用操作时间,也可以使用最终一致性的消息表来都行。
    summerLast
        16
    summerLast  
       2023-06-07 09:29:19 +08:00   ❤️ 1
    增加一个锁定状态,出库在扣减库存
    justfindu
        17
    justfindu  
       2023-06-07 09:40:20 +08:00
    消息队列?
    realpg
        18
    realpg  
       2023-06-07 09:41:11 +08:00   ❤️ 1
    抱歉我傻逼了 没注意你说微服务隐含的意思

    这就类似金融场景要求强一致的,不 confirm 你就敢回馈给用户结果?

    就好像,银行转账,UI 系统把 operation 发给后台,不等执行结果就告诉用户你转账成功钱没了?


    正确的方法,第一个生成订单扣库存,你发 message 到 mq ,这时候并不能给用户订单生成,要等待这个确实后端处理完了库存扣减完毕,这个反馈操作成功了,才能真生成订单。

    而你没生成订单,就没有能取消订单,恢复库存的操作。
    tabris17
        19
    tabris17  
       2023-06-07 09:42:53 +08:00   ❤️ 1
    @realpg 难怪我根本看不懂楼主在说啥。搞了半天他的意思是发了一条异步消息就直接生成订单了,也不管消息有没有被处理成功
    hoopan
        20
    hoopan  
       2023-06-07 09:57:35 +08:00   ❤️ 1
    消息队列不是能强制消费顺序吗?
    seth19960929
        21
    seth19960929  
       2023-06-07 10:24:06 +08:00
    @realpg 很多银行就是转账直接跳到某个等待页面, 快的话马上显示成功, 慢的等 10s.
    所以转账提交成功, 和转账成功分两步
    sadfQED2
        22
    sadfQED2  
       2023-06-07 10:26:35 +08:00 via Android   ❤️ 1
    槽点太多,不知道从哪开始说。
    realpg
        23
    realpg  
       2023-06-07 10:28:29 +08:00
    @seth19960929 #21
    对啊 这就是我说的这个状态
    你不能消息丢给 mq 就给用户订单生成了
    optional
        24
    optional  
       2023-06-07 10:41:17 +08:00 via iPhone
    你把状态流程图画清楚就不会提这个问题了。
    xuanbg
        25
    xuanbg  
       2023-06-07 10:55:22 +08:00
    第一,为什么要先减库存,不是付款成功才减?
    第二,你不是先减掉了吗,怎么会加回时多出一个?这个逻辑我看不懂。

    说来说去,为什么库存不够就不能下单?你们采购都死光了么?就算一时半会补不了货,不还能工厂直发吗?我要是老板,非把你们这些一根筋的程序员打个半死不可。
    nothingistrue
        26
    nothingistrue  
       2023-06-07 11:07:02 +08:00   ❤️ 1
    经典的异步乱序难题。你这个还好解决一些。

    我这里假定「苹果+1 」、「苹果-1 」是两个事件,如果你用得不是事件驱动而是常规 REST 接口,那么可以把事件看作接口的入参对象,把事件消费过程看作接口的方法体。

    首先,开了异步,并且还有严重级别的一致性问题,那么相关操作都要当作业务数据去存储。对于你的案例,就是「苹果+1 」、「苹果-1 」这些事件的消费记录,要当作业务数据存到数据库中。

    「苹果-1 」事件无依赖,它的消费过程不用动。

    「苹果+1 」事件必须晚于「苹果-1 」事件被消费,故它在消费时,要先去查找一下对应的「苹果-1 」事件是否有消费记录。如果没有,则停止当前消费过程,并追加延迟消费逻辑。延迟消费逻辑,可以简单的记消费失败并让发布事件那一方短暂延迟后重新发布事件(对于 REST 接口来说,就是接口抛异常,调用方捕获到异常之后,sleep ,然后重新调接口),也可以记消费成功但同时发布新的「苹果+1 延迟」事件(这个如果是 REST 接口,就有点难弄了,你需要额外加延迟调度框架,绝对不能在 REST 接口中 sleep )。

    如果条件允许的话,还是用 Kafka 做事件驱动的基础设施,它能保证顺序消费。
    nothingistrue
        27
    nothingistrue  
       2023-06-07 11:20:10 +08:00   ❤️ 1
    @lsk569937453 #4
    @realpg #17 #22
    @tabris17 #18

    不管是分布式事务,还是等消息处理成功才正式生成订单,这是 CP without A 的分布式原则选择,强一致但是低可用。下订单的场景明显不能采用这种原则。
    hhjswf
        28
    hhjswf  
       2023-06-07 11:33:55 +08:00
    @xuanbg 先减库存保证库存不超扣。至于超扣这个事情重不重要,没干过电商运营不知道
    MoYi123
        29
    MoYi123  
       2023-06-07 11:49:08 +08:00
    讨论技术问题就只讨论技术, 为什么总有懂哥来个什么程序员思维, 产品设计.
    电商允许不一致, 那要是哪天你去做银行的项目, 遇到相似的场景, 也允许不一致吗?
    peyppicp
        30
    peyppicp  
       2023-06-07 11:58:27 +08:00   ❤️ 1
    我司库存扣减业务,现在一般就几种扣减模式:
    1. 下单减库存,订单取消回补库存,订单取消接口编排库存回补逻辑
    2. 下单预占库存,支付成功减库存,订单超时释放
    3. 支付减库存,业务场景并不多

    前提:当前使用的数据库是魔改过的,并针对高并发场景下执行 UPDATE value=value-1 做过定制优化,20C 机器提供单行 5w+ TPS 容量

    所以 op 需要根据不同场景来看:
    场景 1:下单时交易同步调用库存服务完成扣减,库存内部落单并更新库存-1 ;订单取消时,交易负责触发库存的回补动作,对账兜底
    场景 2:下单交易同步调用库存预占接口类似 TCC 模式预占库存并发送订单超时队列延迟消息,支付回调触发同步减库存;消费到订单已取消消息后,check 订单支付状态未成功则返还库存信息
    场景 3:比较简单不提

    实际上业务中基本上都是同步调用,用 MQ 做异步处理会导致系统设计复杂,用户体验并不好,且存在先后顺序问题,不过一般情况下加个时间戳也就解决了
    dotw2x
        31
    dotw2x  
       2023-06-07 13:05:19 +08:00
    想表达异步消费无序的场景?Actor 模型很适合.
    xuanbg
        32
    xuanbg  
       2023-06-07 13:40:01 +08:00
    @MoYi123 不懂业务的技术,做起事来往往事倍而功半。还在那自我感动攻克了什么什么技术难题,实际上根本不需要。真真是可笑。

    如果你要和我讨论数据一致性问题,请先把业务场景摆出来。电商有电商的一致性需求,银行又有银行的一致性需求。不确定场景,这事太过复杂,就没什么可讨论的价值。
    chaleaochexist
        33
    chaleaochexist  
    OP
       2023-06-07 13:52:28 +08:00
    @sadfQED2 我就是不会才问的.
    请随意喷.
    chaleaochexist
        34
    chaleaochexist  
    OP
       2023-06-07 13:54:19 +08:00
    @xuanbg
    1. 付款成功才减 可能超卖.
    2. 网络延迟

    3. 这是一个脑补的问题, 我没做过电商业务. 实际上我没做过互联网项目. 你也不用着一直反问.
    pierswu
        35
    pierswu  
       2023-06-07 17:39:59 +08:00
    可用库存=库存-待出库存
    创建购买订单,待出库存+1
    支付成功 待出库-1 库存-1
    支付失败 待出库-1

    待出库存可以在数据库也可以在 redis 中
    MoYi123
        36
    MoYi123  
       2023-06-07 18:32:45 +08:00
    @xuanbg 那就来讲讲业务, 库存是电商的一个非常核心的功能, 和仓储, 物流, 财务, 运营, 采购这些都有密切相关, 库存不可靠的话, 你要全公司一起来兼容你的 bug 吗?
    xuanbg
        37
    xuanbg  
       2023-06-07 18:33:53 +08:00
    @MoYi123 库存负数就不能正确充正了吗?
    HyperionX
        38
    HyperionX  
       2023-06-07 18:39:13 +08:00   ❤️ 1
    @xuanbg 有些业务场景就是要求不允许有超售,异步顺序问题也很常见。不懂可以理性探讨,不必一边见识有限还要 diss 别人 low
    MoYi123
        39
    MoYi123  
       2023-06-07 18:46:03 +08:00
    @xuanbg 比方说我有个地方要算库存预计什么时候销售完, 你给我个负数, 这是不是就有 bug 了?
    xuanbg
        40
    xuanbg  
       2023-06-07 18:47:23 +08:00
    @HyperionX 不能超卖的情况肯定是有的,譬如清库存商品。但正常商品就没有不能允许超卖的。做了 6 年电商,还能不懂这些?真的要锁库存,就不是这么一加一减这么简单了。没个分布式锁那能行?

    我的意思是程序员要理解业务,设计最适合最简单最稳定的解决方案,而不是对业务不屑一顾,自顾自在那里炫技。
    xuanbg
        41
    xuanbg  
       2023-06-07 18:50:35 +08:00
    @MoYi123 有没有一种可能,会有一个库存预警系统?这个系统不至于连个负数都会导致异常吧?对了,使用除法时,你还得当心除数为 0 。难道你从来都不先判断除数为 0 的情况?不会吧?不会吧?
    chaleaochexist
        42
    chaleaochexist  
    OP
       2023-06-07 20:27:11 +08:00
    @xuanbg #40 那你就给好好讲讲呗.
    总结一下你的思路. 提高你的写作能力. 顺便还帮助了别人.至少我是感谢你的.
    你在这个帖子下面打了这么多字 大部分都没什么营养. 至少我没多大收获.
    craftx
        43
    craftx  
       2023-06-07 20:29:54 +08:00
    分布式事务,全部成功后,再提交
    或者支付失败后,回滚
    yankebupt
        44
    yankebupt  
       2023-06-07 20:38:47 +08:00
    给每个苹果打上 GUID ,减和补的时候查
    那些违法作弊菠菜网站防黑的时候都会这样,才会做到筹码只赚不亏
    上面发下来一个盈亏值,token 定死,你怎么折腾都行,赢超过这个数全部无效,比这个少或者输了,你发一条,对方收钱的时候倒不会含糊。

    前提你真的是库存
    ChoateYao
        45
    ChoateYao  
       2023-06-07 20:44:54 +08:00
    先实时扣库存,然后记好扣减记录,会需要回滚的时候,直接用该记录回滚。

    回滚使用队列
    AbrahamGreyson
        46
    AbrahamGreyson  
       2023-06-07 21:09:58 +08:00
    加锁 + 确认啊, 或者队列, 微服务就分布式锁 或 redis 被, 一致性问题是很通用的,有很成熟的方案~
    yinmin
        47
    yinmin  
       2023-06-07 22:56:19 +08:00   ❤️ 1
    @chaleaochexist
    把你的问题丢给 GPT4 ,回答的不理想,提示了一下“参考信用卡预授权”,这次回答的不错,提供给 OP 参考:

    在实际的电商系统中,通常会有一个“锁定库存”的操作,这也被称为“预扣库存”。这个操作的目的是在用户下单时先将库存锁定,以防止在用户支付过程中商品被其他用户购买。

    这个流程通常是这样的:

    1. 用户下单时,系统先进行预扣库存操作,即临时锁定用户购买的商品数量。这个操作通常会在数据库中标记出来,以区分实际的库存和预扣的库存。

    2. 用户完成支付后,系统会将预扣的库存转变为实际的库存减少。

    3. 如果用户在一定时间内没有完成支付(例如在 15 分钟或 30 分钟内),系统会自动取消订单,并将预扣的库存释放回去。

    这种方式可以有效地解决并发下单的问题,保证用户在下单时商品的库存是足够的。同时,也可以防止因为支付失败而需要回滚库存的问题,因为在用户支付失败时,系统只需要释放预扣的库存即可。
    chaleaochexist
        48
    chaleaochexist  
    OP
       2023-06-07 23:59:31 +08:00
    @yinmin
    谢谢你的回复 仔细推敲也不太对
    还是超时问题.

    和卖苹果例子一样只不过不减库存而是锁定和解锁
    其实是一样的原理.
    还是 +- 1 操作
    如果每个苹果都有一个唯一 ID. 还是会有 解锁一个没加锁苹果的问题. 也就是阻塞.
    yinmin
        49
    yinmin  
       2023-06-08 00:13:55 +08:00
    @chaleaochexist 应对电子商务大流量并发冲突,或者每个苹果有唯一 ID 带来的阻塞,还有一种思路:用消息队列(类似 rabbitmq),服务器配置成单线程读队列处理库存,理想状态下 1 分钟能处理几万交易,能满足中小电商的需求。
    chaleaochexist
        50
    chaleaochexist  
    OP
       2023-06-08 07:08:21 +08:00
    @yinmin 谢谢
    chaleaochexist
        51
    chaleaochexist  
    OP
       2023-06-08 07:09:29 +08:00
    @yinmin #49 仔细读了一遍感觉不对, 还是 GPT 的回复....那就没有说服力了... 几万
    alexsunxl
        52
    alexsunxl  
       2023-06-08 10:28:02 +08:00
    这个跟微服务完全没关系的。是业务层的事务问题。
    用啥方案都不是关键了,但是一定要关注各种异常处理。
    xiaoyuesanshui
        53
    xiaoyuesanshui  
       2023-06-08 11:57:02 +08:00
    中间加一个缓冲环节

    库存--缓冲--已销售


    下单库存-1
    缓冲+1

    付款成功
    缓冲-1
    已销售+1

    付款失败
    缓冲-1
    库存+1
    chaleaochexist
        54
    chaleaochexist  
    OP
       2023-06-08 13:36:22 +08:00
    @alexsunxl 单体部署 数据库事务约束就可以了.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2433 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 02:20 · PVG 10:20 · LAX 18:20 · JFK 21:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.