找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2145

积分

0

好友

301

主题
发表于 昨天 12:58 | 查看: 7| 回复: 0

一、概述

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的核心配置要素:

  1. 缓存区域定义(proxy_cache_path)
    • levels:目录层级,避免单目录文件过多
    • keys_zone:共享内存区域,存储缓存Key索引
    • max_size:磁盘缓存最大容量
    • inactive:多久没访问就删除
  2. 缓存Key设计(proxy_cache_key)
    • 包含所有影响响应的因素
    • 排除无关参数(如时间戳)
    • 合理控制Key长度
  3. 缓存有效期(proxy_cache_valid)
    • 根据内容类型设置不同过期时间
    • 静态资源长缓存,动态内容短缓存
  4. 缓存更新策略
    • TTL过期自动更新
    • background_update后台更新
    • 主动Purge清理
  5. 异常处理(proxy_cache_use_stale)
    • 后端故障时返回旧缓存
    • 保证服务可用性

6.2 进阶学习方向

  1. 分布式缓存
    • Redis/Memcached作为缓存后端
    • 多台Nginx共享缓存
    • 缓存一致性处理
  2. 边缘缓存
    • CDN配置和优化
    • 多级缓存架构
    • 缓存失效传播
  3. 动态内容缓存
    • ESI(Edge Side Includes)
    • SSI(Server Side Includes)
    • 片段缓存
  4. 缓存安全
    • 缓存投毒攻击防护
    • 敏感信息泄露防护
    • 缓存穿透/击穿/雪崩

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头,指示按哪些请求头区分缓存



上一篇:Open-AutoGLM 部署运行笔记
下一篇:程序员技术绘图指南:Excalidraw、Draw.io等四款工具实测与技巧分享
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 20:16 , Processed in 0.479698 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表