高并发秒杀系统设计:从 CDN 到数据库的全链路架构
秒杀系统的流量洪峰不只是后端的事——CDN 扛不住静态资源、Nginx 限流配置不当、网关熔断策略缺失,任何一个环节出问题都可能导致系统崩溃。本文从全链路视角梳理秒杀系统的分层架构,涵盖 CDN 加速、Nginx 限流、API 网关熔断降级、Redis+Lua 库存扣减、MQ 异步下单,以及前端防刷策略,讲清楚每一层如何协同工作。
秒杀系统的流量是全链路的——从用户点击按钮,到 DNS 解析,到 CDN 节点,到 Nginx 接入层,到 API 网关,到业务服务,到 Redis 缓存,最后到数据库。任何一个环节扛不住,整个系统就崩了。
这篇文章把视角拉高,从 CDN 到数据库,把每一层的职责、技术和配置都讲清楚。
核心思想:流量漏斗
秒杀系统设计的核心思想可以用一个词概括:分层削峰。
几亿人点击,最后落到数据库的订单可能只有几千单。每一层都要把流量“砍”掉一大半,不让无效请求穿透到下一层。
每一层的目标都不同,但逻辑一致:尽量把请求拦截在上游,减少对核心资源的竞争。
下面逐层拆解。
第一层:前端——把无效点击挡在手机里
前端是距离用户最近的一层,也是成本最低的拦截点。能让用户手机自己解决的,千万别发到服务器来。
按钮防抖。用户点击秒杀按钮后,立即置灰 5-10 秒,禁止重复点击。这是最基本也是最有用的操作——能挡掉 80% 的无效点击。
// 按钮点击后立即置灰
const seckillBtn = document.getElementById('seckill-btn');
seckillBtn.addEventListener('click', function() {
this.disabled = true;
this.innerText = '处理中...';
// 发送请求...
setTimeout(() => {
this.disabled = false;
this.innerText = '立即抢购';
}, 5000);
});验证码/滑块。在点击购买前弹出验证码或滑块验证,第一防脚本机器人,第二把同一秒的点击拉平到几秒内——有人反应快有人反应慢,服务器峰值瞬间削减一半。
请求去重与合并。相同参数的并发请求复用同一个 Promise,避免多个组件各自发起同一接口请求造成风暴。
客户端限流。App 端可以内置逻辑:检测到并发过高或接到服务器降级指令后,用户点击直接提示“人太多”,连请求都不发出去。
时间校准。用户手机时间可能不准。App 启动时拉取服务器时间,倒计时以服务器时间为准,防止用户修改手机时间提前抢。
URL 动态化。秒杀链接不要用 /buy/product/123 这种固定格式,脚本几毫秒就能猜出来。链接必须后端动态下发,带上随机签名,比如 /buy/ak9d8s7d8/123。
第二层:CDN——静态资源别来占源站带宽
秒杀开始前,用户会不断刷新商品详情页。如果这些请求全部打到源站,再强的服务器也扛不住。
页面静态化。秒杀商品详情页的图片、文案、CSS、JS 全部静态化,部署到 CDN 节点。用户访问时直接从 CDN 加载,不经过后端服务。秒杀页面的静态内容占比通常超过 80%,CDN 能显著降低源站压力。
动静分离。秒杀页面的 HTML、JS、CSS、图片全部放到 CDN 上。用户刷新页面走的 CDN 流量,不占用源站带宽。只有秒杀按钮需要服务端动态判断。
智能预热。秒杀前 30 分钟,CDN 系统启动“智能预热”——不只是简单地把静态页推到边缘,而是结合历史成交、实时加购、搜索热度等多维度数据,预测真正会被抢的商品。
缓存策略。静态资源设置合理的缓存过期时间,建议 1-5 分钟。对图片、CSS、JS 等可设置更长缓存(如 30 天)。
CDN 注意事项。秒杀场景下 CDN 需要有秒级的失效系统,热点数据需要提前预热。特殊场景下可以控制 CDN 节点数量及分布来提高命中率。动态页面也可以静态化放在 CDN 中,由 CDN 定期去源站更新数据。
第三层:Nginx 接入层——入口处的流量安检
流量出了手机,经过 CDN,到达的第一道服务器关口就是 Nginx 接入层。这一层要做的事情像机场安检:拦截危险分子、控制流量速率、均匀分发请求。
IP 限流(漏桶算法) 。Nginx 的 limit_req_zone 模块基于 IP 维度限制请求频率。比如单 IP 每秒最多 5 次请求,超出直接返回 429。
http {
# 定义限流区域:基于IP,内存10MB,速率每秒100请求
limit_req_zone $binary_remote_addr zone=seckill:10m rate=100r/s;
server {
location /seckill {
# burst=50 允许瞬间突发50个请求排队
# nodelay 表示超过burst直接拒绝
limit_req zone=seckill burst=50 nodelay;
proxy_pass http://seckill_backend;
}
}
}黑名单拦截。在 Nginx 层维护恶意 IP 名单,黑名单请求直接返回 403,不进入后端。
动静分离。Nginx 区分静态资源和动态 API 请求,静态资源直接返回本地文件或 CDN 地址,动态请求才转发到后端。
连接池优化。配置 upstream 的 keepalive,保持长连接,减少每次请求的三次握手开销。
upstream seckill_backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 1000; # 保持1000个长连接
keepalive_timeout 60s;
}OpenResty + Lua 动态限流。对于更复杂的限流需求(比如基于用户 ID 而不是 IP),可以用 OpenResty(Nginx + Lua)实现令牌桶限流。
第四层:API 网关——微服务架构的流量总闸
如果系统是微服务架构,Nginx 后面通常还有一层 API 网关(如 Spring Cloud Gateway、Kong、Sentinel)。网关层的职责比 Nginx 更细:限流、熔断、降级、鉴权。
接口级限流(令牌桶) 。使用 Sentinel 或 Hystrix 对秒杀接口做限流,设置每秒最大处理能力(如 1000 QPS),超出部分直接返回“秒杀火爆,请稍后再试”。
熔断机制。当后端服务响应时间过长或错误率超过阈值(如 50%),熔断器自动打开,阻止流量进入后端,执行降级策略。
# Sentinel 配置示例
流量控制规则:
- 资源名: /seckill/order
QPS阈值: 1500
超出后: 直接拒绝
熔断降级规则:
- 资源名: /seckill/order
超时阈值: 500ms
错误率阈值: 50%
熔断时长: 30s统一鉴权。网关层用 JWT 做统一鉴权,解密后就知道用户身份是否合法,不合法直接踢回去,不需要查数据库。
服务降级。非核心功能(如商品评价、推荐)在 QPS 超过阈值时自动关闭,释放资源给核心秒杀链路。
带宽临时扩容。活动前临时增加带宽,平时 1G 带宽拉到 10G,避免网络成为瓶颈。
第五层:业务层——Redis + Lua + MQ
这一层在上篇文章里已经详细讲过了,这里简要回顾,放在全链路中看它的位置。
Redis 缓存库存。将商品库存预加载到 Redis,使用 Lua 脚本保证“查库存、判库存、扣库存、记录用户”的原子性。Redis 单机 QPS 可达 10 万+。
MQ 异步下单。扣库存成功的请求进入消息队列,后端消费者异步创建订单。流量从“尖峰”变成“平缓曲线”,数据库不会被打死。
服务限流。应用层用 Sentinel 或 Hystrix 对秒杀接口做限流,设置每秒最大处理能力。
第六层:数据层——最终一致性兜底
经过前面五层的层层拦截,到达数据库的流量已经很少了。但数据层仍然需要做几件事:
分库分表。按用户 ID 哈希分库分表,分散写入压力。
读写分离。主库负责写,从库负责读。
最终一致性。秒杀场景追求最终一致性而非强一致性。订单异步写入,通过数据库唯一键约束防止重复下单。
完整链路:从点击到订单
把每一层串起来,一个秒杀请求的完整路径是这样的:
每一层拦截的流量类型不同:
| 层级 | 拦截目标 | 拦截手段 |
|---|---|---|
| 前端 | 重复点击、脚本请求 | 按钮防抖、验证码、请求合并 |
| CDN | 静态资源请求 | 页面静态化、缓存策略 |
| Nginx | 恶意IP、高频IP | IP限流、黑名单 |
| API网关 | 超量请求、异常服务 | 令牌桶限流、熔断降级 |
| Redis | 库存不足的请求 | Lua脚本库存判断 |
| MQ | 数据库写入压力 | 异步削峰 |
监控与压测
架构搭好了,不压测等于没搭。
全链路压测。建立从用户点击到数据库事务的流量模型,精确计算每层服务的容量配比,避免出现“应用层已扩容,但数据库成为瓶颈”的木桶效应。用 JMeter 模拟千万级并发,逐步加压直到发现系统瓶颈。
核心监控指标:
- 基础设施:CPU、内存、网络、磁盘 IO
- 接入层:Nginx 连接数、限流命中率、响应时间
- 业务层:QPS、RT、错误率、库存扣减成功率
- 数据层:数据库连接数、慢查询、MQ 积压量
链路追踪。全链路性能分析,快速定位瓶颈在哪个环节。
总结
秒杀系统的全链路架构,本质上是 “漏斗模型”在每一层的落地。
- 前端挡掉 80% 的无效点击
- CDN 挡掉 80% 的静态资源请求
- Nginx 挡掉恶意 IP 和超频请求
- API网关 做限流熔断,保护后端不被冲垮
- Redis 在内存里完成库存扣减,只放行少量有效请求
- MQ 把下单异步化,保护数据库
每一层都只做一件事,但每一件事都必不可少。流量层层削减,每一层都只处理它该处理的事,不让无效请求穿透到下一层。
理解了这个全链路视角,再去看具体的代码实现和配置细节,方向就不会偏。秒杀系统的设计不是某一个环节的优化,而是从用户点击到数据库落库的每一个环节都要经得起考验。