缓存失效的几种姿势
改了配置不生效,八成是某一层缓存还没过期。问题在于"缓存"这个词涵盖了至少四个互不相关的层,而它们的失效方式完全不同。
四层缓存
- DNS 缓存 —— 改了解析记录,但本地/运营商还在用旧 IP,TTL 决定要等多久;
- TCP / QUIC 连接复用 —— 旧连接指向旧后端,长连接会一直用下去;
- TLS 会话缓存 —— 恢复会话可以跳过完整握手,但也可能让你以为配置改了;
- HTTP 缓存 ——
Cache-Control、ETag、CDN 边缘节点。
排查顺序应该反过来:从上往下,先排除最外层。
HTTP 缓存的常见误配
一个典型场景:静态资源用 max-age=31536000 强缓存,然后文件名不带 hash。结果推一次新版本,用户要清缓存才看得到。
# 文件名带 hash 时才适合长缓存
location ~* \.[0-9a-f]{8}\.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# HTML 永远不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
immutable 这一条值得加上:它告诉浏览器"这个 URL 的内容不会变,别在刷新时发条件请求"。省掉一轮 304。
TLS 会话缓存的副作用
ssl_session_cache 和 ssl_session_tickets 都会让客户端跳过完整握手。这本身是好事,但有一种情况会被误判:
改了证书之后用 curl 测,仍然拿到旧证书 —— 因为它恢复了一个旧会话。
测试时务必加上 -H "Connection: close" 或者直接 openssl s_client,后者每次都新建连接,不受会话缓存影响。
验证顺序
我现在固定用这一套,基本能一次定位:
# 1. 解析对不对
dig +short example.com
# 2. 连到的是不是那台机器
curl -sv https://example.com -o /dev/null 2>&1 | grep -i "connected to"
# 3. 证书对不对、是不是新的
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -dates
# 4. 响应头里的缓存策略
curl -sI https://example.com/static/app.css | grep -i "cache-control\|etag\|age"
第 4 步里的 age 头部很有用 —— 如果它存在且数值很大,说明请求根本没到源站,卡在 CDN 或反向代理上了。
反向代理的缓冲
最后一个容易忽略的点:nginx 的 proxy_buffering 默认是开的。对于普通网页这是优化,但对于流式响应(SSE、WebSocket 之外的 chunked 传输),它会把数据攒在缓冲区里,表现为"内容出得很慢,然后一次全出来"。
location /stream/ {
proxy_buffering off;
proxy_cache off;
}
WebSocket 走的是 Upgrade,不受这个影响,但如果反代配置里漏了 proxy_http_version 1.1 和 Connection: upgrade,握手会直接失败在 426 上。