一、概述
1.1 背景介绍
在2024年初,我曾负责一个内容平台项目,该平台使用PHP渲染页面,平均响应时间高达800ms。每当流量高峰期,后端服务就会频繁告警。即便增加两台服务器,问题依旧存在,因为瓶颈在于数据库查询和页面渲染,横向扩展无法解决根本问题。
后来,我花费一天时间在Nginx层配置了proxy_cache,同样的页面响应时间直接降至5ms以内,后端请求量下降了90%。这就是缓存的威力——将重复的计算结果存储起来,下次请求时直接返回,无需重新计算。
Nginx的proxy_cache是一种反向代理缓存,位于客户端和后端服务之间。当请求首次到达时,Nginx将其转发给后端,获取响应后存储一份在本地磁盘或内存中;后续相同的请求则直接从缓存返回,不再打扰后端。这个机制非常适合所有读多写少的场景,例如新闻资讯、商品详情页和API数据接口。
1.2 技术特点
Nginx 的proxy_cache模块具有以下核心优势:
- 性能极高:缓存存储在本地,没有网络开销,响应时间可达毫秒级。
- 配置灵活:可以根据URL、请求头、Cookie等多维度精细控制缓存策略。
- 节省带宽:支持条件请求(返回304 Not Modified),减少重复数据传输。
- 分级存储:支持内存与磁盘混合存储,热点数据放内存,长尾数据放磁盘。
- 缓存验证:支持后端验证(proxy_cache_revalidate),确保数据新鲜度。
- 原生支持:作为标准模块,无需额外安装。
与Redis或Memcached这类应用层缓存相比,proxy_cache的特点是离用户更近(位于Nginx层),延迟更低,但它不支持分布式缓存(每台Nginx服务器独立维护缓存)。因此,它非常适合单机部署或与CDN配合使用。
1.3 适用场景
| 场景 |
缓存策略 |
效果 |
| 静态页面 |
长时间缓存 + ETag |
响应时间 < 1ms |
| API接口 |
短时间缓存 + vary头 |
减少后端80%请求 |
| 图片/视频 |
长时间缓存 + sendfile |
节省带宽和CPU |
| 商品详情 |
分钟级缓存 + 主动刷新 |
平衡实时性与性能 |
| 搜索结果 |
参数化缓存 |
相同搜索词秒级返回 |
| 用户Feed |
不缓存或私有缓存 |
保证内容个性化 |
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| Nginx |
1.26.x / 1.27.x |
稳定版或主线版均可 |
| 操作系统 |
Rocky Linux 9 / Ubuntu 24.04 LTS |
本文示例基于Rocky Linux 9 |
| 磁盘 |
强烈推荐SSD |
缓存读写性能的关键 |
| 内存 |
建议8GB以上 |
用于将热点缓存存放在tmpfs |
| ngx_cache_purge |
可选 |
实现主动清除缓存时需要 |
二、详细步骤
2.1 准备工作
2.1.1 创建缓存目录
# 创建缓存主目录
mkdir -p /var/cache/nginx/proxy_cache
# 如需使用内存缓存(推荐用于热点数据)
mkdir -p /var/cache/nginx/memory_cache
mount -t tmpfs -o size=2G tmpfs /var/cache/nginx/memory_cache
# 设置持久化挂载(可选)
echo "tmpfs /var/cache/nginx/memory_cache tmpfs size=2G,mode=0755 0 0" >> /etc/fstab
# 设置权限
chown -R nginx:nginx /var/cache/nginx
chmod -R 755 /var/cache/nginx
2.1.2 检查磁盘性能
# 测试磁盘写入速度(缓存性能瓶颈通常在这里)
dd if=/dev/zero of=/var/cache/nginx/test.bin bs=1M count=1024 conv=fdatasync
# SSD建议:> 200MB/s
# HDD建议:> 50MB/s
# 如果低于这个值,考虑使用内存缓存或更换磁盘
# 清理测试文件
rm -f /var/cache/nginx/test.bin
2.1.3 规划缓存容量
# 计算缓存大小规划:
#
# 假设:
# - 平均响应大小:50KB
# - 预计缓存10万个URL
# - 所需空间:50KB × 100,000 = 5GB
#
# 建议配置:
# - max_size:略大于预计使用量,比如8GB
# - inactive:根据内容更新频率,通常1-7天
# - keys_zone:每1MB大约存储8000个key
# 查看磁盘空间
df -h /var/cache/nginx
2.2 核心配置
2.2.1 定义缓存区域
在 http 块中定义缓存路径和参数:
# /etc/nginx/nginx.conf
http {
# 磁盘缓存区域定义
# path: 缓存文件存储路径
# levels: 目录层级,1:2表示一级目录1个字符,二级目录2个字符
# keys_zone: 缓存索引存放在共享内存,名称:大小
# max_size: 缓存文件最大占用磁盘空间
# inactive: 多久没有被访问就删除
# use_temp_path: 是否使用临时目录,off表示直接写入缓存目录,减少IO
proxy_cache_path /var/cache/nginx/proxy_cache
levels=1:2
keys_zone=my_cache:100m
max_size=10g
inactive=7d
use_temp_path=off;
# 内存缓存区域(用于热点数据)
proxy_cache_path /var/cache/nginx/memory_cache
levels=1:2
keys_zone=hot_cache:50m
max_size=2g
inactive=1h
use_temp_path=off;
# API专用缓存区域(较短过期时间)
proxy_cache_path /var/cache/nginx/api_cache
levels=1:2
keys_zone=api_cache:50m
max_size=5g
inactive=10m
use_temp_path=off;
# ... 其他配置
}
levels参数详解:
# levels=1:2 表示:
# URL: http://example.com/path/to/resource
# MD5: d41d8cd98f00b204e9800998ecf8427e
# 存储路径: /var/cache/nginx/proxy_cache/e/27/d41d8cd98f00b204e9800998ecf8427e
#
^ ^
# | |__ 二级目录(2个字符)
# |____ 一级目录(1个字符)
#
# 为什么要分级?
# 避免单目录下文件过多导致文件系统性能下降
# ext4单目录超过10万文件性能会明显下降
2.2.2 配置缓存规则
# /etc/nginx/conf.d/cache.example.com.conf
# 定义缓存key
# 默认是 $scheme$proxy_host$request_uri
# 可以根据需要自定义,比如忽略某些参数
proxy_cache_key "$scheme$host$request_uri";
# 根据响应码设置不同缓存时间
proxy_cache_valid 200 301 302 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 10s; # 其他状态码
server {
listen 80;
server_name example.com;
# 启用缓存
proxy_cache my_cache;
# 缓存锁,防止缓存击穿(同一时刻只有一个请求穿透到后端)
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_lock_age 5s;
# 使用过期缓存的场景
# error: 后端错误时返回旧缓存
# timeout: 后端超时时返回旧缓存
# updating: 正在更新缓存时返回旧缓存
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# 后台异步更新缓存
proxy_cache_background_update on;
# 请求N次后才缓存(过滤一次性请求)
proxy_cache_min_uses 2;
# 添加缓存状态响应头(调试用)
add_header X-Cache-Status $upstream_cache_status;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 这个location使用默认缓存配置
}
# API接口使用专用缓存
location /api/ {
proxy_cache api_cache;
proxy_cache_valid 200 1m; # API缓存1分钟
# 不同参数缓存不同内容
proxy_cache_key "$scheme$host$request_uri";
# 某些情况不缓存
proxy_cache_bypass $http_authorization; # 有认证头不走缓存
proxy_no_cache $http_authorization; # 有认证头不存缓存
proxy_pass http://api_backend;
}
# 用户相关接口不缓存
location /api/user/ {
proxy_cache off;
proxy_pass http://api_backend;
}
# 静态资源长时间缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|woff|ttf)$ {
proxy_cache my_cache;
proxy_cache_valid 200 30d;
expires 30d;
proxy_pass http://static_backend;
}
}
2.2.3 缓存状态说明
$upstream_cache_status 变量的可能值:
| 状态 |
说明 |
| MISS |
缓存未命中,请求转发到后端 |
| HIT |
缓存命中,直接返回缓存内容 |
| EXPIRED |
缓存已过期,请求转发到后端更新 |
| STALE |
返回过期的缓存(后端不可用时) |
| UPDATING |
缓存正在更新,返回旧缓存 |
| REVALIDATED |
缓存验证通过,内容未变化 |
| BYPASS |
绕过缓存(proxy_cache_bypass命中) |
2.3 启动和验证
2.3.1 配置检查
# 检查配置语法
nginx -t
# 查看缓存相关配置
nginx -T | grep -E "proxy_cache|cache_path"
2.3.2 重载配置
# 平滑重载
nginx -s reload
# 查看缓存目录是否正常创建
ls -la /var/cache/nginx/
2.3.3 验证缓存效果
# 第一次请求(应该是MISS)
curl -I http://example.com/api/test
# 观察 X-Cache-Status: MISS
# 第二次请求(如果配置了min_uses=2,还是MISS)
curl -I http://example.com/api/test
# 第三次请求(应该是HIT)
curl -I http://example.com/api/test
# 观察 X-Cache-Status: HIT
# 查看缓存文件
find /var/cache/nginx/proxy_cache -type f | head -10
# 查看缓存文件内容(调试)
head -50 /var/cache/nginx/proxy_cache/e/27/xxx
2.3.4 性能测试对比
# 不走缓存的性能
ab -n 1000 -c 50 -H "Cache-Control: no-cache" http://example.com/api/test
# 走缓存的性能
ab -n 1000 -c 50 http://example.com/api/test
# 对比两次测试的:
# - Requests per second(每秒请求数)
# - Time per request(平均响应时间)
#
# 正常情况下,缓存命中的QPS应该是未命中的10-100倍
三、示例代码和配置
3.1 完整配置示例
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式(包含缓存状态)
log_format cache_log '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'cache:$upstream_cache_status '
'rt:$request_time upt:$upstream_response_time';
access_log /var/log/nginx/access.log cache_log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml;
# ==================== 缓存配置开始 ====================
# 主缓存区域(通用内容)
proxy_cache_path /var/cache/nginx/main_cache
levels=1:2
keys_zone=main_cache:100m
max_size=20g
inactive=7d
use_temp_path=off;
# 内存缓存区域(热点内容)
proxy_cache_path /var/cache/nginx/hot_cache
levels=1:2
keys_zone=hot_cache:50m
max_size=2g
inactive=1h
use_temp_path=off;
# API缓存区域
proxy_cache_path /var/cache/nginx/api_cache
levels=1:2
keys_zone=api_cache:50m
max_size=5g
inactive=10m
use_temp_path=off;
# 静态资源缓存区域
proxy_cache_path /var/cache/nginx/static_cache
levels=1:2
keys_zone=static_cache:100m
max_size=50g
inactive=30d
use_temp_path=off;
# 全局缓存设置
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
# ==================== 缓存配置结束 ====================
# 后端服务器组
upstream backend {
least_conn;
server 10.0.0.11:8080 weight=5;
server 10.0.0.12:8080 weight=5;
keepalive 100;
}
upstream api_backend {
least_conn;
server 10.0.0.21:8080;
server 10.0.0.22:8080;
keepalive 50;
}
upstream static_backend {
server 10.0.0.31:80;
server 10.0.0.32:80;
keepalive 100;
}
include /etc/nginx/conf.d/*.conf;
}
# /etc/nginx/conf.d/www.example.com.conf
# 定义不缓存的条件
map $request_uri $skip_cache {
default 0;
~*/admin/ 1; # 后台不缓存
~*/api/user/ 1; # 用户接口不缓存
~*/api/cart/ 1; # 购物车不缓存
~*/api/order/ 1; # 订单不缓存
}
# 根据Cookie判断是否跳过缓存
map $http_cookie $skip_cache_cookie {
default 0;
~*session_id 1; # 有session不缓存
~*user_token 1; # 有token不缓存
}
server {
listen 80;
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 缓存调试头
add_header X-Cache-Status $upstream_cache_status always;
add_header X-Cache-Key $scheme$host$request_uri always;
# 根路径
location / {
proxy_cache main_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_min_uses 1;
# 跳过缓存条件
set $no_cache 0;
if ($skip_cache) {
set $no_cache 1;
}
if ($skip_cache_cookie) {
set $no_cache 1;
}
proxy_cache_bypass $no_cache;
proxy_no_cache $no_cache;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# 热门页面(使用内存缓存)
location ~ ^/(hot|trending|popular) {
proxy_cache hot_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 5m;
proxy_cache_min_uses 1;
# 热点内容即使过期也返回
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 公开API(带缓存)
location /api/public/ {
proxy_cache api_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 2m;
proxy_cache_valid 404 30s;
# 尊重后端的Cache-Control头
proxy_cache_valid any 0;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 需要认证的API(不缓存)
location /api/ {
proxy_cache off;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
# 静态资源(长期缓存)
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
proxy_cache static_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 30d;
proxy_cache_min_uses 1;
# 浏览器缓存
expires 30d;
add_header Cache-Control "public, immutable";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://static_backend;
proxy_set_header Host $host;
}
location ~* \.(css|js|woff2|woff|ttf|eot)$ {
proxy_cache static_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 7d;
expires 7d;
add_header Cache-Control "public";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://static_backend;
proxy_set_header Host $host;
}
# 视频/大文件(缓存+限速)
location ~* \.(mp4|avi|mkv|zip|tar\.gz)$ {
proxy_cache static_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 30d;
proxy_cache_min_uses 3; # 访问3次才缓存
# 限速,防止带宽被占满
limit_rate 2m;
limit_rate_after 10m; # 前10MB不限速
proxy_pass http://static_backend;
proxy_set_header Host $host;
}
# 健康检查(不缓存不记日志)
location /health {
access_log off;
proxy_cache off;
proxy_pass http://backend;
}
# 缓存清理接口(需要配合ngx_cache_purge模块)
location ~ /purge(/.*) {
# 限制访问
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
proxy_cache_purge main_cache "$scheme$host$1";
}
}
3.2 实际应用案例
案例一:新闻资讯站点缓存策略
新闻站点的特点:首页更新频繁,文章页面发布后很少修改,图片永远不变。
# /etc/nginx/conf.d/news.example.com.conf
server {
listen 80;
server_name news.example.com;
# 首页(短缓存+及时更新)
location = / {
proxy_cache hot_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 1m; # 只缓存1分钟
# 缓存正在更新时返回旧内容,保证用户体验
proxy_cache_use_stale updating;
proxy_cache_background_update on;
proxy_pass http://backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 文章列表页(较短缓存)
location ~ ^/category/ {
proxy_cache main_cache;
proxy_cache_key "$host$request_uri$arg_page"; # 带分页参数
proxy_cache_valid 200 5m;
proxy_pass http://backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 文章详情页(较长缓存)
location ~ ^/article/\d+ {
proxy_cache main_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 1h; # 缓存1小时
# 支持条件请求,减少传输
proxy_cache_revalidate on;
proxy_pass http://backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 图片永久缓存
location /uploads/ {
proxy_cache static_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 365d;
expires max;
add_header Cache-Control "public, immutable";
proxy_pass http://static_backend;
}
}
案例二:电商商品缓存策略
电商的挑战:商品信息要相对实时(库存、价格),但又要承受大流量。
# /etc/nginx/conf.d/shop.example.com.conf
# 定义商品ID提取
map $request_uri $product_id {
~^/product/(\d+) $1;
default "";
}
server {
listen 80;
server_name shop.example.com;
# 商品详情页(HTML部分缓存)
location ~ ^/product/\d+ {
proxy_cache main_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 5m;
# 后台更新商品时主动清缓存
proxy_cache_use_stale updating;
proxy_cache_background_update on;
proxy_pass http://backend;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Product-ID $product_id;
}
# 商品实时数据API(短缓存或不缓存)
location ~ ^/api/product/\d+/realtime {
proxy_cache api_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 10s; # 只缓存10秒
proxy_pass http://api_backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 商品基础信息API(较长缓存)
location ~ ^/api/product/\d+/info {
proxy_cache api_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 10m;
proxy_pass http://api_backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 分类页面(分页缓存)
location ~ ^/category/\d+ {
proxy_cache main_cache;
# 包含排序和分页参数
proxy_cache_key "$host$request_uri$arg_sort$arg_page$arg_pageSize";
proxy_cache_valid 200 10m;
proxy_pass http://backend;
}
# 搜索结果(参数化缓存)
location /search {
proxy_cache api_cache;
# 关键词+筛选条件+分页
proxy_cache_key "$host$uri$arg_q$arg_category$arg_price_min$arg_price_max$arg_page";
proxy_cache_valid 200 5m;
proxy_cache_min_uses 2; # 至少搜2次才缓存
proxy_pass http://api_backend;
}
# 购物车和订单(绝对不缓存)
location ~ ^/(cart|order|checkout) {
proxy_cache off;
proxy_pass http://backend;
}
}
案例三:API网关缓存策略
API网关需要根据不同的接口特性设置不同的缓存策略。
# /etc/nginx/conf.d/api-gateway.conf
# 根据请求方法决定是否缓存
map $request_method $cache_method {
GET 1;
HEAD 1;
default 0;
}
# 根据响应的Cache-Control头决定缓存时间
map $upstream_http_cache_control $custom_cache_time {
default 60s;
~*max-age=(\d+) $1s;
~*no-cache 0;
~*no-store 0;
~*private 0;
}
server {
listen 80;
server_name api.example.com;
# 只缓存GET和HEAD请求
set $no_cache 0;
if ($cache_method = 0) {
set $no_cache 1;
}
# 带Authorization头的不缓存
if ($http_authorization != "") {
set $no_cache 1;
}
# 公开接口
location /v1/public/ {
proxy_cache api_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 5m;
proxy_cache_bypass $no_cache;
proxy_no_cache $no_cache;
# 响应头显示缓存信息
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-TTL $custom_cache_time;
proxy_pass http://api_backend;
}
# 字典/配置类接口(长缓存)
location /v1/config/ {
proxy_cache api_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 1h;
proxy_pass http://api_backend;
add_header X-Cache-Status $upstream_cache_status;
}
# 需要认证的接口(根据用户缓存)
location /v1/user/ {
# 按用户ID缓存(从Header获取)
proxy_cache api_cache;
proxy_cache_key "$host$request_uri$http_x_user_id";
proxy_cache_valid 200 1m;
# 没有用户ID不缓存
if ($http_x_user_id = "") {
set $no_cache 1;
}
proxy_cache_bypass $no_cache;
proxy_no_cache $no_cache;
proxy_pass http://api_backend;
}
# 写接口(POST/PUT/DELETE)
location /v1/ {
# 非GET请求不走上面的location,这里统一处理
proxy_cache off;
proxy_pass http://api_backend;
}
}
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 缓存Key设计原则
# 原则1:Key要包含所有影响响应内容的因素
# 坑:分页参数没加进去,所有页返回相同内容
# 错误
proxy_cache_key "$host$uri";
# 正确
proxy_cache_key "$host$uri$arg_page$arg_pageSize";
# 原则2:Key不要包含无关因素
# 坑:包含了时间戳参数,永远无法命中
# 错误(如果前端加了_t防缓存参数)
proxy_cache_key "$host$request_uri";
# 正确(过滤掉无关参数)
proxy_cache_key "$host$uri$arg_id$arg_type";
# 原则3:Key长度要适中
# 过长的Key会占用更多共享内存
# 默认Key会做MD5,所以实际存储的是32字符的hash
4.1.2 缓存更新策略
# 策略1:TTL过期自动更新
proxy_cache_valid 200 10m; # 10分钟后过期
# 策略2:后台更新(用户无感)
proxy_cache_background_update on; # 过期时后台更新
proxy_cache_use_stale updating; # 更新期间返回旧缓存
# 策略3:主动清理(需要ngx_cache_purge模块)
location ~ /purge(/.*) {
proxy_cache_purge main_cache "$scheme$host$1";
}
# 清理命令:curl http://example.com/purge/api/product/123
# 策略4:根据响应头控制
proxy_cache_revalidate on; # 尊重ETag/Last-Modified
4.1.3 分级缓存架构
# 一级缓存:内存(热点数据)
proxy_cache_path /var/cache/nginx/memory_cache
levels=1:2
keys_zone=level1:50m
max_size=2g
inactive=10m;
# 二级缓存:SSD(温数据)
proxy_cache_path /var/cache/nginx/ssd_cache
levels=1:2
keys_zone=level2:100m
max_size=50g
inactive=1d;
# 三级缓存:HDD(冷数据)
proxy_cache_path /var/cache/nginx/hdd_cache
levels=1:2
keys_zone=level3:200m
max_size=500g
inactive=7d;
# 使用时根据内容热度选择不同缓存
# 热点内容:level1
# 普通内容:level2
# 长尾内容:level3
4.1.4 与CDN配合使用
# Nginx作为源站,配合CDN使用时的配置
server {
listen 80;
server_name origin.example.com;
# 设置合适的Cache-Control头,让CDN缓存
location ~* \.(jpg|png|gif|css|js)$ {
proxy_cache static_cache;
proxy_cache_valid 200 7d;
# CDN缓存7天,浏览器缓存1天
add_header Cache-Control "public, max-age=86400, s-maxage=604800";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
# API响应也可以让CDN缓存
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid 200 5m;
# CDN缓存5分钟
add_header Cache-Control "public, max-age=300";
# 带Vary头,让CDN按这些维度缓存
add_header Vary "Accept-Encoding, Accept-Language";
proxy_pass http://api_backend;
}
}
4.2 注意事项
4.2.1 常见错误
| 错误场景 |
原因 |
解决方案 |
| 缓存永远MISS |
后端返回了Set-Cookie |
添加 proxy_ignore_headers Set-Cookie |
| 缓存永远MISS |
后端返回Cache-Control: private |
添加 proxy_ignore_headers Cache-Control |
| 缓存内容错乱 |
Key设计不当,不同内容用了相同Key |
检查proxy_cache_key包含所有区分因素 |
| 用户看到别人数据 |
用户相关内容被缓存 |
用户接口禁用缓存或按用户ID缓存 |
| 缓存目录爆满 |
max_size设置过小或inactive过长 |
调整参数或增加磁盘 |
| 缓存预热慢 |
min_uses设置过大 |
热点内容设置min_uses=1 |
| 更新不及时 |
缓存时间过长 |
缩短TTL或实现主动刷新 |
4.2.2 安全注意事项
# 1. 防止敏感信息被缓存
# 带Authorization的请求不缓存
proxy_cache_bypass $http_authorization;
proxy_no_cache $http_authorization;
# 带敏感Cookie的不缓存
map $http_cookie $has_sensitive_cookie {
default 0;
~*session 1;
~*token 1;
}
proxy_cache_bypass $has_sensitive_cookie;
proxy_no_cache $has_sensitive_cookie;
# 2. 缓存清理接口要限制访问
location ~ /purge/ {
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
# ...
}
# 3. 不要缓存包含用户信息的响应
# 如果后端在响应中包含用户信息,该接口不应该缓存
# 4. 隐藏后端错误信息
# 使用proxy_cache_use_stale时,后端错误不会暴露给用户
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
4.2.3 性能调优
# 1. 使用sendfile提高静态文件传输效率
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# 2. 开启aio(异步IO)
aio threads;
directio 4m; # 大于4MB的文件使用directio
# 3. 缓存文件打开句柄
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 4. 调整共享内存大小
# keys_zone过小会导致无法存储足够的Key
# 1MB大约存储8000个Key
# 如果有100万URL需要缓存,至少需要125MB
# 5. 使用tmpfs作为热点缓存存储
# mount -t tmpfs -o size=4G tmpfs /var/cache/nginx/hot_cache
# 6. 缓存锁超时设置
proxy_cache_lock on;
proxy_cache_lock_timeout 5s; # 等待锁的超时
proxy_cache_lock_age 5s; # 锁的最大持有时间
五、故障排查和监控
5.1 故障排查
5.1.1 缓存不命中排查
# 步骤1:检查响应头
curl -I http://example.com/api/test
# 查看 X-Cache-Status
# 步骤2:检查是否有跳过缓存的条件
# 在配置中添加调试header
add_header X-Cache-Bypass $no_cache;
add_header X-Cache-Key $scheme$host$request_uri;
# 步骤3:检查后端响应头
curl -I http://backend:8080/api/test
# 看是否有 Cache-Control: no-store/no-cache/private
# 看是否有 Set-Cookie
# 步骤4:查看缓存文件是否生成
find /var/cache/nginx -name "*" -type f -mmin -5 | wc -l
# 如果为0,说明没有新文件生成
# 步骤5:检查错误日志
grep "cache" /var/log/nginx/error.log
5.1.2 缓存文件分析
# 查看缓存文件内容(包含响应头和body)
head -100 /var/cache/nginx/main_cache/a/bc/xxxxx
# 文件结构:
# - 元信息(Key, 创建时间, 过期时间等)
# - HTTP响应头
# - 空行
# - 响应Body
# 解析缓存文件的脚本
#!/bin/bash
# /opt/scripts/parse_cache.sh
CACHE_FILE=$1
echo "=== Cache File: $CACHE_FILE ==="
echo "=== Headers ==="
sed -n '/^KEY:/,/^$/p' $CACHE_FILE
echo "=== Response Headers ==="
sed -n '/^HTTP/,/^$/p' $CACHE_FILE
5.1.3 缓存状态统计
#!/bin/bash
# /opt/scripts/cache_stats.sh
# 从访问日志统计缓存命中率
LOG_FILE="/var/log/nginx/access.log"
echo "=== Cache Statistics ==="
# 统计各状态数量
echo "Status Counts:"
grep -oP 'cache:\K\w+' $LOG_FILE | sort | uniq -c | sort -rn
# 计算命中率
TOTAL=$(grep -c 'cache:' $LOG_FILE)
HIT=$(grep -c 'cache:HIT' $LOG_FILE)
RATE=$(echo "scale=2; $HIT * 100 / $TOTAL" | bc)
echo ""
echo "Total Requests: $TOTAL"
echo "Cache Hits: $HIT"
echo "Hit Rate: $RATE%"
# 按URL统计MISS最多的
echo ""
echo "Top 10 Cache MISS URLs:"
grep 'cache:MISS' $LOG_FILE | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
5.2 性能监控
5.2.1 Prometheus监控指标
# 使用nginx-prometheus-exporter
# 或者自定义metrics
# 添加统计变量
log_format metrics '$upstream_cache_status $request_time $upstream_response_time';
# 使用mtail或其他工具解析日志生成metrics
# 关键指标:
# - nginx_cache_hit_total
# - nginx_cache_miss_total
# - nginx_cache_expired_total
# - nginx_cache_stale_total
# prometheus告警规则示例
groups:
- name: nginx_cache
rules:
- alert: NginxCacheHitRateLow
expr: |
(
sum(rate(nginx_cache_hit_total[5m])) /
sum(rate(nginx_cache_requests_total[5m]))
) < 0.7
for: 10m
labels:
severity: warning
annotations:
summary: "Nginx cache hit rate is below 70%"
5.2.2 缓存磁盘监控
#!/bin/bash
# /opt/scripts/cache_disk_monitor.sh
CACHE_DIR="/var/cache/nginx"
THRESHOLD=80 # 使用率告警阈值
# 检查磁盘使用率
USAGE=$(df $CACHE_DIR | awk 'NR==2 {print $5}' | tr -d '%')
if [ $USAGE -gt $THRESHOLD ]; then
echo "[WARN] Cache disk usage: ${USAGE}%"
# 发送告警
fi
# 统计缓存文件数量
FILE_COUNT=$(find $CACHE_DIR -type f | wc -l)
echo "Cache files: $FILE_COUNT"
# 统计各目录大小
echo "Cache size by directory:"
du -sh $CACHE_DIR/*
5.2.3 实时监控脚本
#!/bin/bash
# /opt/scripts/cache_realtime.sh
# 实时显示缓存状态
while true; do
clear
echo "=== Nginx Cache Realtime Monitor ==="
echo "Time: $(date)"
echo ""
# 最近1分钟缓存状态
echo "Last 1 minute cache status:"
tail -1000 /var/log/nginx/access.log | \
grep -oP 'cache:\K\w+' | \
sort | uniq -c
echo ""
# 缓存目录状态
echo "Cache directory status:"
du -sh /var/cache/nginx/* 2>/dev/null
echo ""
# 当前连接数
echo "Current connections:"
curl -s http://127.0.0.1:8080/nginx_status 2>/dev/null || echo "Status page not available"
sleep 5
done
5.3 备份与恢复
5.3.1 缓存预热
#!/bin/bash
# /opt/scripts/cache_warmup.sh
# 缓存预热脚本
URL_FILE="/opt/scripts/warmup_urls.txt"
CONCURRENT=10
NGINX_HOST="http://localhost"
echo "Starting cache warmup..."
# 并发请求URL列表
cat $URL_FILE | xargs -P $CONCURRENT -I {} curl -s -o /dev/null -w "%{url_effective} -> %{http_code}\n" "$NGINX_HOST{}"
echo "Cache warmup completed."
# 统计预热结果
echo "Warmup statistics:"
curl -I "$NGINX_HOST/api/test" 2>/dev/null | grep "X-Cache-Status"
# /opt/scripts/warmup_urls.txt
# 需要预热的URL列表
/
/hot
/category/1
/category/2
/api/public/config
/api/public/banner
5.3.2 缓存清理
#!/bin/bash
# /opt/scripts/cache_clean.sh
# 缓存清理脚本
CACHE_DIR="/var/cache/nginx"
case "$1" in
all)
# 清理所有缓存
rm -rf $CACHE_DIR/*
echo "All cache cleared."
;;
old)
# 清理7天前的缓存
find $CACHE_DIR -type f -mtime +7 -delete
echo "Old cache (>7 days) cleared."
;;
size)
# 清理到指定大小(保留最新的)
MAX_SIZE=${2:-10G}
# 使用du和find组合清理
while [ $(du -s $CACHE_DIR | awk '{print $1}') -gt $(numfmt --from=iec $MAX_SIZE) ]; do
# 删除最老的文件
find $CACHE_DIR -type f -printf '%T+ %p\n' | sort | head -1000 | awk '{print $2}' | xargs rm -f
done
echo "Cache cleaned to under $MAX_SIZE"
;;
*)
echo "Usage: $0 {all|old|size [MAX_SIZE]}"
exit 1
;;
esac
# 通知Nginx
nginx -s reload
5.3.3 缓存迁移
#!/bin/bash
# /opt/scripts/cache_migrate.sh
# 缓存迁移脚本(迁移到新服务器)
SOURCE_DIR="/var/cache/nginx"
TARGET_HOST="new-nginx-server"
TARGET_DIR="/var/cache/nginx"
# 使用rsync同步缓存文件
rsync -avz --progress $SOURCE_DIR/ $TARGET_HOST:$TARGET_DIR/
# 注意事项:
# 1. 缓存文件包含元信息,可能包含主机名,迁移后可能失效
# 2. 建议迁移后进行缓存预热而不是直接复制文件
# 3. 如果使用了proxy_cache_key包含$host,确保两台服务器域名一致
六、总结
6.1 技术要点回顾
Nginx proxy_cache的核心配置要素:
- 缓存区域定义(proxy_cache_path)
levels:目录层级,避免单目录文件过多
keys_zone:共享内存区域,存储缓存Key索引
max_size:磁盘缓存最大容量
inactive:多久没访问就删除
- 缓存Key设计(proxy_cache_key)
- 包含所有影响响应的因素
- 排除无关参数(如时间戳)
- 合理控制Key长度
- 缓存有效期(proxy_cache_valid)
- 根据内容类型设置不同过期时间
- 静态资源长缓存,动态内容短缓存
- 缓存更新策略
- TTL过期自动更新
- background_update后台更新
- 主动Purge清理
- 异常处理(proxy_cache_use_stale)
6.2 进阶学习方向
- 分布式缓存
- Redis/Memcached作为缓存后端
- 多台Nginx共享缓存
- 缓存一致性处理
- 边缘缓存
- 动态内容缓存
- ESI(Edge Side Includes)
- SSI(Server Side Includes)
- 片段缓存
- 缓存安全
- 缓存投毒攻击防护
- 敏感信息泄露防护
- 缓存穿透/击穿/雪崩
6.3 参考资料
- Nginx官方文档 - ngx_http_proxy_module
- Nginx官方博客 - A Guide to Caching with NGINX
- HTTP Caching - MDN Web Docs
- RFC 7234 - HTTP Caching
掌握Nginx proxy_cache的配置与优化,是提升网站性能、减轻后端压力的关键技能。通过合理的缓存策略,你可以轻松应对高并发场景,确保用户体验。如果你想深入了解运维优化或其他网络系统知识,欢迎在云栈社区交流探讨。
附录
A. 命令速查表
| 命令 |
说明 |
nginx -t |
测试配置语法 |
nginx -T |
显示完整配置 |
nginx -s reload |
平滑重载配置 |
curl -I URL |
查看响应头 |
find /cache -type f | wc -l |
统计缓存文件数 |
du -sh /cache/* |
查看缓存目录大小 |
rm -rf /cache/* |
清空缓存 |
ab -n 1000 -c 50 URL |
压力测试 |
B. 配置参数详解
| 参数 |
默认值 |
说明 |
proxy_cache_path |
无 |
缓存目录和参数定义 |
proxy_cache |
off |
指定使用的缓存区域 |
proxy_cache_key |
$scheme$proxy_host$request_uri |
缓存Key |
proxy_cache_valid |
无 |
不同状态码的缓存时间 |
proxy_cache_min_uses |
1 |
请求N次后才缓存 |
proxy_cache_lock |
off |
缓存锁,防止击穿 |
proxy_cache_lock_timeout |
5s |
等待缓存锁的超时 |
proxy_cache_use_stale |
off |
使用过期缓存的条件 |
proxy_cache_background_update |
off |
后台异步更新 |
proxy_cache_revalidate |
off |
支持条件请求验证 |
proxy_cache_bypass |
无 |
跳过缓存的条件 |
proxy_no_cache |
无 |
不存入缓存的条件 |
proxy_ignore_headers |
无 |
忽略后端的某些头 |
C. 术语表
| 术语 |
解释 |
| Cache Key |
缓存索引键,用于唯一标识一个缓存项 |
| Cache Zone |
缓存区域,由keys_zone定义的共享内存区域 |
| TTL |
Time To Live,缓存有效期 |
| Stale |
过期的缓存内容 |
| Revalidate |
向后端验证缓存是否仍然有效 |
| Purge |
主动清除缓存 |
| Cache Hit |
缓存命中 |
| Cache Miss |
缓存未命中 |
| Cache Bypass |
绕过缓存 |
| Cache Lock |
缓存锁,防止缓存击穿 |
| Conditional Request |
条件请求(If-None-Match, If-Modified-Since) |
| ETag |
实体标签,用于验证资源是否变化 |
| Cache-Control |
HTTP头,控制缓存行为 |
| Vary |
HTTP头,指示按哪些请求头区分缓存 |