秒杀系统设计
秒杀系统设计
秒杀系统所要应对的场景就是:瞬时海量请求。
秒杀系统的难点
- 高并发:秒杀系统是极致的高并场景发自不用说。其高并发可以细分为二:
- 并发读:主要是读取剩余库存量以及商品信息
- 并发写:主要是下单后,系统写入订单记录
- 超卖:秒杀系统中售卖的商品一般都是性价比很高,不怎么赚钱,甚至赔钱赚哟喝的商品。一旦出现超卖现象,会给商家带来巨大的经济损失。从系统层面来看,比如某秒杀商品本来库存 100 件,但是在高并发场景下,瞬时下单量超过 100 件,处理不当,让这些下单都成功了,就会出现超卖。
- 恶意请求:有些人为了低价购入秒杀商品,通过在多台机器上跑脚本,模拟大量用户抢商品的请求(走自己的路,让别人无路可走)。
- 数据库崩溃:海量请求下,如果没有 MQ 削峰,没有过载保护,让所有请求都打到数据库,那么数据库基本就挂了。数据库如果挂了,也会波及其他业务,从而可能让整个系统、网站陷入瘫痪。
- 对现有业务造成冲击
秒杀系统的思考
稳准快
秒杀系统架构的思考角度可以概括为:稳、准、快
- 稳(高可用):系统架构要满足高可用,系统要能撑住活动。
- 准(一致性):商品减库存方式非常关键,不能出现超卖。
- 快(高性能):整个请求链路,从前端到后端,依赖组件都要做到协同优化。
前端优化
静态页面
把秒杀商品页面静态化,减少查数据库的 IO 开销。然后,可以将这些静态页面做 CDN 缓存,如果项目是前后端分离的,还可以在反向代理服务器侧设置静态缓存。
如每个商品都由 ID 来标识,那么 http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的 URL 标识。相应的页面可以提前做前端缓存,这样就不需要向后台查询商品信息。
按钮控制
在秒杀活动开启时间前,下单按钮禁用。
此外,按钮一旦点击之后,禁用一段时间,防止有人疯狂输出。
后端优化
隔离
秒杀活动,本质上还是一个营销活动,性质和打折、促销一样。
秒杀系统设计底线原则,是不应该影响现有业务。所以,为了避免防不胜防,百密一疏的情况下,秒杀系统崩了。
限流、熔断、降级、隔离
隔离:将秒杀系统、数据与其他正常业务隔离。彼此隔离,自然互不影响。
限流:设置阈值,超过阈值,拒绝请求。防止数据库被打死。
降级:保证核心业务继续工作,非核心业务各安天命。
熔断:不要影响别的系统。
缓存
缓存要预热,避免瞬间流量冲击。
此外,防止雪崩、穿透、击穿问题的常规处理要做好。
缓存也要保证高可用。
流量削峰
削峰的思路:排队、答题、分层过滤。
- 排队:用消息队列来缓冲瞬时流量的方案。但是,消息队列自身也有上限,如果积压过多,也会处理不了。
- 答题(摇一摇):可以限制秒杀器并延缓请求。
- 分层过滤:采用漏斗式的设计尽可能拦截无效请求。
减库存
恶意下单
恶意下单的解决方案还是要结合安全和反作弊措施来制止:
- 识别频繁下单不付款或重复下单不付款的卖家,阻断其下单。
- 限制个人购买数
避免超卖
减库存在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数,一般我们有多种解决方案:一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚;另一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错;再有一种就是使用 CASE WHEN 判断语句,例如这样的 SQL 语句:
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
在交易环节中,“库存”是个关键数据,也是个热点数据,因为交易的各个环节中都可能涉及对库存的查询。但是,我在前面介绍分层过滤时提到过,秒杀中并不需要对库存有精确的一致性读,把库存数据放到缓存(Cache)中,可以大大提升读性能。
URL 动态化
通过 MD5 之类的加密算法加密随机的字符串去做 url,然后通过前端代码获取 url 后台校验才能通过。