rea1m

缓存失效的几种姿势

改了配置不生效,八成是某一层缓存还没过期。问题在于"缓存"这个词涵盖了至少四个互不相关的层,而它们的失效方式完全不同。

四层缓存

排查顺序应该反过来:从上往下,先排除最外层。

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 上。