做秒杀方案亦是如此,秒杀活动经常会引发高并发、系统宕机和库存超卖的棘手问题,作为开发者,我们该如何在保证系统稳定性的同时,防止业务风险呢?
本篇聊聊秒杀方案的几个原则和注意点,脑图见文末。
原则
纵观多种秒杀方案,没有相同的,但是这些方案都遵守了相同的原则。具体原则如下:
(1) 保护数据库
秒杀场景下,一定要优先保护数据库,这是重中之重。一旦数据库宕机,那系统彻底瘫痪,会给业务和口碑造成损失。保护数据库要注意如下几点:
- 在应用层需要将不合格的请求全部拦截,避免逻辑触达到数据库。
- 评估并发数,QPS等,给应用设置合理的数据库连接池数量,避免将数据库的连接耗尽。
- 监控数据库,观测数据库的CPU、内存等压力,观测慢SQL等,一旦出现问题,及时作出响应。
(2) 保护应用系统
应用系统也是要保护的对象,不管是单体还是微服务,系统尽量不宕机。保护系统要注意这几点:
- 如果有条件的话,将秒杀系统的BFF层做成独立的服务,这样就算本服务挂了,也不影响别的服务。
- 评估秒杀活动的访问量,适当扩大部分微服务的负载数量,从而提高系统的响应能力。
(3) 提前退出
秒杀系统一旦对外开放,肯定会招来不少羊毛党,甚至恶意攻击。所以,在处理逻辑时,一定要做前置校验,一旦发现非法请求,直接中断。
如果有条件的话,也要做一些攻击型测试和压力测试,看看能否拦住非法请求,看看系统能否承受住压力。
(4) 不超卖
秒杀场景下的商品,一般都是赔本赚吆喝,真的是卖一单亏一单。一般秒杀的商品、价格、库存,都是公司的运营和管理层沟通确认的结果。
为了将亏损控制在合理的范围,要严格按照既定的库存去售卖,一定不能出现超卖的情况。
前端主要注意点
秒杀场景下,前端有一些注意点,如下:
- 页面静态化:不管是在PC、H5、小程序还是APP,页面尽量静态化,能走缓存的走缓存,尽量少的去请求接口。
- CDN:将涉及到的js、图片、html等静态资源,提前刷到CDN中,加快访问速度。
- 从缓存读取数据:秒杀场景下用到的接口要和常规接口区分开,秒杀场景下的接口尽量是从Redis缓存中读取数据,然后响应给前端。前端也需要判断哪些数据可以存入前端缓存中,避免下次重复调接口获取。
- 前端做拦截:前端页面也要做一些拦截动作,过滤非法请求。虽然作用不大,只防君子不防小人,但是也要做。一切能提前拦截掉非法请求的动作,都要做。
后端主要注意点
后端的注意事项较多,大致如下:
(1) 弹性增加服务器:根据自身的整体部署情况,适当扩大负载。比如混合云部署的方式,可以按需临时租用几台云厂商的云服务器,等活动结束立马释放云服务器,这样成本最小。
(2) 限流和降级:不管是在秒杀场景,还是其余场景,保护系统的手段就是限流、降级、熔断。不管搁在什么时候,都是这三板斧。
(3) 前置校验:前置校验可以最大程度的拦住非法请求。比如校验恶意的重复IP、校验用户的重复下单、校验库存等等。
(4) 缓存预热:对于秒杀的商品信息和库存,需要提前做缓存预热,比如将数据提前刷到Redis缓存中。
(5) 定时任务:
- 及时释放库存:一般场景下,可能半小时才会取消未支付的订单。但在秒杀场景下,由于库存有限,避免恶意占库存,所以允许订单未支付的时间就要减少,比如3分钟。这种场景下,可以用定时任务及时取消订单,或者,采用消息队列的定时消息方案(比如RocketMQ的延迟消息)。
- 校准缓存的库存:下单会占用库存,取消订单会释放库存。如果库存预热到了Redis,则需要有个定时任务去校准Redis里的库存数量。
(6) 下单和减库存:
- 乐观锁减库存:更新数据库里的库存数量时,一定要使用乐观锁方案去避免超卖,类似update ttt set stock = xxx where product_id = yyy and stock = zzz;。
- 同步或异步:在走完前置逻辑后,则会进入到下单和减库存逻辑,此时,可以用同步方式直接调用,也可以用异步丢入到MQ的方式。具体采用哪种方式,需要根据系统的吞吐量去做评估。
总结
本文主要聊了秒杀方案的几个原则和前后端注意事项。方案千千万,原则就这么几个。最后,贴上一张脑图方便记忆。