7.4.6 资源版本化与缓存失效
资源版本化的目标是让浏览器长期缓存静态资源,同时在更新时能够立即失效。核心思路是“内容一变,URL就变”。常见方式包括文件名哈希、目录版本号、查询参数版本号,其中以“文件名哈希”最可靠,查询参数在部分代理或 CDN 下可能被忽略。
原理草图(资源版本化与缓存失效链路)
常用策略与取舍(含示例)
- 文件名哈希(推荐):app.3f2c1a.js、style.9b8e7c.css
适配最长缓存时间,命中率高,最可靠。
- 目录版本号:/static/v20240101/app.js
便于整体回滚或批量失效。
- 查询参数版本号:/static/app.js?v=20240101
简单但兼容性差,部分代理会忽略参数导致失效不彻底。
- HTML/接口不缓存,静态资源强缓存;当 HTML 更新引用新的静态资源时完成“主动失效”。
缓存策略建议
- 静态资源(带版本号):Cache-Control: public, max-age=31536000, immutable
- HTML 页面:Cache-Control: no-cache 或 max-age=0, must-revalidate
- 动态接口/JSON:Cache-Control: no-store 或配合 ETag/Last-Modified
目录结构与版本化示例
/var/www/html/
├── index.html
└── static/
├── v20240101/
│ ├── app.3f2c1a.js
│ └── style.9b8e7c.css
└── v20240201/
├── app.88ab10.js
└── style.a11e00.css
Nginx 示例配置(完整可执行)
# /etc/nginx/conf.d/static-cache.conf
server {
listen 80;
server_name example.com;
root /var/www/html;
# HTML 不缓存,确保引用最新资源
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}
# 带版本号的静态资源强缓存
location ~* ^/static/(v[0-9]{8})/.*\.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
access_log off;
}
# 不带版本号的静态资源,短缓存或不缓存
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ {
add_header Cache-Control "max-age=300";
try_files $uri =404;
}
}
构建与发布示例(含命令解释)
# 1) 生成版本号(示例:日期)
VERSION=$(date +%Y%m%d)
echo "VERSION=$VERSION"
# 2) 生成带哈希的文件名(示例用 md5 简化)
JS_HASH=$(md5sum app.js | awk '{print substr($1,1,6)}')
CSS_HASH=$(md5sum style.css | awk '{print substr($1,1,6)}')
echo "JS_HASH=$JS_HASH CSS_HASH=$CSS_HASH"
# 3) 拷贝并重命名到版本目录
mkdir -p /var/www/html/static/v$VERSION
cp app.js /var/www/html/static/v$VERSION/app.$JS_HASH.js
cp style.css /var/www/html/static/v$VERSION/style.$CSS_HASH.css
# 4) 更新 HTML 引用
cat > /var/www/html/index.html <<EOF
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/static/v$VERSION/style.$CSS_HASH.css">
</head>
<body>
<script src="/static/v$VERSION/app.$JS_HASH.js"></script>
</body>
</html>
EOF
md5sum:对文件内容计算哈希,确保内容变化导致 URL 变化。awk '{print substr($1,1,6)}':截取哈希前 6 位缩短文件名。mkdir -p:创建版本目录,便于批量失效或回滚。
验证与预期效果
# 检查配置
nginx -t
# 重新加载生效
nginx -s reload
# 验证静态资源缓存头
curl -I http://example.com/static/v20240101/app.3f2c1a.js
# 预期:Cache-Control: public, max-age=31536000, immutable
# 验证 HTML 不缓存
curl -I http://example.com/index.html
# 预期:Cache-Control: no-cache, must-revalidate
排错清单与定位命令
1. 浏览器仍使用旧文件
- 检查 HTML 是否更新引用新 URL:
grep -n "static/v" /var/www/html/index.html
2. 缓存头未生效
- 检查 Nginx 命中 location:
nginx -T | sed -n '/static-cache.conf/,$p'
3. 静态文件 404
- 检查文件是否存在:
ls -l /var/www/html/static/v20240101/
4. CDN 缓存未更新
- 触发 CDN 刷新或使用新 URL;避免仅靠查询参数。
练习
1. 生成 vYYYYMMDD 版本目录,发布新的 app.js,验证 Cache-Control 是否为一年。
2. 将 index.html 的缓存策略改为 no-store,观察浏览器是否每次都重新请求。
3. 故意删除版本目录中的 app.*.js,通过 curl -I 验证返回 404 并检查 Nginx error log。