持续秒杀高并发技术方案与例子
前端
-
针对静态资源做CDN
- 方案: 将图片、CSS、JS、商品介绍静态页等不常变化的资源,分发到离用户更近的CDN节点。
- 例子: 将秒杀商品的图片、商品描述页的静态HTML、页面样式文件
style.css
、交互脚本countdown.js
上传并托管到阿里云OSS + CDN 或 腾讯云COS + CDN。用户访问时,从最近的CDN节点获取这些资源,极大减轻源站压力,加速页面加载。
-
页面静态化
- 方案: 将动态生成的页面(特别是商品详情页、活动页)提前渲染成纯HTML文件。
- 例子: 在秒杀活动开始前5分钟,通过后台任务,将参与秒杀的100个核心商品的详情页预先渲染成静态HTML文件(如
product_123.html
),并存放在Nginx或CDN上。用户访问商品页时,直接返回这个静态文件,无需查询数据库和动态渲染。
-
倒计时 & Loading
- 方案: 在客户端(浏览器/APP)展示精确的秒杀开始倒计时;在提交抢购请求后显示明确的加载状态。
- 例子:
- 倒计时: 页面加载时,前端JS从服务端获取一个精确的服务器时间戳作为基准,然后本地计算并显示“距秒杀开始 00:05:23”。避免用户频繁刷新页面看时间。
- Loading: 用户点击“立即抢购”后,按钮立即变为不可点击状态,并显示“抢购中…”的旋转图标或进度条。直到收到后端明确的成功/失败/排队响应后,才更新状态。防止用户因无反馈而重复提交。
-
使用验证码削峰
- 方案: 在提交抢购请求前,增加一个需要用户交互的验证环节(非纯计算型)。
- 例子: 用户点击“立即抢购”后,弹出一个滑动拼图验证码或点选文字验证码(如极验、腾讯防水墙)。用户必须完成验证才能将真正的抢购请求发送到后端。这能有效过滤掉:
- 简单脚本和部分机器流量。
- 部分不耐烦的人类用户(轻微削峰)。
- 关键点: 验证码本身不能太复杂耗时,否则用户体验极差。它主要目的是增加自动化攻击的成本和稍微平滑请求洪峰。
后端
-
微服务 - 服务拆分
- 方案: 将系统拆分成职责单一、独立部署的小服务。
- 例子: 将秒杀系统拆分为:
- 用户服务: 处理登录、认证。
- 商品服务: 管理商品信息、库存查询(注意:秒杀库存通常单独管理)。
- 活动服务: 管理秒杀活动配置、时间。
- 订单服务: 处理订单创建、支付。
- 库存服务: 核心! 专门负责秒杀库存的预扣减、最终扣减、恢复。独立部署,资源隔离,避免其他服务故障拖垮库存核心逻辑。
- 秒杀服务: 处理抢购请求入口、令牌发放、请求排队、调用库存服务扣减。
-
负载均衡
- 方案: 将入口流量分发到多个后端服务实例。
- 例子: 使用 Nginx / HAProxy / 云服务商的负载均衡器(如 AWS ALB, 阿里云 SLB)作为流量入口。配置其将 HTTP/HTTPS 请求均匀分发(如轮询、加权轮询、最少连接)到后端的几十台甚至上百台运行着“秒杀服务”的服务器上。
-
限流 & 降级
- 方案: 控制单位时间内进入系统的请求量;在系统压力过大时,暂时关闭非核心功能。
- 例子:
- 限流:
- 网关层限流: 在 API Gateway (如 Spring Cloud Gateway, Zuul) 或 Nginx 上,对
/seckill/submit
接口配置全局限流规则,例如每秒只允许通过 10,000 个请求到后端服务集群,超出部分直接返回 “系统繁忙,请稍后再试”。 - 服务层限流: 在秒杀服务内部,使用 Sentinel 或 Resilience4j 对关键方法(如库存扣减方法)配置 QPS 阈值限制。
- 网关层限流: 在 API Gateway (如 Spring Cloud Gateway, Zuul) 或 Nginx 上,对
- 降级:
- 当系统 CPU/内存/线程池负载过高时,自动触发降级规则。例如:暂时关闭商品详情页的商品推荐功能、关闭用户积分实时更新、将部分日志写入队列异步落盘而非实时写入数据库。
- 秒杀活动结束后,关闭排队通道,直接返回“活动已结束”。
- 限流:
-
缓存
- 方案: 将高频读取的热点数据存储在内存中,减少对数据库的直接访问。
- 例子:
- 热点商品信息: 使用 Redis 缓存参与秒杀的商品基础信息(名称、图片、原价),Key 如
seckill:goods:info:{goodsId}
,设置较短过期时间(如5-10分钟)。 - 库存预热: 在秒杀活动开始前,将秒杀商品的 可秒杀库存数量 加载到 Redis 中(例如
seckill:stock:{goodsId}
)。核心操作(预扣减)直接在 Redis 中进行(使用 Lua 脚本保证原子性)。数据库中的库存作为最终记录,通过异步或最终一致的方式同步。 - 页面缓存: 将静态化页面的 HTML 内容缓存在 Redis 或本地缓存中。
- 防穿透/击穿: 对于查询单个商品库存的请求,如果缓存没有,先从数据库加载并放入缓存。使用
setnx
或分布式锁防止缓存失效时大量请求击穿数据库。对于不存在商品的查询,在缓存中设置一个空值短时间有效。
- 热点商品信息: 使用 Redis 缓存参与秒杀的商品基础信息(名称、图片、原价),Key 如
-
令牌(Token)
- 方案: 在用户真正发起秒杀请求前,先发放一个代表抢购资格或排队位置的令牌。
- 例子:
- 资格令牌: 活动开始前几分钟,用户进入秒杀页面时,服务端根据用户ID或设备ID生成一个唯一令牌(Token),存入 Redis 并设置较短有效期(如30秒)。用户点击“抢购”时,必须携带此 Token。服务端验证 Token 有效且未被使用过,才允许进入后续扣减库存流程,然后立即失效该 Token。防止脚本无限刷接口。
- 排队令牌: 当瞬时流量远超处理能力时,将抢购请求放入一个分布式队列(如 Redis List/Stream, Kafka, RocketMQ)。服务端返回给用户一个排队号(Token)和预估等待时间。用户端轮询或用 WebSocket 等待通知。服务端按队列顺序逐个处理请求(扣减库存、创建订单)。
-
异步处理
- 方案: 将非实时性要求的操作解耦,放入队列由后台任务处理。
- 例子:
- 订单创建: 核心库存扣减成功后,立即返回用户“抢购成功”。将生成正式订单、扣减账户积分、发送通知短信等操作封装成一个消息,发送到消息队列(如 RabbitMQ, Kafka, RocketMQ)。由专门的“订单创建服务”异步消费消息完成这些耗时操作。即使订单创建暂时慢一点,用户也感知不到。
- 日志记录: 将用户访问、抢购行为的日志先写入本地文件或 Kafka,再由 Flume/Logstash 等工具异步收集到 Elasticsearch 或 HDFS。
数据库
-
分库:业务分库、读写分离
- 方案: 按业务模块拆分数据库;将读操作和写操作分离到不同数据库实例。
- 例子:
- 业务分库: 创建独立的
用户库
、商品库
、订单库
、活动库
。秒杀相关的核心库存表可能放在一个单独的库存库
中。避免所有业务表挤在一个库里相互影响。 - 读写分离: 为
订单库
配置一主(Master,负责写订单)多从(Slave,负责读订单查询)。秒杀服务扣减库存写库存库
主库;查询库存余量(在秒杀结束后)可以走库存库
的从库。注意: 秒杀中的库存查询(扣减前校验)通常走缓存或主库,从库有延迟。
- 业务分库: 创建独立的
-
分表:横向分表(分片)、纵向分表
- 方案: 将单张大表按行拆分到多张物理表(水平分表);或按列拆分(垂直分表)。
- 例子:
- 水平分表:
- 订单表: 按
用户ID
哈希取模分表(如order_00
,order_01
, …order_99
),将不同用户的订单分散到100张表。 - 秒杀记录表: 按
活动ID
+日期
分表(如seckill_record_20240531
),每天一张表。或者按商品ID
范围分表。
- 订单表: 按
- 垂直分表:
- 商品详情表: 将商品基础信息(ID, 名称, 价格)放在
goods_base
表;将商品详细描述、参数等大文本字段放在goods_detail
表,通过goods_id
关联。减少查询基础信息时的 IO。
- 商品详情表: 将商品基础信息(ID, 名称, 价格)放在
- 水平分表:
-
冗余设计,反范式,空间换时间
- 方案: 适当增加数据冗余,减少复杂 JOIN 查询;以存储空间换取查询速度。
- 例子:
- 订单列表显示: 在
订单表
中冗余存储商品名称
、商品主图
。这样查询用户订单列表时,无需去关联商品表
就能直接展示关键信息。 - 秒杀库存预扣减: 在 Redis 中单独维护一份秒杀库存(冗余),核心扣减操作在内存中完成,速度极快。数据库中的库存作为最终记录源。
- 计数器: 使用 Redis 的
INCR
命令维护用户秒杀次数计数器,避免每次查数据库COUNT
。
- 订单列表显示: 在
-
分布式数据库
- 方案: 采用原生支持分布式架构的数据库。
- 例子:
- 使用 TiDB 作为订单库或库存库的底层存储。TiDB 自动处理数据分片(水平分表)、负载均衡、高可用和分布式事务(虽然秒杀场景尽量避免分布式事务)。
- 使用 OceanBase 替代传统的 MySQL 主从架构,获得更强的扩展性和一致性能力。
- 使用 Amazon Aurora 或 Google Cloud Spanner 这类云原生分布式数据库。
其它
-
分时段秒杀
- 方案: 将一场大的秒杀活动拆分成多个不同时间点开始的小场次。
- 例子: 原本计划在 20:00 放出 10,000 台手机秒杀。改为在 20:00, 20:15, 20:30, 20:45 四个时段,每时段放出 2,500 台。分散瞬时流量压力,也给用户更多机会。
-
弹性扩容
- 方案: 根据实时流量,自动增加或减少计算资源(服务器、容器)。
- 例子:
- 使用 Kubernetes (K8s) 部署秒杀服务。配置基于 CPU 负载或请求 QPS 的 Horizontal Pod Autoscaler (HPA)。当监控到请求队列积压或 CPU 飙升时,自动从 50 个 Pod 扩容到 200 个 Pod。活动结束后,自动缩容。
- 云服务商:利用阿里云的弹性伸缩组(ESS)、AWS 的 Auto Scaling Group,配置类似的扩容缩容策略。
-
候补 + 排队
- 方案: 在库存秒光后,允许用户加入候补队列;在流量过大时,让用户进入排队等待状态。
- 例子:
- 候补: 商品显示“已售罄”,但有“开启候补”按钮。用户点击后支付定金加入候补队列。如果有用户超时未支付尾款或取消订单,系统按候补顺序自动为排在最前的候补用户创建订单并通知支付尾款。
- 排队: 见 后端 - 令牌(排队令牌) 的例子。在用户点击抢购瞬间,如果系统判断当前处理能力已饱和,不是直接拒绝,而是返回“排队中,您当前排在第 3521 位,预计等待 2 分钟”,并提供取消排队按钮。后台队列处理完前面的请求后,通知该用户获得购买资格。