5.1 代理缓存:让后端喘口气


5.1 代理缓存:让后端喘口气

本节摘要:proxy_cache 让 Nginx 把后端响应存在本地磁盘,相同请求直接命中返回,后端计算量随命中率同比下降。本节覆盖缓存键设计、失效控制、登录态绕过与击穿应对,是一套可直接落地的完整方案。

打开缓存的三要素

http { # 1. 缓存空间:磁盘目录 + 内存键池 proxy_cache_path /data/cache/nginx levels=1:2 keys_zone=pagecache:200m max_size=20g inactive=6h; # 2. 缓存键:什么算"同一个请求" proxy_cache_key "$scheme$request_method$host$request_uri"; upstream app_backend { server 10.0.1.11:8080; } server { location /api/items/ { # 3. 三个开关 proxy_cache pagecache; proxy_cache_valid 200 10m; # 200 响应缓存十分钟 proxy_cache_valid 404 1m; # 404 短缓存防穿透 proxy_cache_use_stale error timeout updating; # 出错时给旧数据 add_header X-Cache-Status $upstream_cache_status; # 命中标记 proxy_pass http://app_backend; } } }

X-Cache-Status 头是调试眼睛:HIT 命中、MISS 回源、EXPIRED 过期回源、BYPASS 主动绕过。上线头几天盯着它看,命中率心里就有数了。

必须处理的两类请求

带登录态的内容不能缓存给所有人。用户 A 的"已收藏"状态出现在缓存里,用户 B 也会看到。按头绕过:

map $http_cookie $skip_cache { default 0; "~*session" 1; # 带 session cookie 的请求不缓存 } server { location /api/ { proxy_cache_bypass $skip_cache; # 这类请求直接回源 proxy_no_cache $skip_cache; # 且响应也不写缓存 # ... } }

价格、库存这类强实时数据要短 TTL 或干脆不进 Nginx 缓存。缓存方案里"数据能旧多久"是业务决策,不是运维决策,上线前拿到业务方的书面答案。

缓存击穿与雪崩

热点 key 过期瞬间,成千上万请求同时回源,后端被同一份数据的计算压垮——这是击穿。解法一行:

proxy_cache_lock on; # 同 key 回源只放一个请求去取 proxy_cache_lock_timeout 5s; # 其余等待或拿旧数据

配合前面已开的 proxy_cache_use_stale updating,等待期的请求可以直接拿旧版本返回,用户几乎无感。雪崩(大量 key 同时过期)的预防是把 TTL 加随机抖动,在后端响应里做更自然,Nginx 侧保持简单。

图:一次请求的缓存判定路径

图:一次请求的缓存判定路径

主动清缓存

配置改错导致脏数据进了缓存,等 TTL 自然过期太慢。两条路:商业版有 PURGE 方法;开源版用"删键"思路——把版本号编进缓存键:

# 用一个 map 或包含版本变量的键,发版时改版本号即整体失效 proxy_cache_key "$scheme$host$request_uri$v_cache_ver";

发版流程里把 v_cache_ver 加一,全量旧缓存瞬间作废,不需要任何清理脚本。

上线后的命中率验收

缓存上线不是终点,验收标准是命中率曲线与后端减负幅度。观察两天的 X-Cache-Status 分布:

# 从访问日志统计缓存命中构成(需 6.2 的日志格式带 cache 字段) awk '{print $NF}' access.log | grep -o "cache=[A-Z]*" | sort | uniq -c # 期望形态: # HIT 62% MISS 30% EXPIRED 6% BYPASS 2%

健康形态有两个特征:HIT 占大头且随时间缓慢爬升(新内容逐步填满);EXPIRED 与 MISS 之比接近 TTL 与内容更新频率的关系,比例异常说明 TTL 设得过短。命中率的另一面要用后端视角核对——应用服务器的 QPS 应同比下降六成左右,两个数字相互印证,缓存才算真正生效。若 Nginx 报 HIT 但后端 QPS 没降,通常是同一内容走了多个缓存键(键里混入了随机参数或大小写不一的路径),回到 proxy_cache_key 的设计上找原因。

一场缓存脏数据事故

商品改价后页面半小时不更新,客服被打爆。排查发现该 URI 的缓存键里没有版本变量,TTL 十分钟内两次回源都拿到了后端的旧副本(后端自身还有一层本地缓存)。多层缓存叠加时,总数据年龄是各层 TTL 之和,改价的可见延迟 = Nginx 层剩余 TTL + 后端层剩余 TTL。修复分两刀:紧急时用键版本号整体失效一次;结构上把"价格可见延迟上限"作为业务指标倒推各层 TTL 预算——上限五分钟的延迟,两层缓存只能各分两分钟。这次事故的沉淀是:缓存层次图必须连同每层的 TTL 一起画,否则没人能回答"改一个数据最慢多久可见"。

缓存的监控也应该有专属面板:命中率按 location 分组展示(而不是一个全局数字),后端 QPS 与命中率叠在同一张图上对照,键空间使用率与磁盘占用另列。这套面板的价值在缓存"悄悄坏掉"时显现——比如后端改了响应头带上了 Set-Cookie,Nginx 从此拒绝写缓存,命中率一周内滑向零而没有告警。给命中率设一条环比告警(较上周同期下跌超过十个百分点即通知),这类静默失效当天就能被发现。

本节要点回顾

  • 三要素:cache_path 定义空间、cache_key 定义身份、cache_valid 定义寿命;
  • 登录态绕过用 cookie 映射 + bypass 与 no_cache 双开关;
  • cache_lock 加 stale 兜底是对击穿的标准答案;
  • 版本号进缓存键是开源版的 PURGE 替代;
  • X-Cache-Status 头贯穿整个调试周期。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U