大促是电商一年里最刺激也最危险的一天。平时日均几万访问的系统,到了整点开抢可能瞬间涌进几十倍流量。如果只靠"多买几台服务器"来应对,几乎一定会翻车——因为大促的故障往往不是因为总容量不够,而是因为某个薄弱环节先被打穿,然后连锁雪崩。数据库连接池打满、缓存击穿、库存超卖、某个下游接口拖垮整个调用链,这些才是真实事故的常见剧本。
这篇文章不讲空泛的"高可用",而是把大促保障拆成可以落地的动作:怎么限流保住系统不被打死,怎么做多级缓存把大部分请求挡在数据库之外,怎么提前扩容加自动弹性扛住峰值,怎么准备降级预案兜底,怎么压测验证,以及库存和订单这种要命的数据一致性怎么处理。最后落到监控告警、值班和预案演练。
读完你应该能搭出一套完整的大促保障方案,而不是等出事那天再手忙脚乱。
一、先把大促的流量特征说清楚
大促流量和日常流量最大的不同,是突发性和脉冲性。日常流量是缓慢起伏的曲线,大促流量是整点开抢瞬间冲上峰值的尖刺。这个尖刺的高度可能是日常的几十倍,宽度却只有几分钟到十几分钟。
理解这个特征后,就能得出两条原则:
第一,容量要按峰值准备,但不必为峰值时刻的每一份资源都长期付费。 提前扩容 + 自动弹性是标准答案——平时用少量资源,峰值前手动扩到目标水位,弹性策略再兜住超预期的部分。
第二,保护系统的关键不是"扛住所有流量",而是"在流量超限时优雅地保住核心功能"。 大促时让一部分非核心请求降级、排队或拒绝,保住下单和支付的顺畅,比全站一起崩要高明得多。这就是限流和降级的价值。
二、限流:最外层的保险
限流的目的是让进入系统的流量不超过它能承受的上限,避免被打死。限流要分层做,从外到内形成多道闸门。
接入层限流。 在负载均衡或网关这一层做总入口限流,把明显超量的请求挡在最外面。Nginx 是最常用的工具,用 limit_req 做请求频率限制、limit_conn 做连接数限制:
# /etc/nginx/conf.d/limit.conf
# 定义限流区:按 IP 限制每秒 20 个请求
limit_req_zone $binary_remote_addr zone=perip:10m rate=20r/s;
# 限制单 IP 同时连接数
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
listen 80;
server_name example.com;
location /api/ {
# burst 允许突发 50 个排队,nodelay 表示突发请求立即处理不延迟
limit_req zone=perip burst=50 nodelay;
limit_conn addr 30;
proxy_pass http://backend;
}
}
Nginx 配置改完要先测试再平滑重载,避免语法错误导致服务中断:
nginx -t && nginx -s reload
应用层限流。 网关限流是粗粒度的,应用内部还要针对关键接口做精细限流,比如秒杀接口每个用户限购、每个接口的 QPS 上限。常用算法有令牌桶(允许一定突发)和漏桶(严格匀速),具体实现可以基于 Redis + Lua 做分布式限流,或者用框架自带的限流组件。
| 限流算法 | 特点 | 适用场景 |
|---|---|---|
| 固定窗口 | 实现简单,边界会有突刺 | 对精度要求不高的粗限流 |
| 滑动窗口 | 比固定窗口平滑,实现稍复杂 | 常规接口限流 |
| 令牌桶 | 允许突发流量,桶里有多少令牌就放多少 | API 网关、秒杀入口 |
| 漏桶 | 严格匀速流出,削峰填谷 | 下游处理能力固定时保护下游 |
限流的关键是设对阈值,并且能被监控。 阈值设得太低会误伤正常用户,设得太高等于没设。经验上阈值应该略低于压测出的系统容量上限,并留有缓冲。同时限流触发要有告警,否则你根本不知道系统在什么时候已经开始拒绝请求了。
三、多级缓存:把请求挡在数据库之前
大促时数据库是最容易被压垮的环节。缓存的核心思路是:能缓存的都缓存,让尽可能少的请求真正打到数据库。 电商场景适合做多级缓存——本地缓存、分布式缓存、CDN 逐层拦截。
本地缓存。 用进程内的缓存(比如 Caffeine、Guava Cache 这类)缓存热点且变化不频繁的数据,比如商品基础信息、类目、配置。本地缓存访问是内存级,延迟极低,但要注意多实例之间的数据一致性和容量控制。
分布式缓存。 Redis 承担跨实例共享的缓存,缓存商品详情、库存快照、用户会话等。大促时要特别注意几个经典问题:
- 缓存穿透:大量请求查一个不存在的 key,每次都打到数据库。解决办法是缓存空值(短过期)或用布隆过滤器挡掉不存在的 key。
- 缓存击穿:某个热点 key 过期瞬间,大量请求同时涌向数据库。解决办法是对热点 key 加互斥锁,只让一个请求去回源,其他等待;或者对超热 key 干脆不设过期、后台主动刷新。
- 缓存雪崩:大量 key 在同一时刻集中过期。解决办法是给过期时间加随机抖动,避免同时失效。
CDN 缓存。 对静态资源(图片、CSS、JS、商品主图)用 CDN 缓存,直接把流量挡在源站之外。大促前要确认 CDN 缓存策略正确,静态资源版本化(加 hash 文件名),避免用户拿到旧版本。
缓存是把双刃剑。 缓存越多,一致性问题越复杂。库存这种强一致的数据不能只靠缓存,要以数据库为准,缓存只做展示和预校验。
四、弹性扩容与容量准备
扩容有两种:提前扩和自动弹。两者要结合用。
提前扩容(计划性扩容)。 大促时间是可预期的,所以在开抢前几小时甚至前一天,就应该把容量扩到目标水位。包括应用实例数量、数据库规格、缓存规格、消息队列、带宽。提前扩的好处是避免弹性扩容的冷启动延迟——新实例启动需要时间,如果等流量上来再扩,可能来不及。经验上,计划性扩容通常在活动开始前 1–4 小时完成,并留出验证时间。
自动弹性扩容。 应对超出预期的流量。触发条件不能只看 CPU 一种指标,因为有些瓶颈(比如线程池排队)CPU 并不高。建议综合 CPU、内存、请求队列长度、接口响应时间等多个指标。还要注意扩容的滞后性:从指标触发到新实例真正能服务有几十秒到几分钟的延迟,所以触发阈值要提前设,比如 CPU 到 60% 就开始扩,而不是等到 90%。
# 示例:用云厂商 CLI 或 API 调整伸缩组期望实例数(不同厂商命令不同,此处示意)
# 查看当前实例数与负载,结合监控面板确认后再调整
# 直接手工扩容前务必确认:镜像/启动脚本可用、依赖下游能承受、不会触发雪崩
# 本地快速查看应用实例负载(用于判断是否需要扩容)
uptime # 查看系统负载
free -h # 查看内存使用
ss -s # 查看连接数汇总
扩容不是无脑加机器。 加机器前要确认下游扛得住——如果数据库已经是瓶颈,加再多应用实例也只会把数据库压得更死。扩容的顺序应该是:先扩下游瓶颈(数据库、缓存),再扩应用层。
五、降级预案:保住核心功能
降级是"资源不够时主动砍掉次要功能,保住核心链路"。大促时最该保住的是下单和支付,其他诸如推荐、评论、积分、排行榜、复杂的个性化展示,都可以临时降级。
降级的常见做法:
- 关闭非核心功能:大促时把推荐算法降级为热门榜单、把评论加载改为"点击加载"、关闭积分商城入口。
- 返回兜底数据:某个接口超时或异常时,返回缓存里的旧数据或默认值,而不是让整个页面报错。
- 简化业务逻辑:取消不必要的校验和计算,走更短的路径。
- 异步化:把非实时的操作(发短信、发通知、写日志)丢进消息队列异步处理,不阻塞主流程。
降级预案必须提前写好、做成开关、可一键切换。真正出事时,没人有空写代码。开关要能通过配置中心热更新,并且降级后的逻辑要提前测试过。
六、全链路压测:别等大促当天才知道能不能扛住
压测是唯一能在上战场前知道系统底细的手段。全链路压测的要点:
- 模拟真实流量模型,不只是压下单接口,而是按照大促的真实比例压整个链路(浏览、搜索、加购、下单、支付)。
- 压测到略超预期峰值,比如预期峰值是 10 万 QPS,就压到 12 万,看系统在哪里先崩、崩的表现是什么。
- 观察瓶颈点,重点看数据库 QPS、慢查询、连接池使用率、缓存命中率、消息队列积压、下游接口响应时间。
- 压测要隔离,压测流量不能污染生产数据。常见做法是压测标记 + 数据落影子库,避免压测订单进入真实数据库。
压测之后要输出一份"容量水位表",记录各环节能扛住的 QPS 上限,这份表就是限流阈值和扩容目标的依据。
七、库存与订单一致性:最不能出错的地方
大促最容易出大事故的地方,就是库存和订单。超卖(实际卖出超过库存)和少卖(库存还有却没卖出去)都会造成客户投诉和资损。
保证一致性的常见手段:
- 扣减库存走原子操作:用数据库的
UPDATE ... WHERE stock >= n这种带条件的原子更新,或者用 Redis 的原子命令(配合 Lua 脚本)做预扣,避免"先查后扣"造成的并发超卖。 - 幂等设计:下单、支付接口必须支持幂等,同一个请求重复到达不会重复扣库存、重复创建订单。常用方案是请求唯一 ID + 去重表,或状态机流转。
- 最终一致性:订单创建、库存扣减、优惠券核销这些可能跨服务,用消息队列做最终一致,配合对账补偿。
- 对账机制:大促期间和之后要有定时对账,及时发现库存、订单、支付三方的数据不一致并人工干预。
这部分宁可保守也不能冒险——下单慢一点可以忍,超卖和重复扣款是信任危机。
八、监控告警与值班
大促当天,人盯不过来,必须靠监控告警。核心监控指标分几类:
- 业务指标:下单量、支付成功率、库存变化、订单积压。
- 应用指标:各接口 QPS、响应时间(P95/P99)、错误率、线程池/连接池使用率、GC 情况。
- 中间件指标:缓存命中率、数据库连接数、慢查询、消息队列积压。
- 系统指标:CPU、内存、磁盘 IO、网络带宽。
告警要分级,避免告警风暴。设好值班表,明确每个模块的负责人和升级路径。同时要准备应急预案手册,把常见故障的处理步骤写清楚,谁都能照着操作。
预案演练也很重要。大促前做几次真实的演练,比如人为把某个缓存节点下线、模拟数据库主库切换、演练限流开关和降级开关,验证预案是否真的有效。演练过和没演练过的预案,效果差别巨大。
常见问题
Q:大促一定要扩容好几倍吗?
不一定。如果日常峰值和促销峰值差距不大,主要靠限流、缓存和降级就够了。只有流量差距悬殊、且核心链路确实需要更多算力时才大幅扩容。关键是先压测出真实容量,再决定扩多少。
Q:限流会不会误伤真实用户?
会,所以阈值要经过压测验证,并区分维度。比如按 IP 限流对 NAT 出口(很多用户在同一个出口 IP)可能误伤,需要考虑按用户维度或会话维度限流,并对关键入口(如秒杀)单独设计策略。
Q:缓存和数据库的数据不一致怎么办?
接受短暂不一致、追求最终一致是常见做法。更新策略上用"先更新数据库再删除缓存",配合延迟双删或消息驱动刷新。对强一致的库存,不走普通缓存,用原子操作或专门的库存服务。
Q:降级会不会影响用户体验?
会,但这是权衡。降级的目标是保住核心链路可用,牺牲的是次要功能。关键是提前和业务方沟通好哪些能降、降到什么程度,并通过开关精细控制,做到只降该降的。
Q:自动弹性扩容为什么不生效?
常见原因:触发指标选错(只看 CPU,忽略了真正的瓶颈)、阈值设太高导致来不及、扩容冷启动太慢、或者下游已经成瓶颈导致扩了也没用。排查时要结合整个链路找真正的瓶颈点。
总结
- 大促保障的核心不是"堆硬件",而是分层防护 + 保核心链路:用限流在最外层挡住超量流量,用缓存把请求挡在数据库之前。
- 限流要分层做(接入层 + 应用层),选对算法(令牌桶/漏桶),阈值按压测结果设定并配告警。
- 多级缓存(本地 + 分布式 + CDN)是扛大促的关键,同时要防住缓存穿透、击穿、雪崩三个经典问题。
- 提前扩容 + 自动弹性结合,扩容顺序是先扩下游瓶颈再扩应用层,别让数据库成为新瓶颈。
- 降级预案、全链路压测、库存订单一致性、监控值班和预案演练缺一不可,其中库存和订单的一致性是绝对不能出错的底线。
企业QQ咨询




