引言:HTTP缓存的重要性

HTTP缓存是现代Web性能优化的核心技术之一,它通过在客户端(浏览器)或中间代理服务器上存储资源副本,从而减少网络请求、降低服务器负载并显著提升用户体验。根据Google的研究,合理的缓存策略可以将页面加载时间减少50%以上。

然而,缓存策略并非一劳永逸的解决方案。开发者经常面临两大挑战:

  1. 缓存失效:如何确保用户获取到最新的资源版本
  2. 缓存一致性:如何在分布式系统中保持多个缓存副本的一致性

本文将深入解析HTTP缓存的工作原理、各种缓存策略的实现方式,并提供解决缓存失效与一致性问题的完整方案。

HTTP缓存基础概念

缓存的分类

HTTP缓存主要分为两类:

  1. 私有缓存(Private Cache)

    • 通常存在于浏览器中
    • 只为单个用户存储资源副本
    • 例如:浏览器缓存、本地存储
  2. 共享缓存(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-Controlmax-age会覆盖Expires

2. 协商缓存(协商缓存)

当强缓存过期或不存在时,浏览器会与服务器进行协商,确定是否可以使用缓存。

Last-Modified / If-Modified-Since

工作流程:

  1. 服务器首次响应时添加Last-Modified头部
  2. 浏览器下次请求时添加If-Modified-Since头部
  3. 服务器比较时间戳,如果未修改则返回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是资源的唯一标识符,比时间戳更可靠。

工作流程:

  1. 服务器首次响应时添加ETag头部
  2. 浏览器下次请求时添加If-None-Match头部
  3. 服务器比较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缓存是提升网站性能的关键技术,但需要在性能和一致性之间找到平衡点。以下是关键要点:

核心策略总结

  1. 分层缓存策略:浏览器缓存 → CDN → 代理缓存 → 应用缓存 → 数据库缓存
  2. 资源分类处理
    • 静态资源:长TTL + 文件哈希
    • 动态API:短TTL + 协商缓存
    • 用户数据:私有缓存 + 主动失效

解决一致性问题的方案

  1. 主动失效:数据变更时立即清除相关缓存
  2. 版本化键:通过版本号管理缓存生命周期
  3. 延迟双删:更新后延迟再次删除缓存
  4. Pub/Sub:跨实例广播缓存失效

性能优化要点

  1. 预防缓存击穿:使用分布式锁
  2. 预防缓存雪崩:随机过期时间 + 多级缓存
  3. 缓存预热:系统启动时加载热点数据
  4. 监控指标:实时监控缓存命中率

通过合理配置和持续优化,HTTP缓存可以显著提升网站性能,同时确保数据的一致性。在实际应用中,需要根据业务特点和系统架构选择合适的缓存策略组合。