【秒杀】
https://www.bilibili.com/video/BV1jA411k7eG?p=9&vd_source=898d5514be58985430a49b46d5500c13
https://github.com/sunshineshu/-How-to-design-a-spike-system 君山 极客时间秒杀课程
1分钟售票8万张!门票抢票背后的技术思考 https://cloud.tencent.com/developer/article/1891602
vivo全球商城:库存系统架构设计与实践
https://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&mid=2247496259&idx=1&sn=0b1cbc5ae9fb63460b17d56abd81df29&chksm=ebdb82d1dcac0bc753e9eed7ac8b8890d9998813617add0c1cf8d3547afc785073f7d525c29b&scene=21#wechat_redirect
场景:
设计目标:最小改动保证秒杀时间的流量洪流不会冲垮服务器
整体思路:
1、流量页面如何将请求拦截在上游?
静态请求
动态请求:
下单页面如何将请求拦截在上游?
1、进入下单页:1)页面URL后台动态获取;2)用户点击下单页购买按钮直接disable
2、订单提交:针对高并发使用缓存或CDN进行处理难度不大
网关层面过滤请求:
1、限定每个用户访问频率:比如5s下单一次
2、限定每个IP访问频率:避免机器人下单
3、把1个时间段的请求拦截掉一个百分比,或只允许特定数量的请求进行后台服务器
后台服务过滤请求:
1、库存放入Redis;2、订单写入缓存;3、订单批量落库
付款页如何将请求拦截在上游?
如果业务逻辑出现订单未及时付款而被取消,需把数据库及Redis库存加回去
来自Vivo案例
对于库存扣减,目前常见有两种库存扣减方案:
(1)下单时扣库存。
-
优点是:实时扣库存,避免付款时因库存不足而阻断影响用户体验。
-
缺点是:库存有限的情况下,恶意下单占库存影响其他正常用户下单。比如说有100台手机,如果没有限制下单数量,这100个库存可能被一个用户恶意占用,导致其他用户无法购买。
(2)支付时扣库存。
-
优点是:不受恶意下单影响。
-
缺点是:当支付订单数大于实际库存,会阻断部分用户支付,影响购物体验。比如说只有100台手机,但可能下了1000个订单,但有900个订单在支付时无法购买。
从用户体验考虑,我们采用的是下单时扣库存 + 回退这种方案。 下单时扣减库存,但只保留一段时间(比如15分钟),保留时间段内未支付则释放库存,避免长时间占用库存。
4.1 库存扣减防重
订单重复提交会导致库存重复扣减,比如用户误提交、系统超时重试等,针对此类问题有如下常见解决方案:
-
订单提交按钮单击置灰,避免重复提交。
注:对于按钮置灰这种方案,可以减少用户误触重复提交的可能性,但不能从根本上解决库存被重复扣减的问题,比如通过脚本来刷扣减库存的接口,依旧造成库存的重复扣减。
-
保证库存扣减接口的幂等性。
注:保证接口幂等的方案有很多,比如每次扣减库存时,带上唯一的流水号,利用数据库的唯一索引保证幂等等。
-
采用令牌机制。用户提交订单会进行令牌校验,校验通过才能提交订单。
注:这种方案保证每次提交的订单是唯一的,如果用户多次下单,那么会产生多个订单。
本系统采用的是保证接口幂等性的方案。
在库存扣减接口入参中增加订单序列号作为唯一标识,库存扣减时增加一条扣减日志。当接口重复请求时,会优先校验是否已经存在扣减记录,如果已存在则直接返回,避免重复扣减问题,具体流程如下:
4.2 防超卖与高并发扣减方案
4.2.1 常规渠道防超卖方案
常规下单渠道流量小且对超卖风险厌恶度极高,常用的防超卖方案有:
方案一:直接数据库扣减。通过sql判断剩余库存是否大于等于待扣库存,满足则扣减库存。该方案利用乐观锁原理即update的排他性确保事务性,避免超卖。
伪代码sql:sql:update store set store = store - #{deductStore } where (store-#{deductStore }) >= 0
优点:
-
实库实扣,不会出现超卖;
-
数据库乐观锁保证并发扣减一致性;
-
数据库事务保证批量扣减正常回滚。
缺点:
-
行级锁的原因存在性能瓶颈,高并发会出现请求堵塞超时问题;
-
直连数据库,每次扣库存都是写操作,接口性能较低。
方案二:利用分布式锁,强制串行化扣减同一商品库存。
优点:减轻数据库压力,同时还能确保不会超卖。
缺点:每次只能有一个请求抢占锁,不能应对高并发场景。
对于常规渠道,库存扣减是后置逻辑,流量不高,我们采用的是直接数据库扣减,且针对弊端做了一些措施:
-
前置校验严格,同时针对刷单场景会有严格限流,保证最终扣减库存的流量可控;
-
库存系统读写分离,减少数据库的压力。
4.2.2 高并发库存扣减方案
针对高并发库存扣减,比如秒杀,一般采用的是缓存扣减库存的方式(redis+lua脚本实现单线程库存更新)作为前置流程,代替数据库直接更新。
在redis中扣减库存虽然性能高,可以大大减轻数据库压力,但需要保证缓存数据能完整、正确的入库,以保证最终一致性。
针对缓存数据更新至数据库,目前主流方案有两种:
方案一:Redis数据直接异步更新至数据库。
优点:简单、没有复杂的流程。
缺陷:redis宕机或者故障,可能会造成缓存内库存数据的丢失。
方案二:Redis扣减库存时,同步在业务数据中insert库存信息。
这里大家可能会有疑问:
-
有数据库的插入操作,性能怎么保证?
-
有数据库的操作,又有redis的更新,事务性怎么保证?
-
异步更新业务库存在延迟,库存逆向回退如何保证?
对于疑问1:由于数据库insert比update性能优,insert是在表的末尾直接插入,没有寻址的过程,可以保证性能比较快。
对于疑问2:方案2不同于缓存直接扣减,而是把缓存扣减放在数据库insert的事务内,通过数据库的事务保证整体的事务。
insert的表被称为库存任务表,其中保存了库存扣减的信息,库存任务表结构可以设计的非常简单,主键 + 库存信息(json字符串)就可以了。
后续通过异步任务,从库存任务表表中查询出库存更新信息,将其同步到具体的库存表中,实现最终一致性,这种方案可以避免数据的丢失。
对于疑问3:库存回退是根据业务库中扣减记录进行回退的,由于异步更新业务库必定存在延迟(延迟极低,数秒以内),所以极端场景会存在走退款逆向流程时业务库的库存扣减记录还未更新。
针对这种情况库存回退设置延迟重试机制,如果再极端点达到重试阈值依旧没有扣减记录,则返回回退成功,不做阻断。
目前我们针对秒杀库存扣减,采用的是方案2。但毕竟涉及数据库的更新,为了避免风险,在前置流量校验上做了限制,保证流量的可控:
4.2.3 库存热点问题
什么是热点问题?热点问题就是因热点商品导致的redis、数据库等性能瓶颈。在库存系统中,热点问题主要存在:
-
采用直接扣减库存数据库的方式,存在数据库的行锁问题。常规渠道的库存扣减,我们采用的就是的就是这种方式。
-
采用缓存扣减库存的方式,大流量的情况下,热点商品扣减库存操作会打向redis单片,造成单片性能抖动,从而出现redis性能瓶颈。
对于第1种热点问题,在vivo商城常见的场景是:新发的爆品手机,在准点售卖时会有抢购效应,容易造成库存数据库单行的瓶颈问题。针对这种热点问题,我们的解决方案是“分而治之”:
对于潜在的热点爆款手机,我们会将库存平均分为多行(比如M行),扣减库存时,随机在M行中选取一行库存数据进行扣减。该方案突破了数据库单行锁的瓶颈限制,解决了爆款商品的热点问题。
对于第2种redis单片热点问题,解决方案也是分而治之。将数据库中的库存数据同步到redis时,把key值打散,分散在多个redis单片中。注:我们目前线上的流量峰值还达不到会造成redis单片瓶颈的问题,为避免过度设计,只做了前置限流,没有进行key值的打散。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)