您现在的位置是:测试开发营 > 数据库
电商库存系统的防超卖和高并发扣减方案
测试开发营2025-11-26 18:24:37【数据库】8人已围观
简介如果你要开发一个电商库存系统,最担心的是什么?闭上眼睛想下,当然是高并发和防超卖了!本文给出一个统筹考虑如何高并发和防超卖数据准确性的方案。读者可以直接借鉴本设计,或在此基础上做出更切合使用场景的设计
如果你要开发一个电商库存系统,电商的防最担心的库存扣减是什么 ?闭上眼睛想下 ,当然是系统高并发和防超卖了!本文给出一个统筹考虑如何高并发和防超卖数据准确性的超卖方案 。读者可以直接借鉴本设计,和高或在此基础上做出更切合使用场景的电商的防设计。
背景
在今年的库存扣减敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。系统Juint除了Suite执行器还有哪些执行器呢?超卖由此我的云计算Runner探索之旅开始了!
下面用电商库存为示例 ,和高来说明如何高并发扣减库存,电商的防原理同样适用于其他需要并发写和数据一致性的库存扣减场景。
1.1 库存数量模型示例为了描述方便,系统下面使用简化的超卖库存数量模型,真实场景中库存数据项会比以下示例多很多,和高但已经够说明原理 。如下表,库存数量表(stockNum)包含商品标识和库存数量两个字段,库存数量代表有多少货可以卖 。

传统通过数据库保证不超卖
库存管理的传统方案为了保证不超卖 ,服务器租用都是使用数据库的事务来保证的 :通过Sql判断剩余的库存数够用,多个并发执行update语句只有一个能执行成功;为了保证扣减不重复 ,会配合一个防重表来防止重复的提交,做到幂等性 ,防重表示例(antiRe)设计如下 :

比如一个下单过程的扣减过程示例如下:
复制事务开始
Insert into antiRe(code) value (‘订单号+Sku’)Update stockNum set num=num-下单数量 where skuId=商品ID and num-下单数量>0事务结束1.2.3.4.面临系统流量越来越大,数据库的性能瓶颈就会暴露出来:就算分库分表也是没用的,促销的模板下载时候高并发都是针对少量商品的,最终并发流量会打向少数表 ,只能去提升单分片的抗量能力,所以接下来设计一种使用Redis缓存做库存扣减的方案 。
Redis缓存做库存扣减的方案
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载 ,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的建站模板值 ,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
2.1 综合使用数据库和Redis满足高并发扣减的原理
扣减库存其实包含两个过程:第一步是超卖校验,第二步是扣减数据的持久化;在传统数据库扣减中 ,两步是一起完成的。抗写的高防服务器实现原理其实是巧妙的利用了分离的思想,分离开防超卖和数据持久化;首先防超卖是由Redis来完成的;通过Redis防超卖后,只要落库就可以;落库通过任务引擎 ,业务数据库使用商品分库分表,任务引擎任务通过单据号分库分表,热点商品的落库会被状态机分散开,消除热点 。
整体架构如下:

第一关解决超卖检验:可以把数据放入Redis中 ,每次扣减库存 ,都对Redis中的亿华云数据进行incryby 扣减,如果返回的数量大于0 ,说明库存够,因为Redis是单线程 ,可以信任返回结果。第一关是Redis ,可以抗高并发 ,性能Ok。超卖校验通过后,进入第二关 。
第二关解决库存扣减 :经过第一关后,第二关不需要再判断数量是否足够,只需要傻瓜扣减库存就行 ,对数据库执行如下语句,当然还是需要处理防重幂等的,不需要判断数量是否大于0了 ,扣减SQL只要如下写就可以。
复制事务开始
Insert into antiRe(code) value (‘订单号+Sku’)Update stockNum set num=num-下单数量 where skuId=商品ID
事务结束1.2.3.4.要点:最终还是要使用数据库,热点怎么解决的呢?任务库使用订单号进行分库分表 ,这样针对同一个商品的不同订单会散列在任务库的不同库存 ,虽然还是数据库抗量,但已经消除了数据库热点。
整体交互序列图如下 :

但Redis也是有瓶颈的,如果出现过热SKU就会打向Redis单片,会造成单片性能抖动。库存防刷有个前提是不能卡单的。可以定制设计JVM内毫秒级时间窗的限流,限流的目的是保护Redis,尽可能的不限流。限流的极端情况就是商品本来应该在一秒内卖完,但实际花了两秒 ,正常并不会发生延迟销售,之所以选择JVM是因为如果采用远端集中缓存限流,还未来得及收集数据就已经把Redis打死。
实现方案可以通过guava之类的框架,每10ms一个时间窗,每个时间窗进行计数,单台服务器超过计数进行限流。比如10ms超过2个就限流,那么一秒一台服务器就是200个,50台服务器一秒就可以卖出1万个货,自己根据实际情况调整阈值就可以 。

Redis的incrby 命令可以用做库存扣减,扣减项可能多个 ,使用Hash结构的hincrby命令 ,先用Reids原生命令模拟整个过程,为了简化模型下面将演示一个数据项的操作 ,多个数据项原理完全等同 。
复制127.0.0.1:6379> hset iphone inStock 1#设置苹果手机有一个可售库存
(integer) 1127.0.0.1:6379>hget iphone inStock #查看苹果手机可售库存为1
"1"127.0.0.1:6379> hincrby iphone inStock -1#卖出扣减一个,返回剩余0 ,下单成功
(integer) 0127.0.0.1:6379>hget iphone inStock #验证剩余0
"0"127.0.0.1:6379> hincrby iphone inStock -1 #应用并发超卖但Redis单线程返回剩余-1,下单失败
(integer) -1127.0.0.1:6379> hincrby iphone inStock 1 #识别-1