系统重试,导致库存扣多啦,怎么办(两行代码破解)?

开发 架构
大家有没有遇到过,库存异常的情况?接下来,就今天和大家聊一聊库存扣减里的方案设计。

大家有没有遇到过,库存异常的情况:

  • 系统重试,导致库存扣了多次;
  • 系统并发,导致库存设置错误;

今天和大家聊一聊库存扣减里的方案设计。

库存微服务一般提供什么接口?

提供库存的查询、扣减、设置等RPC接口:

  • 库存查询接口,微服务一般执行:
select num from stock where sid=$sid
  • 库存扣减接口,微服务一般执行:
update stock set num=num-$reduce where sid=$sid
  • 库存设置接口,微服务一般执行:
update stock set num=$num_new where sid=$sid

库存操作,一般是什么业务场景?

用户下单前,一般会对库存进行查询,有足够的存量才允许扣减:

如上图所示,通过查询接口,得到库存是5。

用户下单时,接着会对库存进行扣减:

如上图所示,购买3单位的商品,通过扣减接口,最终得到库存是2。

简言之,一般是“先查后减”。

库存“先查后减”会遇到什么问题?

系统往往有重试机制,这个重试机制可能实现在系统底层,例如:服务连接池重试,数据库连接池重试,业务代码不可控。

如果通过扣减接口来修改库存,在重试时会导致重复扣减:

如上图所示,try和retry,导致一次扣减执行两次,最终得到一个错误库存。

如何解决“重试导致库存异常”的问题?

这里的根本原因:“reduce”操作是一个非幂等的操作,不能够重复执行,可以升级为“set”操作:

如上图所示,同样是购买3单位的商品,通过set操作,即使有重试机制,也不会得到错误的库存,“set”操作是一个幂等操作。

因此,应该“先查后设”。

库存“先查后设”会遇到什么问题?

并发量很大时,还是可能导致库存异常:

如上图所示,两个并发的操作,查询库存,都得到了库存是5。

接下来多个用户发生了并发的购买动作:

画外音:秒杀类业务特别容易出现。

如上图所示:

  • 用户1购买了3个库存,库存要设置为2;
  • 用户2购买了2个库存,库存要设置为3;
  • 这两个设置库存的接口并发执行,库存会先变成2,再变成3,导致数据不一致(实际卖出了5件商品,但库存只扣减了2,最后一次设置库存会覆盖和掩盖前一次并发操作);

如何解决“并发导致库存异常”的问题?

这里的根本原因:设置操作发生的时候,没有检查库存与查询出来的库存有没有变化,理论上:

  • 库存为5时,用户1的库存设置才能成功;
  • 库存为5时,用户2的库存设置才能成功;

实际执行的时候:

  • 库存为5,用户1的set stock 2确实应该成功;
  • 库存变为2了,用户2的set stock 3应该失败掉;

画外音:有条件的成功。

接口实现优化升级,将库存设置接口执行的:

update stock set num=$y where sid=$sid

升级为:

update stock set num=$num_new where sid=$sid 
and num=$num_old

画外音:加了一个初始条件比对。

简言之,“先查后设,有条件的设”。

这正是大家常说的“Compare And Set”(CAS),是一种常见的降低读写锁冲突,保证数据一致性的方法。

总结

  • “先查后减”,在重试,并发的场景下,容易出现异常;
  • “先查后设”,幂等性优化,能够解决重试的问题;
  • “先查后设,有条件的射”,CAS优化,能够解决并发的问题;

知其然,知其所以然。

思路比结论更重要。

责任编辑:赵宁宁 来源: 架构师之路
相关推荐

2013-03-05 17:11:27

Win 7操作系统蓝屏

2023-09-12 14:58:00

Redis

2021-06-18 10:12:09

JS代码前端

2017-06-15 08:02:02

库存扣减查询

2022-07-14 10:23:39

数据

2024-02-20 12:49:00

CSS函数前端

2022-09-25 23:10:53

Python数据集机器学习

2024-06-12 15:59:59

前端JavaScrip识别

2021-11-28 21:26:39

Windows 7Windows微软

2022-05-17 07:35:13

安全Session

2018-03-15 13:31:48

润乾LinuxGREP搜索

2013-01-29 13:22:24

系统服务

2022-11-18 07:40:57

2021-05-08 23:33:12

iOS苹果系统

2019-06-06 10:04:45

重构代码原代码

2021-01-15 05:36:48

MySQL错位数据库

2018-01-28 20:39:39

戴尔

2019-10-12 09:50:46

Redis内存数据库

2022-07-05 11:48:47

MySQL死锁表锁
点赞
收藏

51CTO技术栈公众号