
去年双11 零点刚过,我们香港机房的前端节点流量瞬间飙到平时的 20 倍,部分页面加载超过 8 秒,下单漏单率接近 12%。监控图一片血红,我的手机被告警轰炸得震动失灵。那一夜,我痛定思痛:流量洪峰面前,没有缓存根本撑不住。
两周后,我在 A5数据 香港 Tseung Kwan O 机房重新部署 Nginx 缓存层,把首页首字节时间压到 85 ms,QPS 峰值抬升到 35 k/s,CPU 占用降了一半。本文记录这场“缓存手术”的全过程,希望能给同样在电商高并发泥潭里挣扎的你,带来可复制的操作与避坑经验。
1. 业务目标与约束
| 维度 | 双 11 前 | 目标 |
|---|---|---|
| 峰值 QPS | 18 k/s | ≥ 30 k/s |
| 首页 TTFB | 600 ms | ≤ 100 ms |
| 订单漏单率 | 12 % | < 0.1 % |
| 运营活动灰度 | N/A | 支持 5 % 流量灰度 |
硬件基线
- 服务器:A5 HK c3.metal × 4(32C Intel Ice Lake + 256 GB RAM + 2 × 1.92 TB NVMe)
- 网络:专线 10 Gbps + BGP Anycast 保护
- 内核:Linux 5.15 LTS(开启 BBR2)、NUMA interleave
2. 总体架构
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Cloud WAF│ -> │ Nginx LB │ -> │ Nginx Cache │ -> Upstream (K8s: cart, product, checkout)
└──────────┘ └──────────┘ └──────────────┘
| |
| └─ Redis (cache tags /锁)
└─ Promtail + Loki (日志) / Grafana (监控)
- 双层 Nginx:第一层仅做 TLS 终结 & L4 负载,第二层专职缓存。这样缓存节点可横向扩展且可无损重启。
- Redis:存储缓存标签和分布式锁,避免缓存雪崩。
- Promtail + Loki:按 key/status 维度实时聚合命中率。
3. 缓存策略设计
3.1 缓存分级
- 静态文件(JS/CSS/图):expires 30d + etag on(Edge/CDN 兜底)。
- 半动态页面(商品列表、详情页):Nginx proxy_cache,失效用 Cache-Tag。
- 极动态接口(库存、价格、下单):不缓存,走 RPS 限流 + 本地化队列。
3.2 关键原则
- 粒度可控:URL + 用户分群(登录/未登录、地区)
- 可灰度:业务 header X-Release-Id 纳入 key
- 可强制刷新:PURGE /uri + Admin API,或通过 Redis tag 广播失效
- 抗雪崩:proxy_cache_lock on + 锁超时 2 s
4. Nginx 核心配置
# /etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=ec_zone:4g
max_size=120g inactive=1h
use_temp_path=off
keepalive_timeout=60s;
# map 命中维度:不同用户态拆分
map http_cookiecache_bucket {
default "anon";
"~*session=" "login";
}
# 关键路由块
server {
listen 443 ssl http2 reuseport backlog=65535;
server_name www.example.com;
# —— SSL 省略 ——
location / {
proxy_pass http://k8s_cluster;
proxy_http_version 1.1;
proxy_set_header Host host;
proxy_set_header X-Forwarded-Forremote_addr;
# ------ 缓存关键指令 ------
set cache_key "schemeproxy_hostrequest_uri|cache_bucket|http_x_release_id";
proxy_cache ec_zone;
proxy_cache_key cache_key;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_ignore_headers Set-Cookie;
add_header X-Cache-Statusupstream_cache_status always;
}
# 失效接口(简化)
location /cache/purge {
allow 10.0.0.0/8; deny all;
proxy_cache_purge ec_zone "$arg_key*";
}
}
实现细节
- levels=1:2:512 × 256 避免 inode 拥塞。
- keys_zone=4g:足够容纳 ≈ 3 M 条键(按 1.4 k/键 估算)。
- use_temp_path=off 直接写缓存盘,减少 /tmp 抖动。
- 结合 systemd-tmpfiles 定期清理失活对象,防止磁盘爆仓。
5. 缓存标签与批量失效
5.1 业务侧输出 Cache-Tag
header('Cache-Tag: product-123, category-456');
5.2 Redis 实现
-- publish.lua
local tags = KEYS
for i, tag in ipairs(tags) do
redis.call("PUBLISH", "cache_purge", tag)
end
return #tags
Nginx nchan_subscriber 订阅 cache_purge,收到后打 proxy_cache_purge。实测单节点 20 k tag/s 失效无压力。
6. 灰度发布与回滚
- CI 在 Argo CD 生成 release_id=20250616_01。
- 前端网关携带 X-Release-Id。
- 新版本与老版本缓存空间天然隔离,可并行对比性能。
- 如果发现 5xx 飙升,切回老版 header,瞬间回滚,无需清理缓存。
7. 监控与自动化
| 指标 | 命令 / 组件 | 告警阈值 |
|---|---|---|
$upstream_cache_status hits / misses |
Prometheus nginx_vts_exporter | 命中率 < 75 % |
| 磁盘 IOPS & util% | node_exporter / blkstat | util > 60 % |
proxy_cache_lock_waiting |
Lua 计数器 | > 500 |
| 平均写延迟 | iostat | > 1 ms |
Grafana 看板:命中率、TTFB P99、PURGE QPS、锁等待曲线。
自动化脚本:当 util% > 70 % 自动扩容到新节点并同步 /var/cache/nginx (rsync –partial)。
8. 性能实测
| 测试场景 | TPS (old) | TPS (new) | 提升 |
|---|---|---|---|
| 首页并发 8 k | 9 k/s | 27 k/s | 3× |
| 商品详情 60 % 缓存命中 | 11 k/s | 31 k/s | 2.8× |
| 高并发下单 | 2.1 k/s | 2.0 k/s | —(不缓存) |
| CPU 平均 | 85 % | 42 % | ↓ 43 % |
9. 避坑清单
- 忘记 proxy_ignore_headers Set-Cookie,导致登录状态页面永远 MISS。
- proxy_cache_lock 放太久(> 10 s)= 活跃请求被“卡死”。
- 多机房 L3/L4 DDoS 清洗后 IP 变更,记得同步 geoip 白名单;否则丢流。
- tmpfs 当缓存盘——除非你有 >512 GB 内存,否则请远离!
- CDN + Edge Rule 覆盖 Nginx 头部,调试时先 curl -H “Cache-Control: no-cache” 直连验证。
我通过 分层缓存架构 + 业务字段建键 + 可灰度隔离 + Redis 标签失效,把香港节点的 Nginx 缓存从“统计指标”升级成了“业务护城河”。这套方案最终让我们在双 11 0:00 洪峰时依然保持 TTFB ≤ 100 ms、漏单率 < 0.05 %,并为后续秒杀、直播带货等更极端场景打下了基础。
如果你也正为电商高并发焦头烂额,不妨从规划缓存粒度和设计失效机制两件事做起;相信我,等到下一个大促,你会感谢今天写下的每一行 Nginx 配置。











