引言:HTTP缓存的重要性与基本概念

HTTP缓存是Web性能优化中最重要的一环,它通过在客户端(浏览器)和服务器端存储资源副本,显著减少了网络传输的数据量,降低了服务器负载,并极大地提升了用户体验。理解HTTP缓存策略不仅有助于开发者优化应用性能,还能帮助解决缓存相关的疑难杂症。

HTTP缓存的工作原理基于一个简单的概念:当浏览器第一次请求资源时,服务器会返回资源及其相关的HTTP头部信息,这些头部告诉浏览器应该如何缓存该资源。在后续的请求中,浏览器可以根据这些策略决定是使用缓存的副本,还是向服务器请求新的资源。

HTTP缓存主要分为两类:

  • 强缓存:浏览器直接从缓存中读取资源,不发送网络请求
  • 协商缓存:浏览器发送请求,但服务器返回304状态码,指示浏览器可以使用缓存的副本

浏览器缓存原理详解

浏览器缓存存储机制

现代浏览器都内置了复杂的缓存管理系统。当浏览器接收到服务器返回的资源时,它会根据响应头中的指示将资源存储在磁盘或内存中。浏览器缓存通常分为几个不同的存储区域:

  1. Memory Cache(内存缓存):存储在当前页面的内存中,读取速度最快,但生命周期与页面相同
  2. HTTP Cache(HTTP缓存):存储在磁盘上,遵循HTTP缓存规范,可以跨页面和会话使用
  3. Service Worker Cache:由Service Worker管理,提供更精细的缓存控制
  4. Push Cache(HTTP/2):服务器主动推送资源的临时缓存

浏览器在请求资源时会按照这个顺序查找缓存:Service Worker Cache → Memory Cache → HTTP Cache → Network。

浏览器缓存决策流程

当浏览器发起请求时,它会按照以下流程进行缓存决策:

  1. 检查是否命中Service Worker缓存
  2. 检查是否命中内存缓存
  3. 检查是否命中HTTP磁盘缓存
  4. 如果缓存未命中或缓存已过期,发起网络请求
  5. 服务器验证缓存有效性
  6. 根据服务器响应更新缓存

HTTP缓存头部详解

强缓存头部

Cache-Control

Cache-Control是HTTP/1.1中最重要的缓存头部,它提供了对缓存的细粒度控制。常见的指令包括:

  • max-age=<seconds>:指定资源可以被缓存的最大时间(秒)
  • no-cache:强制浏览器在使用缓存前必须向服务器验证
  • no-store:禁止浏览器和服务器缓存资源
  • public:资源可以被任何缓存存储(包括CDN)
  • private:资源只能被用户浏览器缓存,不能被CDN等中间缓存存储

示例:

Cache-Control: max-age=3600, public

Expires

Expires是HTTP/1.0的遗留头部,指定资源过期的绝对时间(GMT格式)。在HTTP/1.1中,Cache-Controlmax-age优先级更高。

示例:

Expires: Wed, 21 Oct 2025 07:28:00 GMT

协商缓存头部

Last-Modified / If-Modified-Since

Last-Modified是服务器返回资源时附加的头部,表示资源的最后修改时间。浏览器在后续请求中会发送If-Modified-Since头部,值为上次收到的Last-Modified时间。服务器比较这个时间与资源的实际修改时间,如果未修改则返回304。

示例:

# 服务器响应
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# 浏览器后续请求
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

ETag / If-None-Match

ETag是服务器为资源分配的唯一标识符(通常是内容的哈希值)。浏览器在后续请求中发送If-None-Match头部,值为上次收到的ETag。服务器比较这个值与当前资源的ETag,如果匹配则返回304。

示例:

# 服务器响应
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# 浏览器后续请求
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

服务器端缓存实现

Node.js服务器实现

以下是一个使用Node.js和Express实现完整HTTP缓存策略的示例:

const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const app = express();

// 计算文件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)}"`;

    // 设置强缓存(1小时)
    res.setHeader('Cache-Control', 'max-age=3600, public');
    res.setHeader('Expires', new Date(Date.now() + 3600000).toUTCString());

    // 协商缓存头部
    res.setHeader('Last-Modified', lastModified);
    res.setHeader('ETag', etag);

    // 检查协商缓存
    const ifModifiedSince = req.headers['if-modified-since'];
    const ifNoneMatch = req.headers['if-none-match'];

    if (ifNoneMatch && ifNoneMatch === etag) {
        return res.status(304).end();
    }

    if (ifModifiedSince && ifModifiedSince === lastModified) {
        return res.status(304).end();
    }

    // 未命中协商缓存,发送文件
    res.sendFile(filePath);
});

// API接口缓存示例
app.get('/api/data', (req, res) => {
    // 生成动态内容的ETag
    const data = { timestamp: Date.now(), message: "Hello World" };
    const content = JSON.stringify(data);
    const etag = crypto.createHash('md5').update(content).digest('hex');

    res.setHeader('ETag', `"${etag}"`);
    res.setHeader('Cache-Control', 'no-cache'); // API通常使用no-cache

    // 检查协商缓存
    if (req.headers['if-none-match'] === `"${etag}"`) {
        return res.status(304).end();
    }

    res.json(data);
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

Nginx服务器配置

Nginx作为流行的Web服务器,提供了强大的缓存配置能力:

# 定义缓存路径和参数
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)$ {
        root /var/www/html;
        
        # 强缓存:1年(对于内容哈希化的资源)
        expires 1y;
        add_header Cache-Control "public, immutable";
        
        # 开启Etag
        etag on;
        
        # 开启gzip压缩
        gzip on;
        gzip_types text/css application/javascript image/svg+xml;
        
        # 添加安全头部
        add_header X-Content-Type-Options nosniff;
    }

    # API接口缓存配置
    location /api/ {
        proxy_pass http://backend_server;
        
        # 缓存配置
        proxy_cache my_cache;
        proxy_cache_valid 200 302 10m;  # 200和302响应缓存10分钟
        proxy_cache_valid 404 1m;       # 404响应缓存1分钟
        
        # 缓存key配置
        proxy_cache_key "$scheme$request_method$host$request_uri";
        
        # 添加缓存状态头部(调试用)
        add_header X-Cache-Status $upstream_cache_status;
        
        # 协商缓存支持
        proxy_cache_revalidate on;
        
        # 缓存锁定,防止缓存击穿
        proxy_cache_lock on;
        
        # 添加必要的请求头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 禁止缓存的页面
    location ~* /(admin|login|dashboard) {
        proxy_pass http://backend_server;
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }
}

Apache服务器配置

Apache服务器通过mod_expires和mod_headers模块实现缓存控制:

# 启用必要的模块
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html

    # 静态资源缓存配置
    <IfModule mod_expires.c>
        ExpiresActive On
        
        # 图片资源
        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"
        
        # 字体文件
        ExpiresByType font/woff "access plus 1 year"
        ExpiresByType font/woff2 "access plus 1 year"
        ExpiresByType font/ttf "access plus 1 year"
        ExpiresByType font/eot "access plus 1 year"
        
        # CSS和JavaScript
        ExpiresByType text/css "access plus 1 month"
        ExpiresByType application/javascript "access plus 1 month"
        
        # HTML文件(不缓存或短时间缓存)
        ExpiresByType text/html "access plus 0 seconds"
    </IfModule>

    # Cache-Control头部配置
    <IfModule mod_headers.c>
        # 静态资源添加immutable指令
        <FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$">
            Header set Cache-Control "public, max-age=31536000, immutable"
        </FilesMatch>

        # API接口
        <LocationMatch "^/api/">
            Header set Cache-Control "no-cache, must-revalidate"
            Header set Pragma "no-cache"
        </LocationMatch>

        # 禁用缓存的页面
        <LocationMatch "^/(admin|login|dashboard)">
            Header set Cache-Control "no-store, no-cache, must-revalidate"
            Header set Pragma "no-cache"
            Header set Expires "0"
        </LocationMatch>
    </IfModule>

    # ETag配置
    FileETag MTime Size

    # Gzip压缩
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript
    </IfModule>
</VirtualHost>

缓存策略最佳实践

1. 资源版本控制与缓存清除

对于静态资源,推荐使用文件名哈希或查询参数进行版本控制:

// Webpack配置示例
module.exports = {
    output: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash:8].css'
        })
    ]
};

// 生成的文件名示例:
// app.a3f4c2e1.js
// vendor.b5d6e7f8.js
// styles.c9a8b7d6.css

在HTML中引用这些资源时,可以使用长期缓存:

<!-- 使用哈希文件名的资源可以设置1年缓存 -->
<script src="app.a3f4c2e1.js" integrity="sha384-..."></script>
<link rel="stylesheet" href="styles.c9a8b7d6.css">

<!-- 对于没有哈希的资源,使用版本号或时间戳 -->
<script src="app.js?v=20241021"></script>

2. 缓存策略决策树

根据资源类型制定缓存策略:

资源是否需要实时更新?
├── 是 → Cache-Control: no-cache 或 max-age=0
│   └── 使用协商缓存(ETag/Last-Modified)
│
└── 否 → 资源是否经常变化?
    ├── 是 → Cache-Control: max-age=3600(1小时)
    │   └── 使用协商缓存
    │
    └── 否 → 资源是否静态?
        ├── 是 → Cache-Control: max-age=31536000, immutable(1年)
        │   └── 使用文件哈希命名
        │
        └── 否 → Cache-Control: max-age=86400(1天)

3. 缓存击穿、穿透和雪崩防护

缓存击穿防护(热点数据过期)

// 使用Redis实现缓存击穿防护
const redis = require('redis');
const client = redis.createClient();

async function getHotData(key) {
    const cacheKey = `cache:${key}`;
    
    // 尝试获取缓存
    let value = await client.get(cacheKey);
    if (value) {
        return JSON.parse(value);
    }

    // 获取分布式锁
    const lockKey = `lock:${key}`;
    const lockValue = Date.now().toString();
    const lockAcquired = await client.set(lockKey, lockValue, 'NX', 'EX', 30);

    if (lockAcquired) {
        try {
            // 再次检查缓存(防止并发)
            value = await client.get(cacheKey);
            if (value) {
                return JSON.parse(value);
            }

            // 查询数据库
            const data = await queryDatabase(key);
            
            // 设置缓存,添加随机TTL防止雪崩
            const ttl = 3600 + Math.floor(Math.random() * 600); // 1小时±10分钟
            await client.setex(cacheKey, ttl, JSON.stringify(data));
            
            return data;
        } finally {
            // 释放锁
            await client.del(lockKey);
        }
    } else {
        // 等待并重试
        await new Promise(resolve => setTimeout(resolve, 100));
        return getHotData(key);
    }
}

缓存穿透防护(查询不存在的数据)

// 布隆过滤器防止缓存穿透
const Redis = require('ioredis');
const redis = new Redis();

class BloomFilter {
    constructor(key, size = 1000000, hashCount = 7) {
        this.key = key;
        this.size = size;
        this.hashCount = hashCount;
    }

    // 简单哈希函数
    hash(item, seed) {
        let hash = 0;
        for (let i = 0; i < item.length; i++) {
            hash = (hash * seed + item.charCodeAt(i)) % this.size;
        }
        return hash;
    }

    async add(item) {
        const pipeline = redis.pipeline();
        for (let i = 0; i < this.hashCount; i++) {
            const hashValue = this.hash(item, i + 2);
            pipeline.setbit(this.key, hashValue, 1);
        }
        await pipeline.exec();
    }

    async mightContain(item) {
        const pipeline = redis.pipeline();
        for (let i = 0; i < this.hashCount; i++) {
            const hashValue = this.hash(item, i + 2);
            pipeline.getbit(this.key, hashValue);
        }
        const results = await pipeline.exec();
        return results.every(([, value]) => value === 1);
    }
}

// 使用示例
const bloomFilter = new BloomFilter('user_ids_bloom');

async function getUserById(id) {
    const cacheKey = `user:${id}`;
    
    // 先检查布隆过滤器
    if (!(await bloomFilter.mightContain(id.toString()))) {
        // 确定不存在,直接返回null
        return null;
    }

    // 检查缓存
    const cached = await redis.get(cacheKey);
    if (cached) {
        return JSON.parse(cached);
    }

    // 查询数据库
    const user = await db.users.findById(id);
    
    if (user) {
        // 缓存用户数据
        await redis.setex(cacheKey, 3600, JSON.stringify(user));
    } else {
        // 缓存空值(短TTL)
        await redis.setex(cacheKey, 60, JSON.stringify(null));
    }

    return user;
}

缓存雪崩防护(大量缓存同时过期)

// 分布式缓存雪崩防护
class CacheManager {
    constructor(redisClient) {
        this.redis = redisClient;
    }

    async setWithPanic(key, value, baseTTL, panicThreshold = 0.1) {
        // 添加随机抖动(±10%)
        const jitter = 1 + (Math.random() * 0.2 - 0.1);
        const ttl = Math.floor(baseTTL * jitter);
        
        // 记录缓存设置时间用于监控
        const metadata = {
            createdAt: Date.now(),
            originalTTL: baseTTL,
            actualTTL: ttl,
            jitter: jitter
        };

        await this.redis.setex(key, ttl, JSON.stringify({
            data: value,
            meta: metadata
        }));

        return ttl;
    }

    async getWithFallback(key, fallbackFn, options = {}) {
        const {
            baseTTL = 3600,
            panicMode = false,
            maxRetries = 3
        } = options;

        // 尝试获取缓存
        const cached = await this.redis.get(key);
        if (cached) {
            const parsed = JSON.parse(cached);
            // 检查是否进入panic模式(缓存即将过期)
            const age = (Date.now() - parsed.meta.createdAt) / 1000;
            const remaining = parsed.meta.actualTTL - age;
            
            if (panicMode && remaining < 60) {
                // 缓存即将过期,异步刷新
                this.refreshCacheAsync(key, fallbackFn, baseTTL);
            }
            
            return parsed.data;
        }

        // 缓存未命中,执行回源
        let lastError;
        for (let i = 0; i < maxRetries; i++) {
            try {
                const data = await fallbackFn();
                await this.setWithPanic(key, data, baseTTL);
                return data;
            } catch (error) {
                lastError = error;
                if (i < maxRetries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, i)));
                }
            }
        }

        throw lastError;
    }

    async refreshCacheAsync(key, fallbackFn, baseTTL) {
        // 异步刷新缓存,不阻塞主请求
        try {
            const data = await fallbackFn();
            await this.setWithPanic(key, data, baseTTL);
            console.log(`Async refresh completed for ${key}`);
        } catch (error) {
            console.error(`Async refresh failed for ${key}:`, error);
        }
    }
}

4. Service Worker缓存策略

Service Worker提供了更精细的缓存控制,可以实现离线应用:

// service-worker.js
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
    '/',
    '/styles/main.css',
    '/scripts/app.js',
    '/images/logo.png'
];

// 安装事件:预缓存核心资源
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(urlsToCache))
            .then(() => self.skipWaiting())
    );
});

// 激活事件:清理旧缓存
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME) {
                        return caches.delete(cacheName);
                    }
                })
            );
        }).then(() => self.clients.claim())
    );
});

// 拦截请求并返回缓存或网络响应
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 => {
                    // 缓存成功的API响应
                    if (response.ok) {
                        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.match(/\.(css|js|png|jpg|jpeg|svg|woff|woff2)$/)) {
        event.respondWith(
            caches.match(request).then(cachedResponse => {
                if (cachedResponse) {
                    return cachedResponse;
                }
                return fetch(request).then(response => {
                    // 缓存新资源
                    if (response && response.status === 200) {
                        const responseClone = response.clone();
                        caches.open(CACHE_NAME).then(cache => {
                            cache.put(request, responseClone);
                        });
                    }
                    return response;
                });
            })
        );
        return;
    }

    // 页面请求策略:网络优先,缓存后备
    if (request.headers.get('accept')?.includes('text/html')) {
        event.respondWith(
            fetch(request).then(response => {
                // 缓存成功的HTML响应
                if (response.ok) {
                    const responseClone = response.clone();
                    caches.open(CACHE_NAME).then(cache => {
                        cache.put(request, responseClone);
                    });
                }
                return response;
            }).catch(() => {
                // 网络失败时返回缓存或离线页面
                return caches.match(request).then(cachedResponse => {
                    return cachedResponse || caches.match('/offline.html');
                });
            })
        );
    }
});

// 后台同步:离线时缓存请求,恢复网络后发送
self.addEventListener('sync', event => {
    if (event.tag === 'background-sync') {
        event.waitUntil(
            caches.open('sync-queue').then(cache => {
                return cache.keys().then(requests => {
                    return Promise.all(
                        requests.map(request => {
                            return fetch(request).then(response => {
                                if (response.ok) {
                                    return cache.delete(request);
                                }
                                throw new Error('Sync failed');
                            });
                        })
                    );
                });
            })
        );
    }
});

缓存监控与调试

浏览器开发者工具分析

在Chrome DevTools中,可以通过Network面板查看缓存行为:

  1. 查看缓存状态:在Network面板中,查看Size列和Time列

    • (memory cache)(disk cache) 表示命中缓存
    • 304 Not Modified 表示协商缓存命中
    • 200 OK 表示从网络获取
  2. 查看缓存头部:点击请求,查看Headers标签页中的Response Headers

  3. 禁用缓存:在Network面板勾选”Disable cache”进行调试

缓存状态监控

在服务器端添加缓存状态头部,便于调试:

// Express中间件:添加缓存状态头部
app.use((req, res, next) => {
    const originalJson = res.json.bind(res);
    res.json = function(data) {
        // 添加缓存元数据
        if (req.headers['if-none-match'] || req.headers['if-modified-since']) {
            res.setHeader('X-Cache-Check', 'true');
        }
        return originalJson(data);
    };
    next();
});

// Nginx配置中添加缓存状态
add_header X-Cache-Status $upstream_cache_status;
// 可能的值:HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED

缓存分析工具

// 缓存分析器
class CacheAnalyzer {
    static analyzeHeaders(headers) {
        const analysis = {
            strongCache: false,
           协商缓存: false,
            directives: []
        };

        const cacheControl = headers['cache-control'];
        if (cacheControl) {
            const directives = cacheControl.split(',').map(d => d.trim());
            analysis.directives = directives;

            if (directives.includes('no-store')) {
                analysis.strongCache = false;
                analysis.协商缓存 = false;
            } else if (directives.includes('no-cache')) {
                analysis.strongCache = false;
                analysis.协商缓存 = true;
            } else if (directives.some(d => d.startsWith('max-age='))) {
                const maxAge = parseInt(directives.find(d => d.startsWith('max-age=')).split('=')[1]);
                analysis.strongCache = maxAge > 0;
                analysis.协商缓存 = true;
            }
        }

        if (headers['etag'] || headers['last-modified']) {
            analysis.协商缓存 = true;
        }

        return analysis;
    }

    static calculateSavings(originalSize, cached) {
        if (cached) {
            return {
                bytesSaved: originalSize,
                percentageSaved: 100,
                message: '完全命中缓存'
            };
        }
        return {
            bytesSaved: 0,
            percentageSaved: 0,
            message: '未命中缓存'
        };
    }
}

高级缓存策略

1. 缓存分区(Cache Partitioning)

现代浏览器为了防止Spectre等安全漏洞,实施了缓存分区策略。缓存不再仅基于URL,而是基于顶级域名和资源URL的组合:

缓存键 = (顶级域名, 资源URL)

这意味着从 a.com 加载的 example.com/resource.js 和从 b.com 加载的 example.com/resource.js 会被分别缓存。

2. Vary头部的使用

Vary头部用于指定缓存键的额外维度:

// 根据User-Agent返回不同内容
app.get('/api/device', (req, res) => {
    const userAgent = req.headers['user-agent'];
    const isMobile = /Mobile/i.test(userAgent);
    
    // 设置Vary头部
    res.setHeader('Vary', 'User-Agent');
    res.setHeader('Cache-Control', 'public, max-age=3600');
    
    if (isMobile) {
        res.json({ version: 'mobile', content: '移动版内容' });
    } else {
        res.json({ version: 'desktop', content: '桌面版内容' });
    }
});

3. 边缘计算与CDN缓存

// Cloudflare Workers示例
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    const url = new URL(request.url);
    
    // 检查CDN缓存
    const cache = caches.default;
    let response = await cache.match(request);
    
    if (response) {
        // 添加CDN缓存命中头部
        response = new Response(response.body, response);
        response.headers.set('X-CDN-Cache', 'HIT');
        return response;
    }
    
    // 回源获取
    response = await fetch(request);
    
    // 缓存响应(排除特定条件)
    if (response.ok && !request.headers.get('cache-control')?.includes('no-store')) {
        const cacheResponse = response.clone();
        cacheResponse.headers.set('Cache-Control', 'public, max-age=3600');
        event.waitUntil(cache.put(request, cacheResponse));
    }
    
    response.headers.set('X-CDN-Cache', 'MISS');
    return response;
}

常见问题与解决方案

1. 缓存污染问题

问题:用户更新了资源,但浏览器仍然使用旧缓存。

解决方案

  • 使用文件哈希命名:app.a3f4c2e1.js
  • 使用版本号:/v1/app.js
  • 使用查询参数:/app.js?v=20241021
  • 设置适当的Cache-Control头部

2. 缓存击穿

问题:热点数据同时过期,大量请求同时打到数据库。

解决方案

  • 使用互斥锁(Mutex)防止并发重建缓存
  • 缓存预热:在低峰期提前加载缓存
  • 使用随机TTL分散过期时间

3. 缓存穿透

问题:查询不存在的数据,导致每次都打到数据库。

解决方案

  • 使用布隆过滤器快速判断数据是否存在
  • 缓存空值(短TTL)
  • 参数校验

4. 缓存雪崩

问题:大量缓存同时失效,导致数据库压力激增。

解决方案

  • 缓存预热
  • 随机TTL
  • 多级缓存(本地缓存+分布式缓存)
  • 熔断降级

总结

HTTP缓存是Web性能优化的核心技术,涉及浏览器原理、服务器配置、网络协议等多个层面。掌握缓存策略需要理解:

  1. 强缓存与协商缓存的工作原理和适用场景
  2. HTTP头部的精确控制(Cache-Control、ETag、Last-Modified等)
  3. 服务器实现(Node.js、Nginx、Apache等)
  4. 最佳实践(资源版本控制、策略决策、防护机制)
  5. 高级场景(Service Worker、CDN、边缘计算)

通过合理配置缓存策略,可以显著提升应用性能,降低服务器成本,改善用户体验。同时,需要建立完善的监控和调试机制,及时发现和解决缓存相关问题。