使用 Nginx proxy_cache 搭建私有 CDN 边缘节点实战

本文详细讲解如何利用 Nginx 的 proxy_cache 模块搭建属于自己的私有 CDN 边缘节点,涵盖原理、配置、缓存策略、性能调优及多节点部署实战经验,帮助你在不依赖第三方服务的情况下显著提升站点响应速度。

使用 Nginx proxy_cache 搭建私有 CDN 边缘节点实战
封面图:ZuCDN · ZuCDN 原创

为什么需要私有 CDN 边缘节点

网站访问速度直接决定用户体验与搜索引擎排名。虽然公有 CDN 服务便捷、节点丰富,但存在成本不可控、缓存规则不透明、数据隐私风险以及回源流量附加费等痛点。对于内容相对稳定的站点(如静态资源站、文档站、图片站),或对缓存策略有精细控制需求的场景,自建一套基于 Nginx proxy_cache 的私有 CDN 边缘节点,是一种低成本、高可控的加速方案。

私有 CDN 边缘节点通常部署在靠近用户的地理位置(如不同机房、云区域),通过反向代理缓存回源服务器的内容,有效减少跨地域网络延迟和源站压力。Nginx 的 proxy_cache 模块是成熟、稳定的解决方案,完全免费且性能优异,可轻松承载数千 TPS 的缓存命中流量。

Nginx proxy_cache 核心原理

proxy_cache 是 Nginx 内置的缓存模块,工作流程如下:

  1. 客户端请求到达 Nginx 边缘节点
  2. Nginx 根据请求 URI、查询参数、请求头等生成缓存键(cache key)
  3. 在本地缓存存储目录中查找缓存文件
  4. 如果命中且未过期,立即返回缓存内容
  5. 如果未命中或已过期,代理请求回源服务器,同时根据配置缓存响应

整个过程对客户端完全透明,且支持缓存锁定(避免缓存雪崩)、后台刷新、分级缓存等高级特性。

proxy_cache 相关指令解析

  • proxy_cache_path — 定义缓存存储路径、共有缓存区名称、缓存文件层级、最大大小、失效时间等参数,是缓存的全局声明。
  • proxy_cache — 在 location 中引用已声明的缓存区名称,开启缓存功能。
  • proxy_cache_key — 自定义缓存键的生成规则,默认是 $scheme$proxy_host$request_uri,可按需包含查询参数、cookie 等。
  • proxy_cache_valid — 根据响应状态码和缓存时间控制缓存有效期。
  • proxy_cache_use_stale — 在回源失败时允许返回过期缓存,提高可用性。
  • proxy_cache_lock — 当多个相同请求同时回源时,只允许第一个请求回源,后续请求等待缓存写入后直接返回,避免对源站造成冲击。

实战搭建步骤

环境准备与基础配置

假设你已有:

  • 一台运行 CentOS 7+/Ubuntu 20.04+ 的服务器,用于部署 CDN 边缘节点
  • Nginx 版本 >= 1.9.5(推荐最新稳定版)
  • 回源服务器(可以是你的主站或对象存储)

确认 Nginx 已编译 proxy_cache 模块(默认包含)。编辑主配置文件 nginx.conf,在 http 块内添加缓存路径声明:

proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=cache_static:500m max_size=10g inactive=24h use_temp_path=off;

参数说明:

  • levels=1:2 定义缓存文件目录层次,如 /data/nginx/cache/0/3a 这样可避免单目录文件过多
  • keys_zone 指定共享内存区名称及大小,用于缓存键索引,500m 可存储约 50 万个缓存键
  • max_size 限制磁盘缓存总大小,超过后 Nginx 会使用 LRU 算法清理
  • inactive 指定缓存未被访问时的过期时间,超过 24h 即使未到期也会被清理
  • use_temp_path=off 禁止使用临时存储路径,直接将缓存文件写入目标目录,提升性能

配置缓存存储与策略

在 server 或 location 块中启用缓存。假设我们要缓存根路径下所有静态文件,并回源到 http://origin.example.com:

server {
    listen 80;
    server_name cdn.example.com;

    location / {
        proxy_pass http://origin.example.com;
        proxy_cache cache_static;
        proxy_cache_key "$scheme$proxy_host$request_uri";
        proxy_cache_valid 200 301 302 1h;
        proxy_cache_valid 404 5m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
        proxy_cache_lock on;
        proxy_cache_lock_timeout 5s;

        # 重要:传递真实客户端 IP 给源站
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

上述配置实现了对 200、301、302 响应缓存 1 小时,404 缓存 5 分钟。当回源出错或超时时,允许返回过期缓存(use_stale),并启用缓存锁避免同时反复回源。

缓存锁定与过期控制

缓存锁定是防止“缓存惊群”的关键。当某个热点资源首次失效后,大量并发请求到达边缘节点,若没有锁定机制,每个请求都会穿透到源站,导致瞬间流量尖峰。配置 proxy_cache_lock on 后,只有第一个请求能回源,其余请求等待缓存生成。配合 proxy_cache_lock_age(默认 5s)可以防止死锁。此外,可以根据业务特点设置 proxy_cache_lock_timeout(默认 5s),避免锁定时间过长导致客户端等待。

过期控制方面,除了 proxy_cache_valid,还可以利用源站返回的 Cache-Control 头。添加 proxy_ignore_headers Cache-Control Expires; 可强制覆盖源站缓存头,完全由 Nginx 控制。

清除缓存的管理接口

私有 CDN 必须提供缓存刷新能力。Nginx 官方未提供内置管理接口,但可以通过 ngx_cache_purge 模块(需编译添加)或借助 lua 脚本实现。这里介绍使用第三方模块 ngx_cache_purge

# 在 location 中添加
location ~ /purge(/.*) {
    allow 127.0.0.1;
    deny all;
    proxy_cache_purge cache_static $scheme$proxy_host$1;
}

这样请求 http://cdn.example.com/purge/static/image.png 即可清除该文件的缓存。注意用 IP 白名单保护此接口,或加上密钥验证。

若不想编译模块,也可以使用 proxy_cache_bypass 或定时脚本直接删除缓存文件(但风险较高)。

性能调优与注意事项

缓存文件系统选择

Nginx proxy_cache 将每个缓存条目作为独立文件存储。大量小文件的读写对文件系统性能敏感。推荐使用 XFSext4 并调优 inode 数量。在 Ubuntu 上挂载:

mkfs.xfs -f /dev/sdb
mount -o noatime,nodiratime,allocsize=4k /dev/sdb /data/nginx/cache

noatime 禁用访问时间更新,减少写开销;allocsize=4k 提高小文件性能。

内存缓存 vs 磁盘缓存

Nginx 的 proxy_cache 是磁盘缓存,但缓存键索引存储在共享内存(keys_zone)中。如果内存足够,可以开启 proxy_cache_path 中的 loader_filesloader_threshold 控制缓存预热。另一种思路是结合 open_file_cache 减少文件句柄打开次数:

open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

对于极其热的数据,可以考虑使用 proxy_cache_pathmax_size 设为较小值,配合 proxy_cache_key 精细控制,让热点资源常驻磁盘。

缓存一致性保证

私有 CDN 最大挑战之一:源站更新资源后如何通知边缘节点失效?常见方案:

  • 被动过期:通过 proxy_cache_valid 设置合理的 TTL,容忍一定延迟
  • 主动刷新:源站发布后调用 purge 接口清除对应缓存
  • 版本化 URL:在资源链接中加入版本号或 hash,缓存键自动变化,旧版本无需清理

推荐采用“版本化 URL + 被动短过期”模式,兼顾自动化与一致性。

多节点与负载均衡集成

单一边缘节点存在单点故障。可以在不同区域部署多个 Nginx 节点,并使用 DNS 轮询或 Anycast 实现流量分发。如果需要对缓存做全局一致性管理,可以部署独立的缓存协调服务(如基于 Redis 的缓存失效广播)。

在 Nginx 反向代理层,可以使用 upstream 配置多台回源源站做负载均衡:

upstream origin_backend {
    server 192.168.1.10:80 weight=5;
    server 192.168.1.11:80 weight=3;
    keepalive 32;
}

location / {
    proxy_pass http://origin_backend;
    proxy_cache cache_static;
    # ... 其余缓存配置
}

启用 keepalive 连接池能减少与源站的 TCP 握手开销。

总结

使用 Nginx proxy_cache 搭建私有 CDN 边缘节点是一项实践性很强的技术工作。通过合理配置缓存路径、键规则、过期策略、锁定机制以及文件系统调优,完全可以在无第三方 CDN 的情况下实现数十毫秒级别的响应提升。本文涉及的所有配置均可用于生产环境,但需根据实际流量规模和内容类型做适当调整。建议从单节点开始,逐步添加监控(缓存命中率、源站负载等)并优化,最终建成高可用、高性能的私有加速网络。

延伸阅读