秒杀要解决的主要问题是:并发读与并发写
API设计原则
保证用户请求的数据尽量少、请求数尽量少、路径尽量短、依赖尽量少,不要有单点
秒杀架构原则
高可用
- 整个系统架构需要满足高可用性,流量符合预期的时候肯定要稳定
一致性
- 数据必须一致,即成交总量必须和设定的数量一致
高性能
- 系统的性能要足够强,支撑足够大的流量
动静分离
将用户请求的数据划分为动态数据和静态数据
对分离出来的静态数据做缓存
页面静态化
活动页面上绝大部分内容都是固定的,比如:商品描述、图片等
没有必要每次都去请求服务端,而是将这些静态的内容放到
CDN
上
- 每次打开页面的时候,直接去请求
CDN
服务器,能极大地减少后端的请求流量

MQ异步处理
秒杀活动一般涉及抢购、下单、支付、发货等阶段
- 而抢购与后续的几个阶段是可以异步执行的
为了避免对下单、支付、发货等阶段产生影响,可以将抢购阶段与后续阶段用
MQ
进行解耦处理
- 当用户抢购成功后,往消息队列中丢入
MQ
消息,随后再由订单系统消费进行下单处理
预热数据
在秒杀活动开始之前,可以手动将热点数据加载到缓存中,从而避免秒杀时去请求数据库。
限流、熔断、降级
限流
在每个业务系统做限流操作,从而避免因为请求太多,导致整个系统都无法工作。
当并发请求在正常范围内时,正常处理请求。
当超过设置的限流阈值时,则直接拒绝该请求,提示用户抢购失败。
熔断
请求的错误次数超过阈值时,不再到用后端服务,直接返回失败。
同时每隔一定时间放几个请求去重试后端服务,看看是否正常。
如果正常则关闭熔断状态,如果失败则继续快速失败。
熔断的目的是:
- 避免因下游短暂的异常,导致上游不断重试,最终造成下游有太多请求,最终压垮下游系统。
降级
当服务失败或异常后,返回指定的默认信息。
- 当下游异常时,与其返回空信息,不如返回一个有业务含义的默认信息,可以提高用户体验。
减库存
下单减库存
即当买家下单之后,在商品的总库存中减去买家购买的数量
这种方式控制最精确,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的现象
- 但是有些人下完单以后并不会付款
付款减库存
即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家
但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况
- 因为可能商品已经被其他人买走了
预扣库存
买家下单后,库存为其保留一定的时间(如 10 分钟),超过这个时间,库存将会自动释放
- 释放后其他买家就可以继续购买
在买家付款前,系统会校验该订单的库存是否还有保留:
- 如果没有保留,则再次尝试预扣
- 如果库存不足(也就是预扣失败)则不允许继续付款
- 如果预扣成功,则完成付款并实际地减去库存
避免超卖
在通过事务来判断,即保证减后库存不能为负,否则就回滚
直接设置数据库字段类型为无符号整数,这样一旦库存为负就会在执行
SQL
时报错使用
CASE WHEN
判断语句
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
高并发写
解决并发锁的问题,有两种办法
应用层排队
通过缓存加入集群分布式锁,从而控制集群对数据库同一行记录进行操作的并发度
- 同时也能控制单个商品占用数据库连接的数量,防止热点商品占用过多的数据库连接
数据层排队
应用层排队是有损性能的,数据层排队是最为理想的
业界中,阿里的数据库团队开发了针对
InnoDB
层上的补丁程序,可以基于DB
层对单行记录做并发排队
热点隔离
业务隔离:
- 秒杀作为一种营销活动,卖家需要单独报名,从技术上来说,系统可以提前对已知热点做缓存预热
系统隔离:
- 系统隔离是运行时隔离,入口层就让请求落到不同的集群中
数据隔离:
- 秒杀数据作为热点数据,可以启用单独的缓存集群
热点优化
缓存和限流