第7章:缓存策略

7.1 代理缓存

Nginx代理缓存允许将后端服务器的响应缓存到本地,减少后端服务器的负载,提高响应速度。

7.1.1 缓存区域配置

配置示例

http {
    # 定义缓存区域
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g 
                    inactive=60m use_temp_path=off;
    
    # ... 其他配置 ...
    
    server {
        # ... 其他配置 ...
        
        location / {
            # 启用代理缓存
            proxy_cache my_cache;
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            
            # 转发到后端服务器
            proxy_pass http://backend;
        }
    }
}

指令说明

  • proxy_cache_path:定义缓存区域

    • /var/cache/nginx:缓存文件存储路径
    • levels=1:2:缓存目录结构(1级目录,2级子目录)
    • keys_zone=my_cache:10m:缓存区域名称和内存大小
    • max_size=10g:缓存最大大小
    • inactive=60m:缓存项60分钟未被访问则自动删除
    • use_temp_path=off:不使用临时目录,直接写入最终目录
  • proxy_cache my_cache:启用代理缓存,使用名为my_cache的缓存区域

  • proxy_cache_valid 200 302 10m:状态码200和302的响应缓存10分钟

  • proxy_cache_valid 404 1m:状态码404的响应缓存1分钟

  • proxy_cache_use_stale:在后端服务器出现错误时,使用过期的缓存响应

7.1.2 缓存键设计

缓存键用于唯一标识缓存项,默认使用$scheme$proxy_host$request_uri作为缓存键。

配置示例

http {
    # ... 其他配置 ...
    
    server {
        # ... 其他配置 ...
        
        location / {
            # 自定义缓存键
            proxy_cache_key "$scheme$proxy_host$request_uri$arg_user";
            
            # 基于请求方法的缓存策略
            proxy_cache_methods GET HEAD;
            
            # 启用代理缓存
            proxy_cache my_cache;
            proxy_pass http://backend;
        }
    }
}

指令说明

  • proxy_cache_key:自定义缓存键,包含用户参数
  • proxy_cache_methods:只缓存GET和HEAD请求

常见缓存键设计

  • 基本缓存键:$scheme$proxy_host$request_uri
  • 包含用户参数:$scheme$proxy_host$request_uri$arg_user
  • 包含Cookie:$scheme$proxy_host$request_uri$http_cookie
  • 包含请求头:$scheme$proxy_host$request_uri$http_accept_language

7.1.3 缓存清除策略

7.1.3.1 基于时间的清除

使用proxy_cache_valid指令设置缓存项的有效期,过期后自动清除。

7.1.3.2 手动清除

使用第三方模块ngx_cache_purge手动清除缓存项。

安装ngx_cache_purge模块

# 下载Nginx源码
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz

# 下载ngx_cache_purge模块
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/refs/tags/2.3.tar.gz
tar -zxvf 2.3.tar.gz

# 重新编译Nginx,添加ngx_cache_purge模块
cd nginx-1.18.0
./configure --with-http_ssl_module --add-module=../ngx_cache_purge-2.3
make
make install

配置示例

http {
    # 定义缓存区域
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
    
    # ... 其他配置 ...
    
    server {
        # ... 其他配置 ...
        
        location / {
            proxy_cache my_cache;
            proxy_pass http://backend;
        }
        
        # 缓存清除配置
        location ~ /purge(/.*) {
            allow 127.0.0.1;
            allow 192.168.1.0/24;
            deny all;
            
            proxy_cache_purge my_cache "$scheme$proxy_host$1";
        }
    }
}

使用示例

# 清除指定URL的缓存
curl http://example.com/purge/path/to/resource

7.1.3.3 自动清除(商业版)

Nginx Plus提供了proxy_cache_purge指令的扩展版本,支持更灵活的缓存清除策略,如按前缀清除、按标签清除等。

7.2 浏览器缓存优化

浏览器缓存允许客户端将资源缓存到本地,减少重复请求,提高页面加载速度。

7.2.1 缓存控制头设置

配置示例

server {
    # ... 其他配置 ...
    
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot)$ {
        # 设置过期时间为30天
        expires 30d;
        
        # 设置Cache-Control头
        add_header Cache-Control "public, no-transform";
        
        # 不记录静态资源日志
        access_log off;
    }
    
    location ~* \.(html|htm|txt)$ {
        # 设置过期时间为1小时
        expires 1h;
        
        # 设置Cache-Control头
        add_header Cache-Control "public, must-revalidate";
    }
    
    location ~* \.(xml|json)$ {
        # 设置过期时间为10分钟
        expires 10m;
        
        # 设置Cache-Control头
        add_header Cache-Control "public, must-revalidate";
    }
}

指令说明

  • expires 30d;:设置缓存过期时间为30天
  • add_header Cache-Control "public, no-transform";:允许公共缓存,禁止转换内容
  • add_header Cache-Control "public, must-revalidate";:允许公共缓存,但每次请求都必须验证

Cache-Control指令值

  • public:允许公共缓存(如CDN)
  • private:只允许私有缓存(如浏览器)
  • no-cache:需要验证缓存有效性
  • no-store:禁止缓存
  • must-revalidate:过期后必须验证
  • max-age=3600:缓存有效期为3600秒

7.2.2 ETag与Last-Modified

ETag和Last-Modified是用于验证缓存有效性的HTTP头。

配置示例

server {
    # ... 其他配置 ...
    
    location / {
        # 启用ETag
        etag on;
        
        # 启用Last-Modified
        if_modified_since before;
        
        # ... 其他配置 ...
    }
}

工作原理

  1. 服务器响应时发送ETagLast-Modified
  2. 客户端下次请求时发送If-None-Match(包含ETag值)和If-Modified-Since(包含Last-Modified值)头
  3. 服务器验证缓存是否有效:
    • 如果缓存有效,返回304 Not Modified,不包含响应体
    • 如果缓存无效,返回200 OK,包含完整响应体

验证配置

# 第一次请求,获取完整响应
curl -I http://example.com/image.jpg

# 第二次请求,应该返回304 Not Modified
curl -I -H "If-None-Match: [ETag值]" -H "If-Modified-Since: [Last-Modified值]" http://example.com/image.jpg

7.3 实战项目3:为电商网站配置多级缓存

在这个实战项目中,我们将为电商网站配置多级缓存,包括浏览器缓存、Nginx代理缓存和Redis缓存。

7.3.1 项目准备

1. 安装依赖

# 安装Redis
sudo apt-get update
sudo apt-get install -y redis-server

# 安装Nginx Redis模块依赖
sudo apt-get install -y libhiredis-dev

2. 重新编译Nginx,添加Redis模块

# 下载ngx_http_redis模块
git clone https://github.com/openresty/redis2-nginx-module.git

# 重新编译Nginx
cd nginx-1.18.0
./configure --with-http_ssl_module --with-http_v2_module --add-module=../redis2-nginx-module
make
make install

7.3.2 配置多级缓存

1. 配置Redis缓存

# 启动Redis
sudo systemctl start redis-server

# 测试Redis连接
redis-cli ping

2. 配置Nginx多级缓存

http {
    # 定义代理缓存区域
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nginx_cache:10m max_size=10g inactive=60m use_temp_path=off;
    
    # Redis服务器配置
    upstream redis {
        server 127.0.0.1:6379;
        keepalive 1024;
    }
    
    # 后端应用服务器配置
    upstream backend {
        server 127.0.0.1:8000;
        server 127.0.0.1:8001;
    }
    
    server {
        listen 80;
        server_name example.com;
        
        # 静态资源配置(浏览器缓存)
        location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot)$ {
            root /var/www/html;
            expires 30d;
            add_header Cache-Control "public, no-transform";
            access_log off;
        }
        
        # 商品详情页配置(多级缓存)
        location /product/ {
            # 1. 尝试从Redis缓存获取
            set $redis_key "product:$uri";
            redis2_query get $redis_key;
            redis2_pass redis;
            
            # 2. 如果Redis缓存不存在,从Nginx代理缓存获取
            error_page 404 = @proxy_cache;
        }
        
        # Nginx代理缓存配置
        location @proxy_cache {
            proxy_cache nginx_cache;
            proxy_cache_valid 200 60m;
            proxy_cache_key "$scheme$proxy_host$request_uri";
            proxy_pass http://backend;
            
            # 3. 将响应缓存到Redis
            set $redis_key "product:$uri";
            set $redis_ttl 3600;
            header_filter_by_lua_block {
                local redis_key = ngx.var.redis_key;
                local redis_ttl = ngx.var.redis_ttl;
                local response_body = ngx.arg[1];
                
                -- 连接Redis
                local redis = require "resty.redis";
                local red = redis:new();
                red:set_timeout(1000);
                local ok, err = red:connect("127.0.0.1", 6379);
                
                if ok then
                    -- 将响应体缓存到Redis
                    red:setex(redis_key, redis_ttl, response_body);
                    red:set_keepalive(10000, 100);
                end
            }
        }
        
        # 其他路径配置
        location / {
            proxy_pass http://backend;
        }
    }
}

7.3.3 验证多级缓存

1. 测试缓存命中率

# 在Nginx配置中添加缓存命中率日志
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" '
                  'cache_status=$upstream_cache_status';

access_log /var/log/nginx/access.log main;

2. 查看缓存命中率

grep -o 'cache_status=[^ ]*' /var/log/nginx/access.log | sort | uniq -c

预期输出

100 cache_status=HIT
20 cache_status=MISS
5 cache_status=EXPIRED

3. 测试Redis缓存

# 使用Redis客户端查看缓存
redis-cli keys "product:*"

# 查看缓存内容
redis-cli get "product:/product/123"

4. 测试浏览器缓存

# 第一次请求
curl -I http://example.com/product/123

# 第二次请求(应该返回304 Not Modified)
curl -I -H "If-None-Match: [ETag值]" -H "If-Modified-Since: [Last-Modified值]" http://example.com/product/123

7.3.4 缓存清除机制

1. 添加缓存清除接口

# 缓存清除配置
location ~ /purge(/.*) {
    allow 127.0.0.1;
    allow 192.168.1.0/24;
    deny all;
    
    # 清除Nginx代理缓存
    proxy_cache_purge nginx_cache "$scheme$proxy_host$1";
    
    # 清除Redis缓存
    content_by_lua_block {
        local redis_key = "product:$1";
        local redis = require "resty.redis";
        local red = redis:new();
        red:set_timeout(1000);
        local ok, err = red:connect("127.0.0.1", 6379);
        
        if ok then
            red:del(redis_key);
            red:set_keepalive(10000, 100);
        end
        
        ngx.say("Cache purged for " .. redis_key);
    }
}

2. 使用示例

# 清除指定商品的缓存
curl http://example.com/purge/product/123

7.3.5 常见问题与解决方案

问题1:缓存不生效

解决方案

  1. 检查Nginx配置中的proxy_cache_path指令是否正确
  2. 确保缓存目录存在且Nginx有写入权限:
    sudo mkdir -p /var/cache/nginx
    sudo chown -R www-data:www-data /var/cache/nginx
  3. 检查后端服务器是否返回Cache-Control: no-cacheno-store

问题2:缓存命中率低

解决方案

  1. 增加缓存有效期
  2. 优化缓存键设计,减少缓存碎片化
  3. 确保缓存区域足够大,避免频繁淘汰
  4. 考虑使用CDN缓存静态资源

问题3:缓存更新不及时

解决方案

  1. 实现缓存清除机制,在数据更新时主动清除缓存
  2. 设置合理的缓存有效期,平衡性能和新鲜度
  3. 使用stale-while-revalidatestale-if-error指令,在更新缓存时返回旧缓存

章节总结

在本章中,我们学习了:

  1. 代理缓存

    • 缓存区域配置
    • 缓存键设计
    • 缓存清除策略
  2. 浏览器缓存优化

    • 缓存控制头设置
    • ETag与Last-Modified
  3. 实战项目

    • 为电商网站配置多级缓存(Redis + Nginx代理缓存 + 浏览器缓存)
    • 验证缓存命中率
    • 实现缓存清除机制

实践练习

  1. 配置Nginx代理缓存,缓存静态资源和API响应
  2. 优化浏览器缓存策略,为不同类型的资源设置不同的缓存时间
  3. 实现基于Redis的多级缓存
  4. 配置缓存命中率日志,分析缓存效果
  5. 实现缓存清除机制,在数据更新时主动清除缓存

延伸阅读


下一章第8章:日志与监控

« 上一篇 动态内容处理 下一篇 » 日志与监控