
还是说没有解决方案
卖苹果, 库存 3 个. 有人买了, 先减库存(苹果-1)因为支付失败,需要逆操作补库存(苹果+1).
但是苹果+1 在先 -1 在后. 理论上某个时刻苹果的库存变成 4 了.
请问如何解决?
1 hhjswf 2023 年 6 月 6 日 via Android 为什么+1 会在先? 要是说某个时刻库存是 2 ,人间蒸发了 1 个,怎么保证强一致性,那还有的讨论 |
2 potatowish 2023 年 6 月 7 日 via iPhone 苹果-1 发了一条消息,苹果+1 又发了一条消息,消息在投递和消费的过程如果不能保证顺序性就可能出现你说的这种问题。 |
3 jdOY 2023 年 6 月 7 日 1.幂等 2.先扣减再补 3.补库后的总数小于等于原始的总数 |
4 ltkun 2023 年 6 月 7 日 via Android i++和++i |
5 lsk569937453 2023 年 6 月 7 日 这就是分布式事务啊。。。。。。 |
6 chunworkhard 2023 年 6 月 7 日 学习学习~ |
7 减库存的时候发两个时间,一个用来减库存,一个用来补库存。 补库存的时间晚于减库存的时间。 |
8 asssfsdfw 2023 年 6 月 7 日 打错了, 是购买的时候发两个时间 |
9 asssfsdfw 2023 年 6 月 7 日 也可以不发时间,打两个标记。 补库存的时候发现减库存的标记还在,那就清除两个标记,不增不减。 |
10 JYii 2023 年 6 月 7 日 好像没问题啊。下订单,库存-1 ,生成交易单。交易失败(立即或一段时间后自动失败),库存+1 。 |
11 bthulu 2023 年 6 月 7 日 支付失败是不补库存的, 要客户取消订单才补库存. |
12 MoozLee 2023 年 6 月 7 日 实际场景中前台销售库存 不等于 仓库库存,即使真出现销售库存变多了,问题也不大的 |
13 realpg PRO 关系数据库就事务行锁锁住就完了 非关系数据库,就自己实现个事务锁机制 |
14 b1t 2023 年 6 月 7 日 via iPhone 支付失败有必要扣库存么 |
15 hyqCrystal 2023 年 6 月 7 日 根据业务场景 要发生加一的场景一定是支付失败后库存+1 ,此时前面业务一定发生了商品减库存这个业务。那么业务上再做+1 的时候可以强制校验前面业务是否成功。可以使用操作时间,也可以使用最终一致性的消息表来都行。 |
16 summerLast 2023 年 6 月 7 日 增加一个锁定状态,出库在扣减库存 |
17 justfindu 2023 年 6 月 7 日 消息队列? |
18 realpg PRO 抱歉我傻逼了 没注意你说微服务隐含的意思 这就类似金融场景要求强一致的,不 confirm 你就敢回馈给用户结果? 就好像,银行转账,UI 系统把 operation 发给后台,不等执行结果就告诉用户你转账成功钱没了? 正确的方法,第一个生成订单扣库存,你发 message 到 mq ,这时候并不能给用户订单生成,要等待这个确实后端处理完了库存扣减完毕,这个反馈操作成功了,才能真生成订单。 而你没生成订单,就没有能取消订单,恢复库存的操作。 |
20 hoopan 2023 年 6 月 7 日 消息队列不是能强制消费顺序吗? |
槽点太多,不知道从哪开始说。 |
23 realpg PRO |
24 optional 2023 年 6 月 7 日 via iPhone 你把状态流程图画清楚就不会提这个问题了。 |
25 xuanbg 2023 年 6 月 7 日 第一,为什么要先减库存,不是付款成功才减? 第二,你不是先减掉了吗,怎么会加回时多出一个?这个逻辑我看不懂。 说来说去,为什么库存不够就不能下单?你们采购都死光了么?就算一时半会补不了货,不还能工厂直发吗?我要是老板,非把你们这些一根筋的程序员打个半死不可。 |
26 nothingistrue 2023 年 6 月 7 日 经典的异步乱序难题。你这个还好解决一些。 我这里假定「苹果+1 」、「苹果-1 」是两个事件,如果你用得不是事件驱动而是常规 REST 接口,那么可以把事件看作接口的入参对象,把事件消费过程看作接口的方法体。 首先,开了异步,并且还有严重级别的一致性问题,那么相关操作都要当作业务数据去存储。对于你的案例,就是「苹果+1 」、「苹果-1 」这些事件的消费记录,要当作业务数据存到数据库中。 「苹果-1 」事件无依赖,它的消费过程不用动。 「苹果+1 」事件必须晚于「苹果-1 」事件被消费,故它在消费时,要先去查找一下对应的「苹果-1 」事件是否有消费记录。如果没有,则停止当前消费过程,并追加延迟消费逻辑。延迟消费逻辑,可以简单的记消费失败并让发布事件那一方短暂延迟后重新发布事件(对于 REST 接口来说,就是接口抛异常,调用方捕获到异常之后,sleep ,然后重新调接口),也可以记消费成功但同时发布新的「苹果+1 延迟」事件(这个如果是 REST 接口,就有点难弄了,你需要额外加延迟调度框架,绝对不能在 REST 接口中 sleep )。 如果条件允许的话,还是用 Kafka 做事件驱动的基础设施,它能保证顺序消费。 |
27 nothingistrue 2023 年 6 月 7 日 @lsk569937453 #4 @realpg #17 #22 @tabris17 #18 不管是分布式事务,还是等消息处理成功才正式生成订单,这是 CP without A 的分布式原则选择,强一致但是低可用。下订单的场景明显不能采用这种原则。 |
29 MoYi123 2023 年 6 月 7 日 讨论技术问题就只讨论技术, 为什么总有懂哥来个什么程序员思维, 产品设计. 电商允许不一致, 那要是哪天你去做银行的项目, 遇到相似的场景, 也允许不一致吗? |
30 peyppicp 2023 年 6 月 7 日 我司库存扣减业务,现在一般就几种扣减模式: 1. 下单减库存,订单取消回补库存,订单取消接口编排库存回补逻辑 2. 下单预占库存,支付成功减库存,订单超时释放 3. 支付减库存,业务场景并不多 前提:当前使用的数据库是魔改过的,并针对高并发场景下执行 UPDATE value=value-1 做过定制优化,20C 机器提供单行 5w+ TPS 容量 所以 op 需要根据不同场景来看: 场景 1:下单时交易同步调用库存服务完成扣减,库存内部落单并更新库存-1 ;订单取消时,交易负责触发库存的回补动作,对账兜底 场景 2:下单交易同步调用库存预占接口类似 TCC 模式预占库存并发送订单超时队列延迟消息,支付回调触发同步减库存;消费到订单已取消消息后,check 订单支付状态未成功则返还库存信息 场景 3:比较简单不提 实际上业务中基本上都是同步调用,用 MQ 做异步处理会导致系统设计复杂,用户体验并不好,且存在先后顺序问题,不过一般情况下加个时间戳也就解决了 |
31 dotw2x 2023 年 6 月 7 日 想表达异步消费无序的场景?Actor 模型很适合. |
32 xuanbg 2023 年 6 月 7 日 @MoYi123 不懂业务的技术,做起事来往往事倍而功半。还在那自我感动攻克了什么什么技术难题,实际上根本不需要。真真是可笑。 如果你要和我讨论数据一致性问题,请先把业务场景摆出来。电商有电商的一致性需求,银行又有银行的一致性需求。不确定场景,这事太过复杂,就没什么可讨论的价值。 |
33 chaleaochexist OP @sadfQED2 我就是不会才问的. 请随意喷. |
34 chaleaochexist OP |
35 pierswu 2023 年 6 月 7 日 可用库存=库存-待出库存 创建购买订单,待出库存+1 支付成功 待出库-1 库存-1 支付失败 待出库-1 待出库存可以在数据库也可以在 redis 中 |
36 MoYi123 2023 年 6 月 7 日 @xuanbg 那就来讲讲业务, 库存是电商的一个非常核心的功能, 和仓储, 物流, 财务, 运营, 采购这些都有密切相关, 库存不可靠的话, 你要全公司一起来兼容你的 bug 吗? |
40 xuanbg 2023 年 6 月 7 日 @HyperionX 不能超卖的情况肯定是有的,譬如清库存商品。但正常商品就没有不能允许超卖的。做了 6 年电商,还能不懂这些?真的要锁库存,就不是这么一加一减这么简单了。没个分布式锁那能行? 我的意思是程序员要理解业务,设计最适合最简单最稳定的解决方案,而不是对业务不屑一顾,自顾自在那里炫技。 |
41 xuanbg 2023 年 6 月 7 日 @MoYi123 有没有一种可能,会有一个库存预警系统?这个系统不至于连个负数都会导致异常吧?对了,使用除法时,你还得当心除数为 0 。难道你从来都不先判断除数为 0 的情况?不会吧?不会吧? |
42 chaleaochexist OP |
43 craftx 2023 年 6 月 7 日 分布式事务,全部成功后,再提交 或者支付失败后,回滚 |
44 yankebupt 2023 年 6 月 7 日 给每个苹果打上 GUID ,减和补的时候查 那些违法作弊菠菜网站防黑的时候都会这样,才会做到筹码只赚不亏 上面发下来一个盈亏值,token 定死,你怎么折腾都行,赢超过这个数全部无效,比这个少或者输了,你发一条,对方收钱的时候倒不会含糊。 前提你真的是库存 |
45 ChoateYao 2023 年 6 月 7 日 先实时扣库存,然后记好扣减记录,会需要回滚的时候,直接用该记录回滚。 回滚使用队列 |
46 AbrahamGreyson 2023 年 6 月 7 日 加锁 + 确认啊, 或者队列, 微服务就分布式锁 或 redis 被, 一致性问题是很通用的,有很成熟的方案~ |
47 yinmin 2023 年 6 月 7 日 @chaleaochexist 把你的问题丢给 GPT4 ,回答的不理想,提示了一下“参考信用卡预授权”,这次回答的不错,提供给 OP 参考: 在实际的电商系统中,通常会有一个“锁定库存”的操作,这也被称为“预扣库存”。这个操作的目的是在用户下单时先将库存锁定,以防止在用户支付过程中商品被其他用户购买。 这个流程通常是这样的: 1. 用户下单时,系统先进行预扣库存操作,即临时锁定用户购买的商品数量。这个操作通常会在数据库中标记出来,以区分实际的库存和预扣的库存。 2. 用户完成支付后,系统会将预扣的库存转变为实际的库存减少。 3. 如果用户在一定时间内没有完成支付(例如在 15 分钟或 30 分钟内),系统会自动取消订单,并将预扣的库存释放回去。 这种方式可以有效地解决并发下单的问题,保证用户在下单时商品的库存是足够的。同时,也可以防止因为支付失败而需要回滚库存的问题,因为在用户支付失败时,系统只需要释放预扣的库存即可。 |
48 chaleaochexist OP @yinmin 谢谢你的回复 仔细推敲也不太对 还是超时问题. 和卖苹果例子一样只不过不减库存而是锁定和解锁 其实是一样的原理. 还是 +- 1 操作 如果每个苹果都有一个唯一 ID. 还是会有 解锁一个没加锁苹果的问题. 也就是阻塞. |
49 yinmin 2023 年 6 月 8 日 @chaleaochexist 应对电子商务大流量并发冲突,或者每个苹果有唯一 ID 带来的阻塞,还有一种思路:用消息队列(类似 rabbitmq),服务器配置成单线程读队列处理库存,理想状态下 1 分钟能处理几万交易,能满足中小电商的需求。 |
50 chaleaochexist OP @yinmin 谢谢 |
51 chaleaochexist OP @yinmin #49 仔细读了一遍感觉不对, 还是 GPT 的回复....那就没有说服力了... 几万 |
52 alexsunxl 2023 年 6 月 8 日 这个跟微服务完全没关系的。是业务层的事务问题。 用啥方案都不是关键了,但是一定要关注各种异常处理。 |
53 xiaoyuesanshui 2023 年 6 月 8 日 中间加一个缓冲环节 库存--缓冲--已销售 下单库存-1 缓冲+1 付款成功 缓冲-1 已销售+1 付款失败 缓冲-1 库存+1 |
54 chaleaochexist OP @alexsunxl 单体部署 数据库事务约束就可以了. |