秒杀场景设计方案
一、秒杀设计要面对的难点
秒杀特点:短时间内,大量用户涌入,集中读和写有限的库存。
- 保证超高的流量和并发下系统的稳定性,保证不被打崩
- 保证数据最终一致性, 不能超卖
解决方案:层层拦截,将请求尽量拦截在系统上游,避免将锁冲落到数据库上
二、架构设计
2.1、流量过滤
实际大促的时候,参与秒杀的用户很多,但是商品的数量是有限的,真正能抢到的用户并不多,那么第一步就是要过滤掉大部分无效的流量。
- 客户端优化
- 置灰前端按钮防止活动未开始无效的点击流量
- 前端设置验证码(不过不要像以前的12306那样复杂就行)
- 站点层面的请求拦截(nginx层)
1. 非法请求校验,绕过上面限制的请求(比如抢票的插件)
2. 黑名单、IP地址
3. 用户ID参与活动次数,对请求计数和去重 - 服务层业务校验
- 参与的客户是否符号参加条件,白名单等
- 接口限流(滑动窗口、令牌桶等限流算法(Sentinel))
- 写请求放到队列中,每次有限的写请求到数据层,如果成功了再放下一批,直到库存不够
2.2、性能优化
- 静态页面nginx 动静分离 或者 静态页面缓存到CDN
- nginx+redis 多级缓存
- 活动预热,库存提前加载到redis
- 独立部署,可以降级一些非核心业务,或者无用的业务带代码
2.3、避免超卖
保证数据最终一致性, 不能超卖。
解决方案(可以使用redis分布式锁解决超卖问题):
- 首先查询redis缓存库存是否充足
- 先扣库存再落订单数据,可以防止订单生成了没有库存的超卖问题
- 扣库存的时候先扣数据库库存,再扣减redis库存,保证在同一个事务里。
这种方案中当大量请求落在同一条库存记录上去做update时,行锁导致大量的锁竞争会使得数据库的性能急剧下降,性能无法满足要求。
所以演进的方案就是排队,MQ异步处理:
如果瞬间流量特别大,可以使用消息队列削峰,异步处理。用户写请求过来的时候,先放到消息队列,再拿出来消费,每次有限的写请求到数据层,如果成功了再放下一批,直到库存不够, 串行化去扣减库存,可以一定程度上缓解数据库的并发压力。
三、总结
尽量将请求拦截在系统上游(越上游越好)。
读多写少的多使用缓存(缓存抗读压力)。
顺便在讲一下限流&降级&熔断
- 限流,就是限制请求,防止过大的请求压垮服务器;
- 熔断,服务有问题就熔断,一般熔断降级是一起出现;
- 降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务。