引言

在当今互联网高速发展的时代,网站性能和用户体验已成为决定产品成败的关键因素。HTTP缓存作为提升网站性能的核心技术之一,能够显著减少网络请求、降低服务器负载、加快页面加载速度。本文将深入解析HTTP缓存的原理、策略、配置方法以及实战优化技巧,帮助开发者全面掌握这一重要技术。

一、HTTP缓存基础概念

1.1 什么是HTTP缓存

HTTP缓存是一种客户端(浏览器)和服务器之间的机制,允许浏览器存储之前请求过的资源副本,以便在后续请求中直接使用,而无需重新从服务器获取。这大大减少了网络传输量和服务器压力。

1.2 缓存的分类

HTTP缓存主要分为两类:

  1. 浏览器缓存:存储在用户设备上的缓存,如磁盘缓存、内存缓存
  2. 代理缓存:存储在中间代理服务器(如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"

工作流程

  1. 首次请求:服务器返回资源和ETag
  2. 后续请求:浏览器发送If-None-Match头
  3. 服务器比较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缓存是优化网站性能和用户体验的关键技术。通过合理配置缓存策略,可以显著减少网络请求、降低服务器负载、加快页面加载速度。本文从缓存基础概念、机制详解、配置实战、优化技巧、监控调试、高级策略等多个方面进行了全面解析。

关键要点回顾:

  1. 理解缓存机制:掌握Cache-Control、ETag、Last-Modified等核心概念
  2. 合理配置缓存:根据资源类型和业务需求设置合适的缓存策略
  3. 优化缓存策略:采用分层缓存、版本化、预热等高级技巧
  4. 监控缓存效果:通过命中率监控和调试工具持续优化
  5. 应对挑战:解决缓存击穿、雪崩、污染等常见问题

实践建议:

  1. 静态资源:使用版本化文件名 + 长时间缓存(1年)
  2. 动态内容:根据业务需求设置合理缓存时间(秒到分钟级)
  3. 个性化内容:使用private缓存或不缓存
  4. 敏感数据:使用no-store禁止缓存
  5. 定期审查:监控缓存命中率,调整策略

通过本文的详细解析和实战示例,相信您已经掌握了HTTP缓存的核心技术和优化方法。在实际项目中,请根据具体业务场景灵活应用这些策略,持续监控和优化,以达到最佳的性能和用户体验效果。