Web 缓存技术教程

从浏览器缓存到服务器缓存的全面指南

目录

1. 缓存技术概述

缓存是一种提高数据访问速度的技术,通过将频繁访问的数据存储在更快的存储介质中,减少对原始数据源的访问,从而提高系统性能和用户体验。

1.1 为什么需要缓存

1.2 缓存的类型

小提示

缓存虽然能提高性能,但也会带来数据一致性的挑战。合理设计缓存策略是平衡性能和一致性的关键。

2. 浏览器缓存

2.1 浏览器缓存概述

浏览器缓存是存储在用户浏览器中的临时文件,用于加快页面加载速度。当用户再次访问同一网站时,浏览器可以直接从缓存中加载资源,而不需要重新从服务器下载。

2.2 浏览器缓存的类型

2.3 浏览器缓存控制

浏览器缓存可以通过 HTTP 头部进行控制:

2.4 浏览器缓存示例

在 HTML 中设置缓存控制:

<meta http-equiv="Cache-Control" content="max-age=3600">

在服务器响应中设置缓存控制:

HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: public, max-age=3600
Expires: Wed, 21 Oct 2023 07:28:00 GMT
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
注意

浏览器缓存虽然可以提高性能,但也会导致用户看到过时的内容。对于频繁更新的内容,应该适当设置缓存策略。

3. HTTP 缓存机制

3.1 HTTP 缓存概述

HTTP 缓存机制是 Web 缓存的基础,它定义了客户端和服务器之间如何协商缓存内容。HTTP 缓存可以减少网络请求,提高应用性能。

3.2 HTTP 缓存控制头部

头部 描述 示例
Cache-Control 控制缓存行为 max-age=3600, public
Expires 指定缓存过期时间 Wed, 21 Oct 2023 07:28:00 GMT
ETag 资源版本标识 "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified 资源最后修改时间 Wed, 21 Oct 2023 07:28:00 GMT
If-None-Match 条件请求,与 ETag 配合使用 "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since 条件请求,与 Last-Modified 配合使用 Wed, 21 Oct 2023 07:28:00 GMT

3.3 Cache-Control 指令

指令 描述
public 响应可以被任何缓存存储
private 响应只能被浏览器缓存
no-cache 在使用缓存前必须验证
no-store 不存储任何缓存
max-age 缓存的最大有效时间(秒)
s-maxage 共享缓存的最大有效时间(秒)
must-revalidate 缓存过期后必须验证

3.4 缓存验证流程

  1. 浏览器发送请求,检查本地缓存
  2. 如果缓存有效,直接使用缓存内容
  3. 如果缓存过期,发送条件请求(带有 If-None-Match 或 If-Modified-Since)
  4. 服务器检查资源是否变化
  5. 如果资源未变化,返回 304 Not Modified
  6. 如果资源已变化,返回新资源和 200 OK
小提示

对于静态资源(如 CSS、JavaScript、图片等),应该设置较长的缓存时间,并使用文件名或路径中包含版本号的方式来控制缓存更新。

4. 服务器端缓存

4.1 服务器端缓存概述

服务器端缓存是指在 Web 服务器上实现的缓存机制,用于减轻应用服务器和数据库的负载,提高响应速度。

4.2 服务器端缓存的类型

4.3 Nginx 缓存配置示例

http {
    # 定义缓存路径和参数
    proxy_cache_path /path/to/cache 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 / {
            proxy_pass http://backend;
            proxy_cache my_cache;
            proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
            proxy_cache_valid 200 60m;
            proxy_cache_valid 404 1m;
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

4.4 CDN 缓存

内容分发网络(CDN)是一种分布式缓存系统,通过在全球各地部署节点,将内容缓存到离用户最近的服务器上,从而加快访问速度。

CDN 缓存的主要优势:

注意

服务器端缓存虽然可以提高性能,但也会增加系统复杂性。需要根据实际需求选择合适的缓存策略和缓存系统。

5. 应用层缓存

5.1 应用层缓存概述

应用层缓存是指在应用程序内部实现的缓存机制,用于存储频繁访问的数据,减少对数据库或其他外部服务的请求。

5.2 应用层缓存的实现方式

5.3 Java 应用缓存示例

使用 Caffeine 缓存库:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

public class CacheExample {
    private Cache<String, Data> cache;
    
    public CacheExample() {
        cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
    }
    
    public Data getData(String key) {
        return cache.get(key, this::loadData);
    }
    
    private Data loadData(String key) {
        // 从数据库或其他数据源加载数据
        return database.loadData(key);
    }
}

5.4 Spring Cache 示例

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        // 从数据库加载用户
        return userRepository.findById(id).orElse(null);
    }
}
小提示

应用层缓存应该只缓存那些读取频繁、更新不频繁的数据。对于频繁更新的数据,应该谨慎使用缓存,或者采用适当的缓存失效策略。

6. 数据库缓存

6.1 数据库缓存概述

数据库缓存是指数据库系统内部的缓存机制,用于存储频繁访问的数据,减少磁盘 I/O 操作,提高查询性能。

6.2 数据库缓存的类型

6.3 MySQL 查询缓存

MySQL 的查询缓存可以缓存 SELECT 语句的结果:

-- 启用查询缓存
SET GLOBAL query_cache_size = 67108864; -- 64MB
SET GLOBAL query_cache_type = 1;

-- 使用 SQL_CACHE 提示强制使用缓存
SELECT SQL_CACHE * FROM users WHERE id = 1;
注意

MySQL 8.0 已经移除了查询缓存功能,因为它在大规模应用中可能会导致性能问题。对于现代应用,应该使用应用层缓存或分布式缓存。

6.4 数据库连接池

数据库连接池可以缓存数据库连接,减少连接创建和销毁的开销:

// HikariCP 连接池配置示例
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
config.setIdleTimeout(300000);
config.setConnectionTimeout(20000);

HikariDataSource dataSource = new HikariDataSource(config);

7. 分布式缓存

7.1 分布式缓存概述

分布式缓存是一种跨多个服务器的缓存系统,用于在分布式环境中共享缓存数据,提高系统可扩展性和可用性。

7.2 分布式缓存的优势

7.3 常见的分布式缓存系统

缓存系统 特点 使用场景
Redis 内存数据库,支持多种数据结构 会话存储,计数器,排行榜
Memcached 简单的键值存储 对象缓存,会话存储
Hazelcast Java 分布式计算平台 Java 应用缓存,分布式计算
Apache Ignite 分布式内存平台 高性能计算,实时分析

7.4 Redis 缓存示例

// Java 中使用 Redis 缓存
import redis.clients.jedis.Jedis;

public class RedisCache {
    private Jedis jedis;
    
    public RedisCache() {
        jedis = new Jedis("localhost", 6379);
    }
    
    public void set(String key, String value, int expireSeconds) {
        jedis.set(key, value);
        jedis.expire(key, expireSeconds);
    }
    
    public String get(String key) {
        return jedis.get(key);
    }
    
    public boolean exists(String key) {
        return jedis.exists(key);
    }
    
    public void delete(String key) {
        jedis.del(key);
    }
}
小提示

分布式缓存虽然可以提高系统性能和可扩展性,但也会增加系统复杂性。需要根据实际需求选择合适的分布式缓存系统,并合理设计缓存策略。

8. 缓存策略

8.1 缓存策略概述

缓存策略是指如何管理缓存数据的生命周期,包括何时缓存数据、何时更新缓存、何时失效缓存等。

8.2 常见的缓存策略

8.3 Cache-Aside 策略示例

public class CacheAsideExample {
    private Cache cache;
    private DataSource dataSource;
    
    public Data getData(String key) {
        // 1. 查询缓存
        Data data = cache.get(key);
        
        // 2. 缓存未命中,查询数据源
        if (data == null) {
            data = dataSource.getData(key);
            
            // 3. 更新缓存
            if (data != null) {
                cache.set(key, data, 3600); // 缓存一小时
            }
        }
        
        return data;
    }
    
    public void updateData(String key, Data data) {
        // 1. 更新数据源
        dataSource.updateData(key, data);
        
        // 2. 删除缓存(或更新缓存)
        cache.delete(key);
    }
}

8.4 缓存策略选择

选择合适的缓存策略需要考虑以下因素:

注意

没有一种缓存策略适用于所有场景,应该根据实际需求选择合适的缓存策略。

9. 缓存失效

9.1 缓存失效概述

缓存失效是指使缓存数据不再有效的过程,通常是因为底层数据发生变化,需要更新或删除缓存。

9.2 缓存失效策略

9.3 缓存失效示例

// 时间过期示例
cache.set("key", "value", 3600); // 缓存一小时

// 主动失效示例
public void updateUser(User user) {
    // 更新数据库
    userRepository.save(user);
    
    // 删除缓存
    cache.delete("user:" + user.getId());
}

// 版本控制示例
public class VersionedCache {
    private Cache cache;
    
    public void set(String key, Object value) {
        int version = getNextVersion();
        cache.set(key, new VersionedValue(value, version));
    }
    
    public Object get(String key) {
        VersionedValue value = cache.get(key);
        if (value != null && value.getVersion() == getCurrentVersion()) {
            return value.getValue();
        }
        return null;
    }
    
    private int getNextVersion() {
        // 获取下一个版本号
        return cache.increment("version");
    }
    
    private int getCurrentVersion() {
        // 获取当前版本号
        return cache.get("version");
    }
}

9.4 缓存穿透、缓存击穿和缓存雪崩

缓存失效可能导致以下问题:

解决方案:

小提示

缓存失效是缓存系统中最复杂的问题之一,需要根据实际场景选择合适的缓存失效策略,并处理好缓存穿透、缓存击穿和缓存雪崩等问题。

10. 最佳实践

10.1 缓存设计原则

10.2 缓存性能优化

10.3 缓存安全

10.4 缓存测试

笔记

缓存是一种提高系统性能的有效手段,但也需要合理设计和谨慎使用。过度使用缓存可能导致系统复杂性增加,数据一致性难以保证。

返回首页