Nginx功能篇19—代理缓存

概述

前面文章学习到了 Nginx 的静态缓存功能,该功能本质上是用来控制客户端的缓存,相关的指令有:

  • expires 指令
  • add_header 指令

本章将深入到 Nginx 的 代理缓存(Proxy Cache),其本质上也是服务器缓存的一种。

代理缓存:指的是当 Nginx 作为 反向代理 时,将后端服务器返回的频繁请求内容存储在 Nginx 本地的内存或磁盘中,以便在后续请求相同内容时快速响应,而无需再次从后端服务器获取,减少对后端服务器的请求次数,提升性能与用户体验。

提示
一些中文或英文文档会使用 "服务器缓存" 来指代 "代理缓存",但严格来说,"服务器缓存" 是一个宽泛的模糊概念,可能包含代理缓存、FastCGI 缓存、Redis 缓存等。所以在 Nginx 语境下,"代理缓存" 表述更精准。
术语解释
缓存:计算机中非常常见的术语,指的是将‌频繁访问或可能再次访问‌的数据临时存放在‌比原始数据源更快的存储介质‌中(如 CPU 缓存、内存、本地磁盘文件、CDN 节点等),当下次再次访问相同资源时,直接从缓存中读取,从而加快访问速度。缓存在不同文档上下文中可指代 CPU 缓存、内存缓存、磁盘缓存(本地文件缓存)、浏览器缓存、CDN 缓存、DNS 缓存等。

核心工作流程

完整的缓存生命周期包含 6 个关键步骤:

  1. 缓存键生成‌ - 请求到达后,Nginx 根据配置规则生成唯一的缓存键(默认由协议、代理主机名、请求 URI 组合而成)
  2. ‌缓存查找‌ - 用生成的缓存键在共享内存区域中快速检索是否存在对应缓存条目
  3. ‌缓存命中‌ - 找到未过期的有效缓存,直接读取本地缓存内容返回给客户端,完全不访问后端服务器
  4. 缓存未命中‌ - 无有效缓存时,将请求转发给后端服务器获取最新响应
  5. 缓存存储‌ - 收到后端响应后,将符合缓存规则的内容写入指定的缓存目录,同时更新缓存元数据
  6. 缓存失效‌ - 缓存条目超过设置的 TTL 时间,或长时间未被访问,会被自动清理,下次请求触发重新回源

术语与指令

相关术语

如下表所示:

术语 解释 补充说明
缓存键(Cache Key) Nginx 代理缓存中的核心概念之一,其决定了 Nginx 如何识别和存储缓存内容。通俗来说,缓存键是一个唯一的标识符,用于区分不同的缓存条目,当 Nginx 接收到一个请求时,它会根据缓存键来判断是否已经缓存了该请求的响应。如果缓存键匹配,Nginx 会直接返回缓存的内容,而不需要再次处理请求 缓存键由指令 proxy_cache_key 进行控制,默认为 proxy_cache_key $scheme$proxy_host$request_uri;,变量表示的是协议(http 或 https)、代理主机名和请求 URI
共享内存区域(keys_zone) 专门分配的内存块,用于存储所有缓存键的元数据 1MB 内存约可存储 8000 个缓存键,避免每次查找都扫描磁盘
TTL(Time To Live) 缓存条目的有效时长,由 proxy_cache_valid 指令指定 不同 HTTP 状态码可设置不同过期时间
‌inactive 超时 缓存条目在指定时间内未被访问,即使 TTL 未到期也会被删除 防止冷数据长期占用磁盘空间
LRU 淘汰策略 当缓存总大小到达了 max_size 上限时,Nginx 自动清理 最近最少使用 的旧缓存 确保热数据始终留在缓存中
回源 Nginx 主动向后端源站发起新请求、获取最新响应内容并更新本地缓存的完整过程 发生在缓存未命中或缓存失效时
Stale 缓存 已过期的缓存内容。特定条件下 Nginx 仍可将其返回给客户端 配合 proxy_cache_use_stale 指令,提升可用性
"静默刷新"(Background Update) 缓存过期时,Nginx 先返回 Stale 内容给客户端,同时在后台异步回源更新缓存。由 proxy_cache_background_update 指令进行开关控制 常搭配 proxy_cache_use_stale updating、proxy_cache_valid、proxy_cache_lock 一起使用

相关指令

  • proxy_cache 指令 - 启用对应的内存共享区域,例如 proxy_cache my_zone;。通常配置在 location 上下文中

  • proxy_cache_valid 指令 - 设置不同 http 状态码的缓存时间,例如 proxy_cache_valid 200 302 10m; 和 proxy_cache_valid 404 1m;。通常配置在 location 上下文中

  • proxy_cache_use_stale 指令- 在与后端服务器通信时,在哪些情况下可使用过期的缓存响应,例如 proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;。通常配置在 location 上下文中

  • proxy_cache_path 指令 - 代理缓存核心配置,如 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:50m inactive=1h max_size=1g use_temp_path=off;。仅能配置在 http 上下文中

    • /var/cache/nginx - 设置缓存文件所存放的目录,需要提前创建且对应用户有读写权限
    • levels=1:2 - 设置两级的目录结构
    • keys_zone=my_cache:50m - 定义 my_cache 这个共享内存区域并设置 50MB 的大小
    • inactive=1h - 1 小时内未被访问的缓存会自动清理
    • max_size=1g - 缓存的总大小上限
    • use_temp_path=off - 缓存直接写入到目标目录而不需要临时的中转目录
  • proxy_cache_lock 指令 - 是否开启缓存更新锁。当多个客户端同时请求同一个未缓存或已过期的资源时,Nginx 可以通过缓存更新锁避免重复生成缓存内容,从而减轻后端服务器的负载

  • proxy_cache_lock_timeout 指令 - 设置缓存锁的超时时间

  • proxy_cache_background_update 指令 - 是否开启 "静默刷新"

  • proxy_cache_bypass 指令 - 在请求阶段,定义在什么情况下跳过缓存查找过程,直接向后端服务器发起请求

  • proxy_no_cache 指令 - 在响应阶段,定义在什么情况下不会将响应保存到缓存中

  • proxy_cache_revalidate 指令 - 是否使用包含 "If-Modified-Since" 和 "If-None-Match" 的头字段,对已过期的缓存项进行重新验证

  • proxy_pass 指令 - 设置后端服务器的协议(http 协议或 https 协议)和地址(IP 地址或域名),语法为 proxy_pass URL;

  • proxy_cache_key 指令 - 控制缓存键的组合内容,默认为 proxy_cache_key $scheme$proxy_host$request_uri;

  • add_header 指令- 添加标准的或自定义的响应头,如 add_header X-Cache-Status $upstream_cache_status always;,其中 $upstream_cache_status 的取值有以下的值:

    • HIT - 缓存命中,直接返回缓存内容
    • MISS - 缓存未命中,回源更新
    • BYPASS - 匹配了 proxy_cache_bypass 指令定义的指令参数,跳过缓存
    • EXPIRED - 缓存已过期,回源更新
    • UPDATING - 缓存过期但正在后台更新,当前返回 Stale
    • STALE - 返回了过期的 Stale 缓存(后端异常或正在 updating)
    • REVALIDATED - 搭配 proxy_cache_revalidate 指令,确认缓存仍有效
  • proxy_set_header 指令 - 在请求转发给后端服务器之前,对 HTTP 请求头进行添加、修改或删除,语法为 proxy_set_header field value;

配置示例

重要提示
当 Nginx 作为反向代理且启用了代理缓存时,**务必确保后端服务器不启用 gzip 压缩**(或 Nginx 通过 Accept-Encoding 请求头告知后端不压缩)

代理缓存通用配置:

Shell > mkdir -p /var/cache/nginx/

Shell > chown nginx:nginx /var/cache/nginx/ && chmod 755 /var/cache/nginx/
...
events {
    ...
}

http {
    ...
    # 代理缓存核心配置
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:50m inactive=1h max_size=1g use_temp_path=off;
    ...

    server {
        listen 192.168.100.20:80;
        server_name www.my-tests.com;
        return 301 https://www.my-tests.com$request_uri;
    }

    server {
        listen 192.168.100.20:443 ssl http2 backlog=8000 reuseport;
        server_name www.my-tests.com;

        # SSL/TLS 证书相关的配置
        ssl_certificate /usr/local/nginx/ssl/fullchain.pem;
        ssl_certificate_key /usr/local/nginx/ssl/my_privkey.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 15m;
        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout 5s;

        # 在代理层统一设置压缩响应
        gzip on;
        gzip_vary on;
        gzip_buffers 32 4K;
        gzip_comp_level 5;
        gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/xhtml+xml image/svg+xml application/x-font-ttf application/x-font-opentype application/vnd.ms-fontobject font/woff;
        gzip_min_length 256;

        location / {
            ...
            proxy_cache my_cache;

            # 这里的指令参数使用的是 "$host" 而不是 "$proxy_host",主要是避免了重新缓存
            proxy_cache_key "$scheme$host$request_uri";

            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_valid 500 502 503 504 10s;

            proxy_cache_lock on;
            proxy_cache_lock_timeout 5s;
            proxy_cache_background_update on;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

            # 添加自定义的调试响应头
            add_header X-Cache-Status $upstream_cache_status always;

            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 Accept-Encoding "";  # 禁止后端压缩

            # 指向的是单台后端服务器,也可以指向一组服务器,只不过我们还没有学习代理相关的内容
            proxy_pass https://192.168.100.30;
        }
    }
}

代理缓存清理

代理缓存自动清理的依据是:

  • TTL(Time To Live)
  • inactive 超时
  • LRU 淘汰策略

除了自动清理之外,您还可以手动清理,如:

# 清理全部缓存(生产环境中谨慎使用)
Shell > rm -rf /var/cache/nginx/*

调试与验证

# 第一次请求,预期 MISS
Shell > curl -I https://www.my-tests.com/

# 第二次请求,预期是 HIT
Shell > curl -I https://www.my-tests.com/

# 带 Cookie 请求(预期 BYPASS,如果配置了 "proxy_cache_bypass $cookie_sessionid")
Shell > curl -I -H "Cookie: sessionid=abc123" https://www.my-tests.com/

# 调试时注意响应头的内容
...
HTTP/2 200
Server: nginx
X-Cache-Status: HIT
Content-Type: text/html
...

# 查看缓存目录的结构
Shell > tree /var/cacahe/nginx/

常见问题与排错

Q:缓存始终 MISS 的原因有哪些?

大概有两种:

  • 后端响应头包含 Set-Cookie - 在 Nginx 主配置文件中配置 proxy_ignore_headers Set-Cookie;
  • 后端返回 Cache-Control: no-cache - 在 Nginx 主配置文件中配置 proxy_ignore_headers Cache-Control;
Avatar photo

关于 陸風睿

GNU/Linux 从业者、开源爱好者、技术钻研者,撰写文档既是兴趣也是工作内容之一。Q - "281957576";WeChat - "jiulongxiaotianci",Github - https://github.com/jimcat8
用一杯咖啡支持我们,我们的每一篇[文档]都经过实际操作和精心打磨,而不是简单地从网上复制粘贴。期间投入了大量心血,只为能够真正帮助到您。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇