引言:HTTP缓存的重要性
HTTP缓存是现代Web性能优化的核心技术之一,它通过在客户端(浏览器)或中间代理服务器上存储资源副本,从而减少网络请求、降低服务器负载并显著提升用户体验。根据Google的研究,合理的缓存策略可以将页面加载时间减少50%以上。
然而,缓存策略并非一劳永逸的解决方案。开发者经常面临两大挑战:
- 缓存失效:如何确保用户获取到最新的资源版本
- 缓存一致性:如何在分布式系统中保持多个缓存副本的一致性
本文将深入解析HTTP缓存的工作原理、各种缓存策略的实现方式,并提供解决缓存失效与一致性问题的完整方案。
HTTP缓存基础概念
缓存的分类
HTTP缓存主要分为两类:
私有缓存(Private Cache)
- 通常存在于浏览器中
- 只为单个用户存储资源副本
- 例如:浏览器缓存、本地存储
共享缓存(Shared Cache)
- 存在于网络中的代理服务器
- 可为多个用户存储资源副本
- 例如:CDN、反向代理缓存
缓存控制流程
HTTP缓存的基本工作流程如下:
客户端请求 -> 检查本地缓存 -> 缓存有效? -> 是 -> 返回缓存内容
↓ 否
向服务器请求 -> 服务器响应 -> 存储到缓存 -> 返回给客户端
HTTP缓存策略详解
1. 强缓存(Strong Caching)
强缓存是最快的缓存策略,当缓存有效时,浏览器不会向服务器发送任何请求,直接使用本地缓存。
Cache-Control头部
Cache-Control是HTTP/1.1中最重要的缓存控制头部,它使用指令来控制缓存行为:
Cache-Control: max-age=3600, must-revalidate, public
常用指令:
| 指令 | 说明 |
|---|---|
max-age=<seconds> |
资源保持新鲜的最大时间(秒) |
s-maxage=<seconds> |
仅适用于共享缓存,覆盖max-age |
no-store |
禁止任何缓存 |
no-cache |
必须先与服务器确认缓存有效性 |
must-revalidate |
缓存过期后必须重新验证 |
public |
响应可被任何缓存存储 |
private |
响应仅适用于私有缓存 |
Expires头部(HTTP/1.0)
Expires头部指定资源的过期时间(GMT格式):
Expires: Thu, 31 Dec 2023 23:59:59 GMT
注意:Cache-Control的max-age会覆盖Expires。
2. 协商缓存(协商缓存)
当强缓存过期或不存在时,浏览器会与服务器进行协商,确定是否可以使用缓存。
Last-Modified / If-Modified-Since
工作流程:
- 服务器首次响应时添加
Last-Modified头部 - 浏览器下次请求时添加
If-Modified-Since头部 - 服务器比较时间戳,如果未修改则返回304状态码
示例:
服务器响应:
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
Cache-Control: max-age=3600
浏览器下次请求:
GET /style.css HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
服务器响应(未修改):
HTTP/1.1 304 Not Modified
ETag / If-None-Match
ETag是资源的唯一标识符,比时间戳更可靠。
工作流程:
- 服务器首次响应时添加
ETag头部 - 浏览器下次请求时添加
If-None-Match头部 - 服务器比较ETag,如果匹配则返回304
示例:
服务器响应:
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
浏览器下次请求:
GET /style.css HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
服务器响应(未修改):
HTTP/1.1 304 Not Modified
实战:Web服务器缓存配置
Nginx缓存配置
以下是一个完整的Nginx缓存配置示例:
# 定义缓存路径和参数
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
server {
listen 80;
server_name example.com;
# 静态资源缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
# 使用缓存
proxy_cache my_cache;
# 缓存键(根据请求URL)
proxy_cache_key $scheme$proxy_host$request_uri;
# 缓存状态码200和302的响应,缓存1小时
proxy_cache_valid 200 302 1h;
# 缓存状态码404的响应,缓存1分钟
proxy_cache_valid 404 1m;
# 添加缓存状态头部(用于调试)
add_header X-Cache-Status $upstream_cache_status;
# 缓存控制头部
add_header Cache-Control "public, max-age=3600";
# 后端服务器
proxy_pass http://backend_server;
# 忽略后端服务器的Cache-Control头部
proxy_ignore_headers Cache-Control;
# 缓存过期后重新验证
proxy_cache_revalidate on;
# 缓存锁,防止缓存击穿
proxy_cache_lock on;
# 缓存锁定超时时间
proxy_cache_lock_timeout 5s;
}
# API接口缓存配置(较短时间)
location /api/ {
proxy_cache my_cache;
proxy_cache_valid 200 5m; # 缓存5分钟
proxy_cache_valid 500 502 503 504 1m;
# 添加缓存状态头部
add_header X-Cache-Status $upstream_cache_status;
# 私有缓存,不共享
add_header Cache-Control "private, max-age=300";
proxy_pass http://api_backend;
}
# 禁用缓存的管理接口
location /admin/ {
proxy_cache off;
proxy_pass http://admin_backend;
}
}
Apache HTTP Server缓存配置
# 启用mod_cache和mod_cache_disk
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
# 配置缓存存储
CacheRoot /var/cache/apache
CacheDirLevels 2
CacheDirLength 1
# 配置缓存行为
<IfModule mod_cache.c>
# 启用缓存
CacheEnable disk /
# 默认缓存时间(秒)
CacheDefaultExpire 3600
# 最大缓存时间
CacheMaxExpire 86400
# 不缓存的文件扩展名
CacheNoCache "*.php *.asp *.aspx *.jsp"
# 忽略查询字符串(可选)
CacheIgnoreQueryString On
# 添加缓存状态头部
Header set X-Cache-Status "%{CACHE_STATUS}e"
</IfModule>
# 静态资源特定配置
<Directory "/var/www/html/static">
# 强缓存1小时
Header set Cache-Control "public, max-age=3600"
# 启用ETag
FileETag All
</Directory>
# API接口配置
<Location "/api/">
# 短时间缓存
Header set Cache-Control "private, max-age=300"
# 必须重新验证
Header set Cache-Control "must-revalidate"
</Location>
Node.js/Express缓存实现
const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const app = express();
// 内存缓存实现
const memoryCache = new Map();
// 计算文件ETag的函数
function calculateETag(filePath) {
const fileBuffer = fs.readFileSync(filePath);
return crypto.createHash('md5').update(fileBuffer).digest('hex');
}
// 静态资源中间件(带缓存)
app.use('/static', (req, res, next) => {
const filePath = path.join(__dirname, 'public', req.path);
// 检查文件是否存在
if (!fs.existsSync(filePath)) {
return next();
}
// 获取文件信息
const stats = fs.statSync(filePath);
const lastModified = stats.mtime.toUTCString();
const etag = calculateETag(filePath);
// 检查客户端缓存
const ifNoneMatch = req.headers['if-none-match'];
const ifModifiedSince = req.headers['if-modified-since'];
// 协商缓存检查
if ((ifNoneMatch && ifNoneMatch === etag) ||
(ifModifiedSince && ifModifiedSince === lastModified)) {
res.status(304).end();
return;
}
// 设置强缓存头部
res.setHeader('Cache-Control', 'public, max-age=3600');
res.setHeader('Last-Modified', lastModified);
res.setHeader('ETag', etag);
// 发送文件
res.sendFile(filePath);
});
// API接口缓存中间件
function apiCacheMiddleware(duration = 300) {
return (req, res, next) => {
const key = `__api__${req.originalUrl || req.url}`;
// 检查缓存
const cached = memoryCache.get(key);
if (cached && (Date.now() - cached.timestamp) < duration * 1000) {
res.setHeader('X-Cache-Status', 'HIT');
res.setHeader('Cache-Control', `private, max-age=${duration}`);
return res.json(cached.data);
}
// 重写res.json方法
const originalJson = res.json.bind(res);
res.json = function(data) {
// 存储到缓存
memoryCache.set(key, {
data: data,
timestamp: Date.now()
});
// 设置响应头部
res.setHeader('X-Cache-Status', 'MISS');
res.setHeader('Cache-Control', `private, max-age=${duration}`);
return originalJson(data);
};
next();
};
}
// API路由示例
app.get('/api/users', apiCacheMiddleware(60), (req, res) => {
// 模拟数据库查询
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
res.json(users);
});
// 禁用缓存的管理接口
app.use('/admin', (req, res, next) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
next();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
缓存失效与一致性难题
缓存失效策略
1. 基于时间的失效
// Redis缓存实现(Node.js)
const redis = require('redis');
const client = redis.createClient();
async function cacheWithExpiration(key, value, ttl) {
try {
// 设置缓存并指定过期时间(秒)
await client.setEx(key, ttl, JSON.stringify(value));
console.log(`缓存设置成功,${ttl}秒后过期`);
} catch (error) {
console.error('缓存设置失败:', error);
}
}
// 使用示例
app.get('/api/products', async (req, res) => {
const cacheKey = 'products:all';
try {
// 尝试获取缓存
const cached = await client.get(cacheKey);
if (cached) {
res.setHeader('X-Cache-Status', 'HIT');
return res.json(JSON.parse(cached));
}
// 查询数据库
const products = await db.query('SELECT * FROM products');
// 设置缓存,5分钟过期
await cacheWithExpiration(cacheKey, products, 300);
res.setHeader('X-Cache-Status', 'MISS');
res.json(products);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
2. 主动失效策略
// 主动删除缓存的实现
async function invalidateCache(pattern) {
const keys = await client.keys(pattern);
if (keys.length > 0) {
await client.del(keys);
console.log(`已清除缓存: ${keys.join(', ')}`);
}
}
// 产品更新时清除相关缓存
app.put('/api/products/:id', async (req, res) => {
const { id } = req.params;
const updates = req.body;
try {
// 更新数据库
await db.query('UPDATE products SET ? WHERE id = ?', [updates, id]);
// 清除相关缓存
await invalidateCache(`products:*`);
await invalidateCache(`product:${id}`);
res.json({ success: true, message: '产品已更新,缓存已清除' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
3. 版本化缓存键
// 使用版本号管理缓存
const CACHE_VERSION = 'v2';
function getCacheKey(baseKey) {
return `${CACHE_VERSION}:${baseKey}`;
}
// 当API版本更新时,只需更改CACHE_VERSION
app.get('/api/v2/users', async (req, res) => {
const cacheKey = getCacheKey('users:all');
// ... 缓存逻辑
});
// 版本更新时的缓存清除
function updateApiVersion(newVersion) {
// 更新版本号
global.CACHE_VERSION = newVersion;
// 可选:清除旧版本缓存
// await invalidateCache('v1:*');
}
缓存一致性解决方案
1. Cache-Aside模式(旁路缓存)
这是最常见的缓存模式,应用直接与缓存和数据库交互:
// Cache-Aside模式实现
class CacheAside {
constructor(cacheClient, dbClient) {
this.cache = cacheClient;
this.db = dbClient;
}
async get(key) {
// 1. 先尝试从缓存获取
const cached = await this.cache.get(key);
if (cached) {
return JSON.parse(cached);
}
// 2. 缓存未命中,从数据库获取
const data = await this.db.query(`SELECT * FROM ${key}`);
// 3. 写入缓存
if (data) {
await this.cache.setEx(key, 300, JSON.stringify(data));
}
return data;
}
async set(key, value) {
// 1. 先更新数据库
await this.db.query(`INSERT INTO ${key} VALUES (?)`, [value]);
// 2. 再删除缓存(延迟双删策略)
await this.cache.del(key);
// 延迟再次删除(解决主从延迟问题)
setTimeout(async () => {
await this.cache.del(key);
}, 500);
}
async update(key, value) {
// 1. 先更新数据库
await this.db.query(`UPDATE ${key} SET ?`, [value]);
// 2. 删除缓存
await this.cache.del(key);
}
async delete(key) {
// 1. 先删除缓存
await this.cache.del(key);
// 2. 再删除数据库
await this.db.query(`DELETE FROM ${key}`);
}
}
2. Write-Through模式(直写模式)
数据同时写入缓存和数据库:
// Write-Through模式实现
class WriteThrough {
constructor(cacheClient, dbClient) {
this.cache = cacheClient;
this.db = dbClient;
}
async set(key, value) {
// 同时写入缓存和数据库
await Promise.all([
this.cache.setEx(key, 300, JSON.stringify(value)),
this.db.query(`INSERT INTO ${key} VALUES (?)`, [value])
]);
}
async update(key, value) {
// 同时更新缓存和数据库
await Promise.all([
this.cache.setEx(key, 300, JSON.stringify(value)),
this.db.query(`UPDATE ${key} SET ?`, [value])
]);
}
}
3. Write-Back模式(回写模式)
先写入缓存,异步批量写入数据库:
// Write-Back模式实现
class WriteBack {
constructor(cacheClient, dbClient) {
this.cache = cacheClient;
this.db = dbClient;
this.pendingWrites = new Map();
this.flushInterval = 5000; // 每5秒刷新一次
this.startFlushTimer();
}
async set(key, value) {
// 只写入缓存
await this.cache.setEx(key, 300, JSON.stringify(value));
// 记录待写入操作
this.pendingWrites.set(key, {
value: value,
timestamp: Date.now()
});
}
startFlushTimer() {
setInterval(async () => {
if (this.pendingWrites.size === 0) return;
const writes = Array.from(this.pendingWrites.entries());
this.pendingWrites.clear();
// 批量写入数据库
for (const [key, data] of writes) {
try {
await this.db.query(`INSERT INTO ${key} VALUES (?) ON DUPLICATE KEY UPDATE ?`,
[data.value, data.value]);
} catch (error) {
console.error(`批量写入失败 ${key}:`, error);
// 重新加入队列
this.pendingWrites.set(key, data);
}
}
}, this.flushInterval);
}
}
4. Pub/Sub模式解决缓存一致性
// Redis发布订阅实现缓存一致性
const redis = require('redis');
const pubClient = redis.createClient();
const subClient = pubClient.duplicate();
// 订阅缓存失效频道
subClient.subscribe('cache-invalidation', (message) => {
const { pattern, source } = JSON.parse(message);
console.log(`收到缓存失效消息: ${pattern} (来源: ${source})`);
// 在当前实例清除缓存
invalidateCache(pattern);
});
// 发布缓存失效消息
async function publishCacheInvalidation(pattern, source = 'unknown') {
await pubClient.publish('cache-invalidation', JSON.stringify({
pattern,
source,
timestamp: Date.now()
}));
}
// 更新产品并广播缓存失效
app.put('/api/products/:id', async (req, res) => {
const { id } = req.params;
const updates = req.body;
try {
// 更新数据库
await db.query('UPDATE products SET ? WHERE id = ?', [updates, id]);
// 清除本地缓存
await invalidateCache(`products:*`);
// 广播缓存失效消息
await publishCacheInvalidation(`products:*`, 'api-server-1');
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
高级缓存策略与最佳实践
1. 缓存击穿防护
缓存击穿是指某个热点数据在缓存过期的瞬间,大量请求同时打到数据库。
// 使用互斥锁防止缓存击穿
async function getHotDataWithLock(key, fetchFn, ttl = 300) {
const lockKey = `lock:${key}`;
const lockTTL = 10; // 锁的过期时间
// 尝试获取缓存
const cached = await client.get(key);
if (cached) {
return JSON.parse(cached);
}
// 获取分布式锁
const lockAcquired = await client.set(lockKey, '1', 'NX', 'EX', lockTTL);
if (lockAcquired) {
try {
// 只有获取锁的进程才能查询数据库
const data = await fetchFn();
await client.setEx(key, ttl, JSON.stringify(data));
return data;
} finally {
// 释放锁
await client.del(lockKey);
}
} else {
// 未获取锁,等待一小段时间后重试
await new Promise(resolve => setTimeout(resolve, 100));
return getHotDataWithLock(key, fetchFn, ttl);
}
}
// 使用示例
app.get('/api/hot-product', async (req, res) => {
try {
const data = await getHotDataWithLock('hot:product', async () => {
// 模拟数据库查询
return await db.query('SELECT * FROM products ORDER BY sales DESC LIMIT 1');
});
res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
2. 缓存雪崩防护
缓存雪崩是指大量缓存同时失效,导致请求全部打到数据库。
// 随机过期时间防止雪崩
function setCacheWithRandomTTL(key, value, baseTTL, variance = 0.2) {
// 在基础TTL上增加±20%的随机值
const randomTTL = baseTTL * (1 + (Math.random() * variance * 2 - variance));
return client.setEx(key, Math.floor(randomTTL), JSON.stringify(value));
}
// 多级缓存架构
class MultiLevelCache {
constructor() {
this.l1 = new Map(); // 本地内存缓存(L1)
this.l2 = redis.createClient(); // Redis缓存(L2)
this.l3 = null; // 数据库(L3)
}
async get(key) {
// L1: 本地内存
const l1Data = this.l1.get(key);
if (l1Data && l1Data.expiry > Date.now()) {
return l1Data.value;
}
// L2: Redis
const l2Data = await this.l2.get(key);
if (l2Data) {
// 回填L1
this.l1.set(key, {
value: JSON.parse(l2Data),
expiry: Date.now() + 60000 // L1缓存1分钟
});
return JSON.parse(l2Data);
}
// L3: 数据库
const data = await this.queryDatabase(key);
if (data) {
// 回填L2和L1
await this.l2.setEx(key, 300, JSON.stringify(data));
this.l1.set(key, {
value: data,
expiry: Date.now() + 60000
});
}
return data;
}
async queryDatabase(key) {
// 模拟数据库查询
return null;
}
}
3. 缓存预热
// 系统启动时预热缓存
async function warmUpCache() {
console.log('开始缓存预热...');
const hotKeys = [
{ key: 'homepage:products', ttl: 300 },
{ key: 'config:settings', ttl: 3600 },
{ key: 'user:top100', ttl: 600 }
];
for (const { key, ttl } of hotKeys) {
try {
// 获取数据
const data = await fetchFromDatabase(key);
// 写入缓存
await client.setEx(key, ttl, JSON.stringify(data));
console.log(`预热完成: ${key}`);
} catch (error) {
console.error(`预热失败 ${key}:`, error);
}
}
console.log('缓存预热完成');
}
// 定时预热(例如每天凌晨3点)
function scheduleWarmUp() {
const now = new Date();
const nextWarmUp = new Date(now);
nextWarmUp.setHours(3, 0, 0, 0); // 凌晨3点
if (nextWarmUp < now) {
nextWarmUp.setDate(nextWarmUp.getDate() + 1);
}
const delay = nextWarmUp - now;
setTimeout(async () => {
await warmUpCache();
scheduleWarmUp(); // 重新调度下一次
}, delay);
}
// 应用启动时执行
if (require.main === module) {
warmUpCache().then(() => {
scheduleWarmUp();
});
}
4. 缓存监控与指标
// 缓存监控类
class CacheMonitor {
constructor() {
this.metrics = {
hits: 0,
misses: 0,
totalRequests: 0,
hitRate: 0
};
this.startTime = Date.now();
}
recordHit() {
this.metrics.hits++;
this.metrics.totalRequests++;
this.updateHitRate();
}
recordMiss() {
this.metrics.misses++;
this.metrics.totalRequests++;
this.updateHitRate();
}
updateHitRate() {
if (this.metrics.totalRequests > 0) {
this.metrics.hitRate = (this.metrics.hits / this.metrics.totalRequests * 100).toFixed(2);
}
}
getMetrics() {
const uptime = (Date.now() - this.startTime) / 1000;
return {
...this.metrics,
uptimeSeconds: uptime.toFixed(2),
requestsPerSecond: (this.metrics.totalRequests / uptime).toFixed(2)
};
}
reset() {
this.metrics = {
hits: 0,
misses: 0,
totalRequests: 0,
hitRate: 0
};
this.startTime = Date.now();
}
}
// 集成到缓存客户端
class MonitoredCache {
constructor(client) {
this.client = client;
this.monitor = new CacheMonitor();
}
async get(key) {
const result = await this.client.get(key);
if (result) {
this.monitor.recordHit();
} else {
this.monitor.recordMiss();
}
return result;
}
async setEx(key, ttl, value) {
return this.client.setEx(key, ttl, value);
}
getStats() {
return this.monitor.getMetrics();
}
}
// Express中间件输出监控指标
app.get('/cache/metrics', (req, res) => {
res.json(cacheClient.getStats());
});
现代前端构建中的缓存策略
Webpack资源哈希
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
// 使用contenthash确保只有修改的文件哈希变化
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
clean: true
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset/resource',
generator: {
// 图片文件使用hash
filename: 'images/[name].[hash:8][ext]'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css'
}),
new HtmlWebpackPlugin({
template: './src/index.html',
// 在HTML中注入带hash的资源
inject: true,
// 添加缓存控制meta标签
meta: {
'http-equiv': {
'http-equiv': 'Cache-Control',
'content': 'public, max-age=31536000'
}
}
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
// 使用hash
filename: '[name].[contenthash:8].js',
priority: 10
}
}
}
}
};
Service Worker缓存策略
// service-worker.js
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/bundle.js',
'/images/logo.png'
];
// 安装事件 - 预缓存
self.addEventListener('install', event => {
console.log('Service Worker 安装中...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('已打开缓存空间');
return cache.addAll(urlsToCache);
})
);
self.skipWaiting(); // 立即激活新版本
});
// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
console.log('Service Worker 激活中...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// 拦截请求事件
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// 跳过非GET请求
if (request.method !== 'GET') {
return;
}
// API请求策略:网络优先,回退到缓存
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(request)
.then(response => {
// 克隆响应,因为响应流只能使用一次
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(request);
})
);
return;
}
// 静态资源策略:缓存优先,网络回退
if (url.pathname.startsWith('/static/') ||
url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
event.respondWith(
caches.match(request).then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(request).then(response => {
// 缓存新资源
if (response && response.status === 200) {
caches.open(CACHE_NAME).then(cache => {
cache.put(request, response.clone());
});
}
return response;
});
})
);
return;
}
// 其他请求:网络优先
event.respondWith(
fetch(request).catch(() => {
return caches.match(request);
})
);
});
// 后台同步(用于离线操作)
self.addEventListener('sync', event => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData());
}
});
async function syncData() {
// 实现数据同步逻辑
const requests = await getPendingRequests();
for (const request of requests) {
try {
await fetch(request.url, request.options);
await removePendingRequest(request.id);
} catch (error) {
console.error('同步失败:', error);
}
}
}
缓存策略决策树
graph TD
A[开始] --> B{资源类型?}
B -->|静态资源| C[使用文件哈希]
B -->|动态内容| D{变化频率?}
D -->|高频变化| E[协商缓存<br/>ETag/Last-Modified]
D -->|低频变化| F{是否可预测?}
F -->|是| G[主动失效<br/>版本化键]
F -->|否| H[短TTL + 重新验证]
C --> I[设置max-age=1年<br/>immutable]
E --> J[设置max-age=0<br/>must-revalidate]
G --> K[事件驱动失效]
H --> L[max-age=60-300秒]
I --> M[结束]
J --> M
K --> M
L --> M
总结
HTTP缓存是提升网站性能的关键技术,但需要在性能和一致性之间找到平衡点。以下是关键要点:
核心策略总结
- 分层缓存策略:浏览器缓存 → CDN → 代理缓存 → 应用缓存 → 数据库缓存
- 资源分类处理:
- 静态资源:长TTL + 文件哈希
- 动态API:短TTL + 协商缓存
- 用户数据:私有缓存 + 主动失效
解决一致性问题的方案
- 主动失效:数据变更时立即清除相关缓存
- 版本化键:通过版本号管理缓存生命周期
- 延迟双删:更新后延迟再次删除缓存
- Pub/Sub:跨实例广播缓存失效
性能优化要点
- 预防缓存击穿:使用分布式锁
- 预防缓存雪崩:随机过期时间 + 多级缓存
- 缓存预热:系统启动时加载热点数据
- 监控指标:实时监控缓存命中率
通过合理配置和持续优化,HTTP缓存可以显著提升网站性能,同时确保数据的一致性。在实际应用中,需要根据业务特点和系统架构选择合适的缓存策略组合。
