1.优化前的架构
架构简述:
如图,该架构是创业公司常见的一种垂直架构,业务是all in one的没有做拆分,redis只是用来session读写、验证码读写并没有用做缓存。
业务数据都是从mysql数据库直接取得。
库存的处理主要是通过事务处理+乐观锁+
update ... where 库存-购买数>0,如果购买数>库存,验证不过,不能下单。
1.1.抢购时出现的问题
- 数据读取失败,抢购页面进不去
- 抢购成功率低
- 后台大量报数据库更新失败
- 后台大量报数据库连接异常
- 同平台其他业务挂掉
1.2.为什么会出现上面的问题
- 把库存的读取和更新直接落到后台的mysql数据库
- 前端没有做限流,大量的请求都传递到后台,数据库连接数暴增
- mysql是低速的磁盘IO处理速度慢
- 行锁的存在,使得线程之间需要进行锁的争夺,一个线程获得行锁以后,其他并发线程就需要等待它处理完成,这样系统将无法利用多线程并发执行的优势,且随着并发数的增加,等待的线程会越来越多,rt急剧飚升,最终导致可用连接数被占满,数据库拒绝服务
- 乐观锁高并发时抢购的成功率降低
- 线上业务没拆分
2.优化后的架构
架构简述:
如图,添加缓存层,把
活动页、商品详情页缓存到redis。
库存在活动开始前同步到redis,并且队列化(list)。
活动结束后redis的库存再同步回mysql(定时任务)
- 2.1.抢购库存队列的设计
- 根据库存,创建redis队列,队列长度=库存大小
- 把mysql的一条资源实例化为一个队列的多个资源
- 以商品为单位创建队列(key=活动id+商品id)
- 减库存出队,加库存入队
- 实时减,异步加
- 限流,让少量请求落到后端
- 活动结束redis库存(队列长度)同步到mysql
2.2.抢购流程时序图
如图,商品入库时,库存信息写到mysql,创建抢购活动或编辑抢购活动时库存信息以队列(list)方式写入redis,商品表中的库存减掉本次活动设定的库存。
客户下单时,验证通过后下单减库存,队列出队,库存-1,如果支付失败或发生异常,则还原库存,队列再入队。取消订单、退货、退款、超时未付款都要还原库存。
最后活动结束后,剩余库存同步到mysql,根据redis队列长度更新商品表的总库存和销量。
2.3.超卖与少卖
- 为什么会出现超卖和少卖
减库存在redis,下单在mysql,不同库不同事务
高并发
- 如何避免
-
- 业务层面,取消订单、退货、支付超时、支付失败都还原库存,下单时校验
- 捕获异常,异常发生时还原库存
- 双写2个redis,只读一个redis,一个redis坏了,直接切到另一个redis
2.4.如何限流
- 登陆验证
- 限购数量检查
- 库存检查
- 活动开始后不可以重复提交(5分钟)
- 每个web服务器设置接受请求的阀值(AtomicInteger),超过阀值就不再接受请求。
2.5.何时减库存
- 下单减库存
- 付款成功减库存(第三方支付有可能不能即时返回支付结果,暂未采用)
- 设置超时时间(30分钟),释放未付款库存
3.未来规划
经过优化后,每周的抢购都能顺利进行(初创时期,并发数不大),比优化前的架构要好很多。但是随着用户数的增加,优化后的架构还是不能满足高可用、高并发的需求。
接下来会去做业务的拆分,数据库也按业务拆分,梳理和优化业务,重构代码,把现在的架构改成分布式的微服务架构,并把抢购活动拆分出来。
从而做到水平扩展,使架构有较强的伸缩能力。