引言
在当今互联网高速发展的时代,网站性能和用户体验已成为决定产品成败的关键因素。HTTP缓存作为提升网站性能的核心技术之一,能够显著减少网络请求、降低服务器负载、加快页面加载速度。本文将深入解析HTTP缓存的原理、策略、配置方法以及实战优化技巧,帮助开发者全面掌握这一重要技术。
一、HTTP缓存基础概念
1.1 什么是HTTP缓存
HTTP缓存是一种客户端(浏览器)和服务器之间的机制,允许浏览器存储之前请求过的资源副本,以便在后续请求中直接使用,而无需重新从服务器获取。这大大减少了网络传输量和服务器压力。
1.2 缓存的分类
HTTP缓存主要分为两类:
- 浏览器缓存:存储在用户设备上的缓存,如磁盘缓存、内存缓存
- 代理缓存:存储在中间代理服务器(如CDN、反向代理)上的缓存
1.3 缓存的工作流程
用户请求 → 浏览器检查缓存 → 缓存有效 → 直接使用缓存
↓
缓存无效/过期 → 向服务器请求 → 服务器响应 → 更新缓存
二、HTTP缓存机制详解
2.1 缓存控制头(Cache-Control)
Cache-Control是HTTP/1.1中最重要的缓存控制头,它定义了缓存的行为策略。
2.1.1 常用指令
Cache-Control: public, max-age=3600, must-revalidate
- public:响应可以被任何缓存存储(包括浏览器和代理服务器)
- private:响应只能被浏览器缓存,不能被代理服务器缓存
- max-age=seconds:指定资源在缓存中的最大有效时间(秒)
- no-cache:缓存前必须向服务器验证资源是否过期
- no-store:禁止缓存,每次请求都必须从服务器获取
- must-revalidate:缓存过期后必须向服务器验证
- immutable:资源在有效期内不会改变,无需验证
2.1.2 实战示例
# 静态资源(如图片、CSS、JS)缓存1年
Cache-Control: public, max-age=31536000, immutable
# 动态内容缓存10分钟
Cache-Control: public, max-age=600, must-revalidate
# 禁止缓存(如登录页面)
Cache-Control: no-store, no-cache, must-revalidate
2.2 过期验证机制
2.2.1 Expires头(HTTP/1.0)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
- 指定资源过期的具体时间
- 依赖客户端时钟,可能存在时钟偏差问题
- 现代应用中通常与Cache-Control配合使用
2.2.2 ETag(实体标签)
ETag是服务器为资源分配的唯一标识符,用于验证资源是否改变。
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
工作流程:
- 首次请求:服务器返回资源和ETag
- 后续请求:浏览器发送If-None-Match头
- 服务器比较ETag:相同返回304,不同返回200和新资源
2.2.3 Last-Modified/If-Modified-Since
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
- 服务器记录资源最后修改时间
- 浏览器下次请求时发送If-Modified-Since
- 服务器比较时间戳决定是否返回304
2.3 缓存验证流程
graph TD
A[浏览器请求资源] --> B{检查缓存}
B -->|有缓存| C{缓存是否过期?}
B -->|无缓存| D[向服务器请求]
C -->|未过期| E[直接使用缓存]
C -->|已过期| F[发送验证请求]
F --> G{服务器验证}
G -->|未修改| H[返回304 Not Modified]
G -->|已修改| I[返回200和新资源]
H --> J[更新缓存时间]
I --> K[更新缓存]
三、缓存策略配置实战
3.1 Web服务器配置
3.1.1 Nginx配置示例
# 静态资源缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
# 开启Gzip压缩
gzip_static on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript image/svg+xml;
}
# HTML文件缓存策略(通常设置较短时间)
location ~* \.html$ {
expires 10m;
add_header Cache-Control "public, must-revalidate";
}
# API接口缓存策略
location /api/ {
expires 5m;
add_header Cache-Control "public, must-revalidate";
# 对于POST请求,禁止缓存
if ($request_method = POST) {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}
# 动态内容(如用户个人页面)
location /user/ {
expires -1;
add_header Cache-Control "private, no-cache, must-revalidate";
}
3.1.2 Apache配置示例
# 启用mod_expires模块
<IfModule mod_expires.c>
ExpiresActive On
# 图片文件缓存1年
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
# CSS和JS文件缓存1年
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
# HTML文件缓存10分钟
ExpiresByType text/html "access plus 10 minutes"
# 字体文件缓存1年
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
# 缓存控制头配置
<IfModule mod_headers.c>
# 为静态资源添加Cache-Control头
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# 为HTML文件添加Cache-Control头
<FilesMatch "\.html$">
Header set Cache-Control "public, max-age=600, must-revalidate"
</FilesMatch>
</IfModule>
3.2 应用层配置
3.2.1 Node.js/Express配置
const express = require('express');
const app = express();
// 静态资源缓存中间件
function staticCacheMiddleware(req, res, next) {
const ext = req.path.split('.').pop();
const cacheableExtensions = ['jpg', 'jpeg', 'png', 'gif', 'ico', 'css', 'js', 'svg', 'woff', 'woff2', 'ttf', 'eot'];
if (cacheableExtensions.includes(ext)) {
// 设置缓存时间为1年
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString());
} else if (ext === 'html') {
// HTML文件缓存10分钟
res.setHeader('Cache-Control', 'public, max-age=600, must-revalidate');
} else {
// 其他资源不缓存
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
}
next();
}
// 应用中间件
app.use(staticCacheMiddleware);
// 静态文件服务
app.use(express.static('public', {
maxAge: '1y', // 浏览器缓存1年
etag: true, // 启用ETag
lastModified: true // 启用Last-Modified
}));
// API接口缓存示例
app.get('/api/data', (req, res) => {
// 检查请求头中的缓存验证信息
const ifNoneMatch = req.headers['if-none-match'];
const ifModifiedSince = req.headers['if-modified-since'];
// 生成ETag(实际应用中应根据内容生成)
const data = { message: 'Hello World', timestamp: Date.now() };
const etag = require('crypto').createHash('md5').update(JSON.stringify(data)).digest('hex');
// 验证缓存
if (ifNoneMatch && ifNoneMatch === `"${etag}"`) {
return res.status(304).end(); // 未修改,使用缓存
}
// 设置缓存头
res.set({
'ETag': `"${etag}"`,
'Cache-Control': 'public, max-age=300, must-revalidate',
'Last-Modified': new Date().toUTCString()
});
res.json(data);
});
app.listen(3000);
3.2.2 Python/Django配置
# Django缓存配置示例
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
from django.http import HttpResponse
import time
# 视图缓存装饰器
@cache_page(60 * 15) # 缓存15分钟
@vary_on_cookie # 根据Cookie变化缓存
def product_list(request):
"""商品列表页面"""
# 模拟数据库查询
products = Product.objects.all()
return render(request, 'products.html', {'products': products})
# API接口缓存
from django.views.decorators.http import condition
def get_etag(request):
"""生成ETag"""
# 根据内容生成ETag
data = get_api_data()
etag = hashlib.md5(json.dumps(data).encode()).hexdigest()
return f'"{etag}"'
def get_last_modified(request):
"""获取最后修改时间"""
return datetime.now()
@condition(etag_func=get_etag, last_modified_func=get_last_modified)
def api_view(request):
"""API接口视图"""
data = get_api_data()
return JsonResponse(data)
# 自定义缓存中间件
class CustomCacheMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# 根据URL路径设置不同的缓存策略
if request.path.startswith('/static/'):
# 静态资源缓存1年
response['Cache-Control'] = 'public, max-age=31536000, immutable'
elif request.path.startswith('/api/'):
# API接口缓存5分钟
response['Cache-Control'] = 'public, max-age=300, must-revalidate'
else:
# 其他页面不缓存
response['Cache-Control'] = 'no-store, no-cache, must-revalidate'
return response
四、缓存策略优化技巧
4.1 缓存分层策略
4.1.1 资源分类缓存
// 资源分类缓存配置示例
const cacheConfig = {
// 永久不变的资源(如版本号、库文件)
permanent: {
extensions: ['woff2', 'ttf', 'eot', 'svg'],
maxAge: 31536000 * 10, // 10年
cacheControl: 'public, max-age=315360000, immutable'
},
// 版本化资源(带哈希值的文件名)
versioned: {
patterns: ['*.bundle.js', '*.chunk.js', '*.min.css'],
maxAge: 31536000, // 1年
cacheControl: 'public, max-age=31536000, immutable'
},
// 静态资源(可能更新)
static: {
extensions: ['jpg', 'jpeg', 'png', 'gif', 'ico'],
maxAge: 86400 * 30, // 30天
cacheControl: 'public, max-age=2592000, must-revalidate'
},
// 动态内容
dynamic: {
paths: ['/api/', '/user/', '/dashboard/'],
maxAge: 300, // 5分钟
cacheControl: 'public, max-age=300, must-revalidate'
},
// 敏感内容
sensitive: {
paths: ['/login', '/checkout', '/admin'],
cacheControl: 'no-store, no-cache, must-revalidate'
}
};
4.1.2 缓存键设计
// 缓存键设计示例
function generateCacheKey(request) {
const url = request.url;
const method = request.method;
const headers = request.headers;
// 基础键:URL + 方法
let key = `${method}:${url}`;
// 添加Accept-Encoding头(处理压缩)
if (headers['accept-encoding']) {
key += `:${headers['accept-encoding']}`;
}
// 添加Cookie(对于个性化内容)
if (headers.cookie) {
// 注意:只添加必要的Cookie,避免缓存爆炸
const necessaryCookies = ['session_id', 'user_id'];
const cookieStr = necessaryCookies
.map(name => {
const match = headers.cookie.match(new RegExp(`${name}=([^;]+)`));
return match ? `${name}=${match[1]}` : '';
})
.filter(Boolean)
.join(';');
if (cookieStr) {
key += `:${cookieStr}`;
}
}
// 添加查询参数(对于GET请求)
if (method === 'GET' && url.includes('?')) {
const params = new URLSearchParams(url.split('?')[1]);
// 只添加影响内容的参数
const relevantParams = ['page', 'sort', 'filter'];
const filteredParams = relevantParams
.filter(p => params.has(p))
.map(p => `${p}=${params.get(p)}`)
.join('&');
if (filteredParams) {
key += `:${filteredParams}`;
}
}
return key;
}
4.2 缓存失效策略
4.2.1 主动失效
// 缓存主动失效示例
class CacheManager {
constructor() {
this.cache = new Map();
this.ttl = new Map(); // 存储过期时间
}
// 设置缓存
set(key, value, ttl = 300) {
this.cache.set(key, value);
this.ttl.set(key, Date.now() + ttl * 1000);
// 设置自动清理
setTimeout(() => {
if (this.ttl.get(key) <= Date.now()) {
this.cache.delete(key);
this.ttl.delete(key);
}
}, ttl * 1000);
}
// 主动失效特定模式
invalidatePattern(pattern) {
const regex = new RegExp(pattern);
for (const key of this.cache.keys()) {
if (regex.test(key)) {
this.cache.delete(key);
this.ttl.delete(key);
}
}
}
// 失效用户相关缓存
invalidateUserCache(userId) {
this.invalidatePattern(`.*user:${userId}.*`);
}
// 失效产品相关缓存
invalidateProductCache(productId) {
this.invalidatePattern(`.*product:${productId}.*`);
}
}
// 使用示例
const cacheManager = new CacheManager();
// 设置缓存
cacheManager.set('user:123:profile', { name: 'John', age: 30 }, 3600);
// 用户更新资料后,失效相关缓存
function updateUserProfile(userId, newData) {
// 更新数据库
updateUserInDB(userId, newData);
// 失效缓存
cacheManager.invalidateUserCache(userId);
// 也可以设置新的缓存
cacheManager.set(`user:${userId}:profile`, newData, 3600);
}
4.2.2 版本化缓存
// 版本化缓存策略
class VersionedCache {
constructor() {
this.versions = new Map(); // 存储不同版本的缓存
this.currentVersion = 'v1';
}
// 设置缓存(带版本)
set(key, value, version = this.currentVersion) {
if (!this.versions.has(version)) {
this.versions.set(version, new Map());
}
this.versions.get(version).set(key, value);
}
// 获取缓存(支持版本回退)
get(key, version = this.currentVersion) {
// 先尝试当前版本
if (this.versions.has(version) && this.versions.get(version).has(key)) {
return this.versions.get(version).get(key);
}
// 尝试旧版本(如果需要)
const versions = Array.from(this.versions.keys()).sort().reverse();
for (const v of versions) {
if (v <= version && this.versions.get(v).has(key)) {
return this.versions.get(v).get(key);
}
}
return null;
}
// 更新版本
updateVersion(newVersion) {
// 保留旧版本一段时间,用于回滚
const oldVersions = Array.from(this.versions.keys())
.filter(v => v !== newVersion);
// 清理过旧的版本(保留最近3个版本)
if (oldVersions.length > 3) {
const toRemove = oldVersions.slice(0, oldVersions.length - 3);
toRemove.forEach(v => this.versions.delete(v));
}
this.currentVersion = newVersion;
}
}
// 使用示例
const versionedCache = new VersionedCache();
// 部署新版本时
function deployNewVersion() {
// 更新版本号
versionedCache.updateVersion('v2');
// 新版本的缓存数据
versionedCache.set('config:api', { endpoint: 'https://api.new.com' }, 'v2');
// 用户请求时,根据版本获取缓存
const config = versionedCache.get('config:api', 'v2');
}
4.3 缓存预热
4.3.1 预热策略
// 缓存预热示例
class CacheWarmer {
constructor() {
this.warming = false;
this.warmQueue = [];
}
// 添加预热任务
addWarmTask(url, priority = 1) {
this.warmQueue.push({ url, priority, timestamp: Date.now() });
this.warmQueue.sort((a, b) => b.priority - a.priority); // 按优先级排序
}
// 开始预热
async startWarming() {
if (this.warming) return;
this.warming = true;
while (this.warmQueue.length > 0) {
const task = this.warmQueue.shift();
try {
// 模拟请求资源
await this.fetchAndCache(task.url);
console.log(`预热完成: ${task.url}`);
} catch (error) {
console.error(`预热失败: ${task.url}`, error);
}
// 避免请求过快
await this.sleep(100);
}
this.warming = false;
}
// 获取并缓存资源
async fetchAndCache(url) {
// 实际应用中,这里会发起请求并缓存响应
const response = await fetch(url);
const data = await response.text();
// 缓存到内存或Redis
// cache.set(url, data, 3600);
return data;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
const warmer = new CacheWarmer();
// 预热热门页面
warmer.addWarmTask('/api/products', 10);
warmer.addWarmTask('/api/categories', 8);
warmer.addWarmTask('/api/featured', 5);
// 启动预热
warmer.startWarming();
五、缓存监控与调试
5.1 缓存命中率监控
// 缓存命中率监控
class CacheMonitor {
constructor() {
this.stats = {
hits: 0,
misses: 0,
total: 0,
byType: new Map(),
byPath: new Map()
};
}
// 记录缓存命中
recordHit(type, path) {
this.stats.hits++;
this.stats.total++;
// 按类型统计
if (!this.stats.byType.has(type)) {
this.stats.byType.set(type, { hits: 0, misses: 0 });
}
this.stats.byType.get(type).hits++;
// 按路径统计
if (!this.stats.byPath.has(path)) {
this.stats.byPath.set(path, { hits: 0, misses: 0 });
}
this.stats.byPath.get(path).hits++;
}
// 记录缓存未命中
recordMiss(type, path) {
this.stats.misses++;
this.stats.total++;
// 按类型统计
if (!this.stats.byType.has(type)) {
this.stats.byType.set(type, { hits: 0, misses: 0 });
}
this.stats.byType.get(type).misses++;
// 按路径统计
if (!this.stats.byPath.has(path)) {
this.stats.byPath.set(path, { hits: 0, misses: 0 });
}
this.stats.byPath.get(path).misses++;
}
// 获取命中率
getHitRate() {
if (this.stats.total === 0) return 0;
return (this.stats.hits / this.stats.total) * 100;
}
// 获取详细统计
getStats() {
const hitRate = this.getHitRate();
// 按类型计算命中率
const typeStats = {};
for (const [type, stats] of this.stats.byType) {
const total = stats.hits + stats.misses;
typeStats[type] = {
hitRate: total > 0 ? (stats.hits / total) * 100 : 0,
hits: stats.hits,
misses: stats.misses,
total
};
}
// 按路径计算命中率(只显示前10个)
const pathStats = {};
const paths = Array.from(this.stats.byPath.entries())
.sort((a, b) => (b[1].hits + b[1].misses) - (a[1].hits + a[1].misses))
.slice(0, 10);
for (const [path, stats] of paths) {
const total = stats.hits + stats.misses;
pathStats[path] = {
hitRate: total > 0 ? (stats.hits / total) * 100 : 0,
hits: stats.hits,
misses: stats.misses,
total
};
}
return {
overall: {
hitRate,
hits: this.stats.hits,
misses: this.stats.misses,
total: this.stats.total
},
byType: typeStats,
byPath: pathStats
};
}
// 重置统计
reset() {
this.stats = {
hits: 0,
misses: 0,
total: 0,
byType: new Map(),
byPath: new Map()
};
}
}
// 使用示例
const monitor = new CacheMonitor();
// 模拟缓存操作
function simulateCacheOperation(url, isHit) {
const type = url.split('.').pop() || 'html';
if (isHit) {
monitor.recordHit(type, url);
} else {
monitor.recordMiss(type, url);
}
}
// 模拟一些请求
simulateCacheOperation('/api/products', true);
simulateCacheOperation('/api/products', true);
simulateCacheOperation('/api/users', false);
simulateCacheOperation('/image.jpg', true);
simulateCacheOperation('/style.css', true);
simulateCacheOperation('/api/products', false);
// 获取统计
const stats = monitor.getStats();
console.log('缓存命中率:', stats.overall.hitRate.toFixed(2) + '%');
console.log('详细统计:', JSON.stringify(stats, null, 2));
5.2 缓存调试工具
5.2.1 浏览器开发者工具
// 使用Chrome DevTools调试缓存
// 1. 打开Network面板
// 2. 勾选"Disable cache"选项(禁用缓存)
// 3. 刷新页面观察请求
// 4. 查看响应头中的Cache-Control、ETag等信息
// 5. 查看缓存存储
// 在DevTools中:Application → Storage → Cache Storage
// 可以查看所有缓存的资源和详细信息
// 6. 使用Service Worker调试缓存
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('Service Worker registered');
// 监听Service Worker状态
registration.addEventListener('updatefound', () => {
console.log('New Service Worker found');
});
});
// 监听消息
navigator.serviceWorker.addEventListener('message', event => {
console.log('From SW:', event.data);
});
}
5.2.2 缓存验证脚本
// 缓存验证脚本示例
class CacheValidator {
constructor() {
this.results = [];
}
// 验证单个URL的缓存头
async validateUrl(url, expectedHeaders) {
try {
const response = await fetch(url, { method: 'HEAD' });
const headers = {};
// 收集所有相关头
for (const [key, value] of response.headers) {
if (key.toLowerCase().includes('cache') ||
key.toLowerCase().includes('etag') ||
key.toLowerCase().includes('last-modified')) {
headers[key] = value;
}
}
// 验证预期头
const results = [];
for (const [header, expectedValue] of Object.entries(expectedHeaders)) {
const actualValue = headers[header.toLowerCase()];
const passed = actualValue === expectedValue;
results.push({
header,
expected: expectedValue,
actual: actualValue,
passed
});
}
return {
url,
headers,
results,
passed: results.every(r => r.passed)
};
} catch (error) {
return {
url,
error: error.message,
passed: false
};
}
}
// 批量验证
async validateBatch(urls, expectedHeaders) {
const promises = urls.map(url => this.validateUrl(url, expectedHeaders));
const results = await Promise.all(promises);
this.results = results;
return results;
}
// 生成报告
generateReport() {
const total = this.results.length;
const passed = this.results.filter(r => r.passed).length;
const failed = total - passed;
const report = {
summary: {
total,
passed,
failed,
successRate: (passed / total * 100).toFixed(2) + '%'
},
details: this.results
};
return report;
}
}
// 使用示例
const validator = new CacheValidator();
// 定义预期的缓存头
const expectedHeaders = {
'Cache-Control': 'public, max-age=31536000, immutable',
'ETag': null // 可选
};
// 验证URL列表
const urlsToValidate = [
'/static/js/app.js',
'/static/css/style.css',
'/static/images/logo.png',
'/api/data'
];
validator.validateBatch(urlsToValidate, expectedHeaders).then(() => {
const report = validator.generateReport();
console.log('缓存验证报告:', JSON.stringify(report, null, 2));
});
六、高级缓存策略
6.1 Service Worker缓存
6.1.1 Service Worker基础
// sw.js - Service Worker文件
const CACHE_NAME = 'my-app-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/static/css/main.css',
'/static/js/app.js',
'/static/images/logo.png'
];
// 安装事件 - 预缓存静态资源
self.addEventListener('install', event => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => {
// 跳过等待,立即激活
return self.skipWaiting();
})
);
});
// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => {
// 控制所有页面
return self.clients.claim();
})
);
});
// 拦截请求事件
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// 策略1: 静态资源 - 缓存优先
if (STATIC_ASSETS.includes(url.pathname)) {
event.respondWith(
caches.match(request)
.then(cachedResponse => {
if (cachedResponse) {
// 缓存命中,返回缓存
return cachedResponse;
}
// 缓存未命中,网络请求并缓存
return fetch(request)
.then(response => {
// 克隆响应,因为响应只能使用一次
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(request, responseToCache);
});
return response;
});
})
);
}
// 策略2: API接口 - 网络优先,失败时使用缓存
else if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(request)
.then(response => {
// 克隆响应
const responseToCache = response.clone();
// 缓存成功的API响应
if (response.ok) {
caches.open(CACHE_NAME)
.then(cache => {
cache.put(request, responseToCache);
});
}
return response;
})
.catch(() => {
// 网络失败,尝试缓存
return caches.match(request);
})
);
}
// 策略3: 其他请求 - 网络优先
else {
event.respondWith(
fetch(request)
.catch(() => {
// 网络失败,尝试缓存
return caches.match(request);
})
);
}
});
// 后台同步(可选)
self.addEventListener('sync', event => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData());
}
});
// 推送通知(可选)
self.addEventListener('push', event => {
const options = {
body: event.data ? event.data.text() : 'New notification',
icon: '/images/icon-192.png',
badge: '/images/badge-72.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{ action: 'explore', title: 'Go to the site' },
{ action: 'close', title: 'Close notification' }
]
};
event.waitUntil(
self.registration.showNotification('Push Notification', options)
);
});
// 通知点击事件
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/')
);
}
});
6.1.2 Workbox(Google的Service Worker工具库)
// 使用Workbox简化Service Worker开发
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');
// 配置Workbox
workbox.setConfig({
debug: false
});
// 设置缓存名称
workbox.core.setCacheNameDetails({
prefix: 'my-app',
suffix: 'v1',
precache: 'precache',
runtime: 'runtime'
});
// 预缓存静态资源
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
// 静态资源缓存策略(缓存优先)
workbox.routing.registerRoute(
({ request }) => request.destination === 'style' ||
request.destination === 'script' ||
request.destination === 'image' ||
request.destination === 'font',
new workbox.strategies.CacheFirst({
cacheName: 'static-resources',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30天
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// API接口缓存策略(网络优先)
workbox.routing.registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new workbox.strategies.NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 3, // 3秒超时
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60 // 5分钟
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// HTML页面缓存策略(网络回退到缓存)
workbox.routing.registerRoute(
({ request }) => request.mode === 'navigate',
new workbox.strategies.NetworkFirst({
cacheName: 'pages-cache',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 10,
maxAgeSeconds: 24 * 60 * 60 // 24小时
})
]
})
);
// 离线页面处理
workbox.routing.registerRoute(
({ request }) => request.mode === 'navigate',
async ({ event }) => {
try {
// 尝试网络请求
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// 网络失败,返回离线页面
const cache = await caches.open('pages-cache');
const cachedResponse = await cache.match('/offline.html');
return cachedResponse || Response.error();
}
}
);
// 后台同步
workbox.routing.registerRoute(
({ request }) => request.url.includes('/api/sync'),
new workbox.strategies.NetworkOnly({
plugins: [
new workbox.backgroundSync.BackgroundSyncPlugin('sync-queue', {
maxRetentionTime: 24 * 60 // 24小时
})
]
})
);
6.2 CDN缓存策略
6.2.1 CDN配置示例
# CDN边缘节点配置示例
server {
listen 80;
server_name cdn.example.com;
# 静态资源缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
# 缓存时间
expires 1y;
add_header Cache-Control "public, immutable";
# 开启Gzip压缩
gzip_static on;
gzip_vary on;
gzip_min_length 1024;
# 缓存键优化(去除查询参数)
set $cache_key $uri;
# 缓存验证
proxy_cache_valid 200 304 1y;
proxy_cache_key $uri;
# 缓存锁定(防止缓存击穿)
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
# 缓存条件(只缓存成功响应)
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# 缓存范围(支持Range请求)
proxy_cache_methods GET HEAD;
# 缓存头传递
proxy_ignore_headers Cache-Control;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
# 源站配置
proxy_pass http://origin-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# API接口缓存(边缘缓存)
location /api/ {
# 缓存时间(较短)
expires 5m;
add_header Cache-Control "public, max-age=300, must-revalidate";
# 缓存验证
proxy_cache_valid 200 304 5m;
proxy_cache_key "$uri$is_args$args";
# 缓存条件
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# 源站配置
proxy_pass http://api-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 动态内容(不缓存)
location /user/ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
proxy_pass http://app-server;
proxy_set_header Host $host;
}
}
6.2.2 CDN缓存清除
// CDN缓存清除脚本示例
class CDNCachePurger {
constructor(config) {
this.config = config;
this.purgeQueue = [];
}
// 添加清除任务
addPurgeTask(pattern, priority = 1) {
this.purgeQueue.push({
pattern,
priority,
timestamp: Date.now(),
attempts: 0
});
// 按优先级排序
this.purgeQueue.sort((a, b) => b.priority - a.priority);
}
// 执行清除
async purge() {
const results = [];
while (this.purgeQueue.length > 0) {
const task = this.purgeQueue.shift();
try {
const result = await this.executePurge(task.pattern);
results.push({
pattern: task.pattern,
success: true,
result
});
console.log(`成功清除缓存: ${task.pattern}`);
} catch (error) {
task.attempts++;
if (task.attempts < 3) {
// 重试
this.purgeQueue.push(task);
console.warn(`清除失败,重试: ${task.pattern}`, error.message);
} else {
results.push({
pattern: task.pattern,
success: false,
error: error.message
});
console.error(`清除失败: ${task.pattern}`, error);
}
}
// 避免请求过快
await this.sleep(100);
}
return results;
}
// 执行单个清除(根据CDN提供商)
async executePurge(pattern) {
// 这里根据实际CDN提供商的API实现
// 例如:Cloudflare、Akamai、AWS CloudFront等
// 示例:Cloudflare API
if (this.config.provider === 'cloudflare') {
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${this.config.zoneId}/purge_cache`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
files: [pattern]
})
}
);
if (!response.ok) {
throw new Error(`Cloudflare API error: ${response.status}`);
}
return await response.json();
}
// 示例:AWS CloudFront
if (this.config.provider === 'cloudfront') {
const AWS = require('aws-sdk');
const cloudfront = new AWS.CloudFront({
accessKeyId: this.config.accessKeyId,
secretAccessKey: this.config.secretAccessKey,
region: this.config.region
});
const params = {
DistributionId: this.config.distributionId,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: 1,
Items: [pattern]
}
}
};
return await cloudfront.createInvalidation(params).promise();
}
throw new Error(`Unsupported CDN provider: ${this.config.provider}`);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
const purger = new CDNCachePurger({
provider: 'cloudflare',
zoneId: 'your-zone-id',
apiToken: 'your-api-token'
});
// 添加清除任务
purger.addPurgeTask('/static/js/app.js', 10);
purger.addPurgeTask('/static/css/*.css', 5);
purger.addPurgeTask('/api/products', 1);
// 执行清除
purger.purge().then(results => {
console.log('清除结果:', JSON.stringify(results, null, 2));
});
七、缓存最佳实践
7.1 缓存策略决策树
graph TD
A[资源类型] --> B{静态资源?}
B -->|是| C{带版本哈希?}
C -->|是| D[Cache-Control: public, max-age=31536000, immutable]
C -->|否| E[Cache-Control: public, max-age=86400, must-revalidate]
B -->|否| F{动态内容?}
F -->|是| G{个性化内容?}
G -->|是| H[Cache-Control: private, no-cache, must-revalidate]
G -->|否| I[Cache-Control: public, max-age=300, must-revalidate]
F -->|否| J{敏感数据?}
J -->|是| K[Cache-Control: no-store, no-cache, must-revalidate]
J -->|否| L[Cache-Control: public, max-age=60, must-revalidate]
7.2 常见问题与解决方案
7.2.1 缓存击穿
// 缓存击穿解决方案:缓存锁
class CacheWithLock {
constructor() {
this.cache = new Map();
this.locks = new Map();
}
async get(key, fetcher) {
// 检查缓存
if (this.cache.has(key)) {
const value = this.cache.get(key);
if (value.expiry > Date.now()) {
return value.data;
}
}
// 检查锁
if (this.locks.has(key)) {
// 等待锁释放
return new Promise(resolve => {
const checkLock = () => {
if (!this.locks.has(key)) {
resolve(this.get(key, fetcher));
} else {
setTimeout(checkLock, 100);
}
};
checkLock();
});
}
// 获取锁
this.locks.set(key, true);
try {
// 获取数据
const data = await fetcher();
// 设置缓存(5分钟)
this.cache.set(key, {
data,
expiry: Date.now() + 5 * 60 * 1000
});
return data;
} finally {
// 释放锁
this.locks.delete(key);
}
}
}
// 使用示例
const cache = new CacheWithLock();
async function getProduct(productId) {
return cache.get(`product:${productId}`, async () => {
// 模拟数据库查询
console.log(`Fetching product ${productId} from database...`);
await new Promise(resolve => setTimeout(resolve, 1000));
return { id: productId, name: 'Product Name' };
});
}
// 多个并发请求
async function testConcurrency() {
const promises = [];
for (let i = 0; i < 5; i++) {
promises.push(getProduct(123));
}
const results = await Promise.all(promises);
console.log('All results:', results);
}
7.2.2 缓存雪崩
// 缓存雪崩解决方案:随机过期时间
class CacheWithRandomExpiry {
constructor() {
this.cache = new Map();
}
set(key, value, baseTTL = 300) {
// 添加随机因子(±10%)
const randomFactor = 1 + (Math.random() * 0.2 - 0.1);
const ttl = Math.floor(baseTTL * randomFactor);
this.cache.set(key, {
value,
expiry: Date.now() + ttl * 1000
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (item.expiry <= Date.now()) {
this.cache.delete(key);
return null;
}
return item.value;
}
// 定期清理过期缓存
startCleanup(interval = 60000) {
setInterval(() => {
const now = Date.now();
for (const [key, item] of this.cache) {
if (item.expiry <= now) {
this.cache.delete(key);
}
}
}, interval);
}
}
// 使用示例
const cache = new CacheWithRandomExpiry();
cache.startCleanup();
// 批量设置缓存(避免同时过期)
function batchSetCache(items) {
items.forEach((item, index) => {
// 为每个项目添加不同的随机延迟
const delay = index * 100; // 100ms间隔
setTimeout(() => {
cache.set(item.key, item.value, 300);
console.log(`Cached ${item.key}`);
}, delay);
});
}
7.2.3 缓存污染
// 缓存污染解决方案:缓存键验证
class CacheWithValidation {
constructor() {
this.cache = new Map();
this.validators = new Map();
}
// 注册验证器
registerValidator(pattern, validator) {
this.validators.set(pattern, validator);
}
// 设置缓存(带验证)
async set(key, value, fetcher) {
// 检查是否有验证器
let isValid = true;
for (const [pattern, validator] of this.validators) {
if (key.includes(pattern)) {
isValid = await validator(value);
break;
}
}
if (!isValid) {
throw new Error('Cache validation failed');
}
this.cache.set(key, {
value,
timestamp: Date.now()
});
}
// 获取缓存(带验证)
async get(key, fetcher) {
const item = this.cache.get(key);
if (item) {
// 检查验证器
let isValid = true;
for (const [pattern, validator] of this.validators) {
if (key.includes(pattern)) {
isValid = await validator(item.value);
break;
}
}
if (isValid) {
return item.value;
}
}
// 缓存无效或不存在,重新获取
const value = await fetcher();
await this.set(key, value, fetcher);
return value;
}
}
// 使用示例
const cache = new CacheWithValidation();
// 注册验证器:确保API响应包含必要字段
cache.registerValidator('api:', async (data) => {
if (!data || typeof data !== 'object') return false;
if (!data.success && !data.error) return false;
return true;
});
// 使用
async function fetchData() {
return cache.get('api:products', async () => {
const response = await fetch('/api/products');
const data = await response.json();
return data;
});
}
八、总结
HTTP缓存是优化网站性能和用户体验的关键技术。通过合理配置缓存策略,可以显著减少网络请求、降低服务器负载、加快页面加载速度。本文从缓存基础概念、机制详解、配置实战、优化技巧、监控调试、高级策略等多个方面进行了全面解析。
关键要点回顾:
- 理解缓存机制:掌握Cache-Control、ETag、Last-Modified等核心概念
- 合理配置缓存:根据资源类型和业务需求设置合适的缓存策略
- 优化缓存策略:采用分层缓存、版本化、预热等高级技巧
- 监控缓存效果:通过命中率监控和调试工具持续优化
- 应对挑战:解决缓存击穿、雪崩、污染等常见问题
实践建议:
- 静态资源:使用版本化文件名 + 长时间缓存(1年)
- 动态内容:根据业务需求设置合理缓存时间(秒到分钟级)
- 个性化内容:使用private缓存或不缓存
- 敏感数据:使用no-store禁止缓存
- 定期审查:监控缓存命中率,调整策略
通过本文的详细解析和实战示例,相信您已经掌握了HTTP缓存的核心技术和优化方法。在实际项目中,请根据具体业务场景灵活应用这些策略,持续监控和优化,以达到最佳的性能和用户体验效果。
