网站开启缓存后,仍有一类访问容易变慢:新页面发布、旧缓存清理,或者缓存过期后的第一次请求。此时,反向代理通常还没有可用的页面副本,需要回源生成内容。
pre-caching 的用途,就是把这次请求提前完成。它读取网站 Sitemap,按设定的速度访问页面,触发 CDN 或 Nginx 的缓存机制,再通过响应头和复检结果确认缓存状态。对于博客、文档站和其他公开内容站点,可以把它放进发布流程或定时任务,让页面在访客到来前尽量准备好。
项目地址:dqzboy/pre-caching
一、项目解决什么问题
Nginx 开启页面缓存后,一般会经历以下过程:

pre-caching 主动发出这些请求,把部分缓存未命中的回源工作安排到发布后或低峰期。实际收益取决于源站耗时、缓存规则、缓存容量,以及访客访问的 URL 和预热 URL 是否一致,不能据此承诺固定的加速倍数。
它适合以下场景:
- 发布后预热:内容或站点版本更新后,提前访问相关页面。
- 缓存清理后重建:清除旧缓存后,重新填充公开页面缓存。
- 定期增量预热:根据 Sitemap 的
lastmod筛选近期变更页面。 - 缓存规则排查:查看哪些页面命中、未命中、绕过缓存,或者缺少缓存状态头。
- 运行结果留档:输出 JSON 报告,交给已有监控或部署脚本处理。
二、pre-caching 如何工作

当前版本支持 Sitemap 索引和 gzip;XML 解析失败时会尝试正则提取。默认使用 GET,并读取完整响应体。它不会像浏览器一样执行 JavaScript,也不会自动下载 HTML 引用的全部 CSS、图片和脚本;预热范围由 Sitemap 中的页面 URL 决定。
请求速度同时受并发数、全局 QPS、请求后休息和压力保护约束。默认是单并发、最高 1 QPS,每次请求完成后至少休息 1 秒;非 HIT 请求还会按耗时增加休息。因此,RATE=1 表示上限,实际速度可能更低。
pre-caching 通过访问触发已有的缓存规则。页面能否写入缓存,仍由 Nginx、CDN 和源站响应决定。项目本身不提供自动清理旧缓存的功能。
三、构建与部署
1、获取源码并构建
- 在已安装 Go 和 Git 的开发机执行:
git clone https://github.com/dqzboy/pre-caching.git
cd pre-caching
chmod +x release.sh run_precache.sh
./release.sh
根据菜单选择 Linux amd64、arm64 或全部架构,并输入自行确定的构建版本号。脚本会执行格式检查、go vet、带竞态检测的测试、Shell 语法检查,再构建相应二进制。不要把脚本中的示例版本号当作项目已发布的版本。
2、复制到服务器
ssh user@server 'mkdir -p /opt/pre-caching'
scp dist/pre-caching-amd64 run_precache.sh precache.env.example \
user@server:/opt/pre-caching/
如果当前用户无权在 /opt 下创建目录,可由管理员预先创建并授予执行用户写入权限;也可以统一改成该用户有权限的其他部署路径。arm64 服务器将二进制文件名替换为 pre-caching-arm64
在服务器上初始化配置:
cd /opt/pre-caching
chmod +x pre-caching-amd64 run_precache.sh
cp precache.env.example precache.env
vi precache.env
脚本会在自身目录和 dist/ 中查找相应架构的二进制文件。
3、配置站点
下面是一份适合开始验证的配置;域名、Sitemap 路径和排除规则需要按站点修改:
SITEMAP_URL="https://www.example.com/sitemap.xml"
CACHE_HEADER="x-cache"
VERIFY_SSL=1
CONCURRENCY=1
RATE=1
DELAY=1000
REST_RATIO=1
TIMEOUT=10
RETRY=1
MAX_LATENCY=3s
COOLDOWN=10s
MAX_SLOW=3
MAX_FAIL=3
LASTMOD=0
RECHECK=5s
EXCLUDE="/wp-admin,/wp-login.php,/wp-json,/cart,/checkout,/my-account,/account"
JSON_OUT="/opt/pre-caching/last-report.json"
这里明确开启了 HTTPS 证书验证。项目当前的默认值是关闭校验;生产环境使用有效站点证书时,可设置 VERIFY_SSL=1,直接运行二进制时使用 -verify
四、常用参数与使用方式
请求与速度
| 参数 | 脚本环境变量 | 默认值 | 说明 |
|---|---|---|---|
-sitemap | SITEMAP_URL | 必填 | Sitemap 或 Sitemap 索引地址 |
-cacheheader | CACHE_HEADER | 脚本为 x-cache | 留空只统计 HTTP 结果,不做缓存分类 |
-size | CONCURRENCY | 1 | 并发 worker 数 |
-rate | RATE | 1 | 全局 QPS 上限;0 表示不限速 |
-delay | DELAY | 1000 | 请求完成后的全局最小休息,单位毫秒 |
-rest-ratio | REST_RATIO | 1 | 非 HIT 请求按耗时休息的倍数,与最小休息取较大值 |
-timeout | TIMEOUT | 10 | 单请求超时,单位秒 |
-retry | RETRY | 1 | 符合条件的失败重试次数;超时不重试 |
-verify | VERIFY_SSL | false / 0 | 是否验证 HTTPS 证书 |
- 以上参数和默认值来自当前源码及启动脚本;动态站可以先保留默认速度,根据实际 CPU、PHP-FPM 队列和响应耗时调整。
筛选、复检与报告
| 参数 | 脚本环境变量 | 默认值 | 说明 |
|---|---|---|---|
-limit | LIMIT | 0 | 只处理前 N 页;0 为全部 |
-exclude | EXCLUDE | 空 | 逗号分隔,按完整 URL 的子串匹配,并非正则 |
-lastmod | LASTMOD | 0 | 仅处理最近变更页面,如 24h;0 为全部 |
-recheck | RECHECK | 0 | 首轮结束后等待多久再复检,如 5s、1m |
-json | JSON_OUT | 空 | JSON 报告路径 |
-log | LOG_FILE | 程序或脚本目录下的 pre-cache.log | 传 none 关闭程序文件日志 |
-max-error-rate | MAX_ERROR_RATE | 0 | 服务不可用率阈值,单位为百分比;0 关闭 |
lastmod、recheck 等时长使用 Go 的 duration 格式;一天写成 24h。Sitemap 缺少有效 lastmod 的页面仍会请求,因此增量预热是否能减少工作量,取决于 Sitemap 是否提供可靠的修改时间。
压力保护
| 参数 | 脚本环境变量 | 默认值 | 说明 |
|---|---|---|---|
-max-latency | MAX_LATENCY | 3s | 达到慢响应阈值后冷却;默认启用首次超时保护 |
-cooldown | COOLDOWN | 10s | 慢响应后的全局冷却时间 |
-max-slow | MAX_SLOW | 3 | 连续慢响应的中止次数 |
-max-fail | MAX_FAIL | 3 | 连续失败的中止次数,重试尝试也计数 |
-slow-ratio | SLOW_RATIO | 3 | 平均耗时超过基线倍数后降低并发 |
-slow-threshold | SLOW_THRESHOLD | 2s | 慢页面榜单阈值,仅影响展示 |
默认保护开启时,首次请求超时即可中止后续工作。客户端超时后,源站可能仍在处理该请求;遇到保护中止,应先定位慢页面和源站瓶颈,再决定是否重跑。1
当前版本还会观察超时页面:同一 URL 连续 3 个自然日超时后才加入 slow-pages.txt,后续运行自动排除;同一天反复超时只算一天。可通过 NO_LEARN=1 关闭自动学习。
五、如何结合 Nginx 缓存使用
1、先确定缓存所在的位置
- 常说的“Nginx 缓存插件”,可能对应不同组件:
| 站点结构或需求 | 对应组件 | 作用 |
|---|---|---|
| Nginx 反向代理 Node.js、Go、Java 等 HTTP 服务 | proxy_cache | 缓存上游 HTTP 响应 |
| Nginx 直接连接 PHP-FPM,例如 WordPress | fastcgi_cache | 缓存 FastCGI 页面响应 |
| 需要按 URL 清理旧缓存 | 第三方 ngx_cache_purge 或已有清理工具 | 删除指定缓存项 |
| 主动填充缓存、检查状态 | pre-caching | 发起页面请求并复检 |
proxy_cache 与 fastcgi_cache 是对应 Nginx 模块提供的功能,不需要为了预热而额外安装第三方缓存插件;实际安装包是否包含所需模块,可用 nginx -V 查看构建信息。ngx_cache_purge 属于另行安装的第三方模块,不能假设所有 Nginx 都支持它的清理指令。
下面两种方案按实际架构选择一种。对于已有站点,应合并进当前配置,保留已有证书、重定向、日志和路由设置。示例用 HTTP 展示缓存配置;生产站点的缓存规则应加在真正处理 HTTPS 内容请求的 server / location 中,而非只负责 HTTP 跳转的入口。
2、公共缓存条件
以下条件用于公开内容站点:仅允许 GET/HEAD、没有查询参数、没有 Cookie 和 Authorization,并排除常见后台与账户路径。所有带 Cookie 的请求都绕过缓存,是便于起步的保守策略;如果要允许统计 Cookie 等匿名场景,应根据站点实际规则细分。
这组路径只覆盖常见名称。若站点有自定义个人中心、购物流程、语言或地域个性化入口,需要补充规则;仅在确认响应可以由匿名访客共享时,才应纳入页面缓存。
保存为 /etc/nginx/snippets/nginx-cache-common.conf,由 http {} 加载一次:
# 公共缓存条件:在 http {} 内 include,一份即可。
# 示例按公开、无个性化内容的站点设计;特殊业务请收紧可缓存范围。
map $request_method $pc_skip_method {
default 1;
GET 0;
HEAD 0;
}
map $args $pc_skip_args {
default 1;
"" 0;
}
# 保守策略:所有带 Cookie 的请求均绕过共享页面缓存。
map $http_cookie $pc_skip_cookie {
default 1;
"" 0;
}
map $http_authorization $pc_skip_auth {
default 1;
"" 0;
}
map $request_uri $pc_skip_path {
default 0;
~^/(?:admin|wp-admin|wp-json|api|login|logout|user|account|members|cart|checkout|my-account)(?:/|\?|$) 1;
~^/(?:wp-login|wp-cron|xmlrpc)\.php(?:\?|$) 1;
}
map "$pc_skip_method:$pc_skip_args:$pc_skip_cookie:$pc_skip_auth:$pc_skip_path" $pc_skip_cache {
default 1;
"0:0:0:0:0" 0;
}
其中 proxy_cache_bypass / fastcgi_cache_bypass 用于控制读取缓存,proxy_no_cache / fastcgi_no_cache 用于控制写入缓存;示例同时设置两者。
3、方案 A:Nginx 反向代理缓存
假设应用监听 127.0.0.1:3000,站点域名为 www.example.com。以下文件须由 http {} include;如果主配置已经加载公共 map,就去掉重复的 include:
# 放在由 http {} include 的目录中,例如 /etc/nginx/conf.d/。
# 公共 map 文件需要在 http {} 中加载一次,路径按实际部署修改。
include /etc/nginx/snippets/nginx-cache-common.conf;
proxy_cache_path /var/cache/nginx/precache-proxy
levels=1:2 keys_zone=PRECACHE_PROXY:32m
max_size=2g inactive=60m use_temp_path=off;
upstream precache_app {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://precache_app;
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_cache PRECACHE_PROXY;
proxy_cache_key "$scheme|$host|$request_uri";
proxy_cache_methods GET HEAD;
proxy_cache_min_uses 1;
proxy_cache_valid 200 10m;
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_bypass $pc_skip_cache;
proxy_no_cache $pc_skip_cache $upstream_http_set_cookie;
proxy_hide_header X-Cache;
add_header X-Cache $upstream_cache_status always;
}
}
这里明确设置 proxy_cache_min_uses 1,让满足缓存条件的首次访问即可建立缓存;proxy_cache_lock 可减少同一缓存项在并发未命中时的重复回源。X-Cache 取自 $upstream_cache_status,供预热工具读取。
proxy_cache_valid 200 10m 提供有效期配置,但上游的 X-Accel-Expires、Cache-Control、Expires 等头也会影响缓存行为。Nginx 默认不会缓存带 Set-Cookie 的上游响应;示例也显式阻止这类写入。不要为了把结果变成 HIT 而直接忽略上游的私有缓存或 Cookie 规则。
4、方案 B:PHP / WordPress 的 FastCGI 缓存
Nginx 直接连接 PHP-FPM 时,使用 fastcgi_cache。以下是独立的替代方案,不要与方案 A 的同域名 server 原样同时启用。网站根目录和 PHP-FPM 地址必须按实际部署修改:
# 这是与 proxy_cache 二选一的 PHP/WordPress 站点示例。
# 放在由 http {} include 的目录中,公共 map 只加载一次。
include /etc/nginx/snippets/nginx-cache-common.conf;
fastcgi_cache_path /var/cache/nginx/precache-fastcgi
levels=1:2 keys_zone=PRECACHE_FCGI:32m
max_size=2g inactive=60m use_temp_path=off;
server {
listen 80;
server_name www.example.com;
root /var/www/example;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 按 PHP-FPM 实际监听地址修改,也可使用 Unix socket。
fastcgi_pass 127.0.0.1:9000;
fastcgi_cache PRECACHE_FCGI;
fastcgi_cache_key "$scheme|$request_method|$host|$request_uri";
fastcgi_cache_methods GET HEAD;
fastcgi_cache_min_uses 1;
fastcgi_cache_valid 200 10m;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 10s;
fastcgi_cache_bypass $pc_skip_cache;
fastcgi_no_cache $pc_skip_cache $upstream_http_set_cookie;
fastcgi_hide_header X-Cache;
add_header X-Cache $upstream_cache_status always;
}
}
5、先手工确认 MISS → HIT
使用同一个匿名 GET 请求连续访问公开页面。下面命令会下载并丢弃响应体,只显示响应头:
curl -sS -D - -o /dev/null https://www.example.com/example-page/
curl -sS -D - -o /dev/null https://www.example.com/example-page/
在单层 Nginx、页面可缓存且尚未建缓存的情况下,期望先看到 X-Cache: MISS,随后看到 X-Cache: HIT;如果此前已有有效缓存,两次都可能是 HIT。curl -I 可以用于查看头部,但验证预热时使用 GET 更贴近项目的默认请求方式。
接着运行:
LIMIT=10 RECHECK=5s ./run_precache.sh
观察首轮 MISS / EXPIRED 等页面在复检阶段是否变为 HIT。需要区分“请求完成”和“缓存写入完成”;只有请求结果不足以证明缓存已建立。
六、更新内容后:清理、预热、复检
如果内容已经更新,但 Nginx 中的旧页面缓存仍有效,再访问该 URL 可能只会命中旧内容。因此,发布流程可以按以下顺序组织:

HIT 只能说明命中了缓存,不能证明内容就是刚发布的版本;关键页面可结合页面标识或业务内容进行抽查。
使用第三方 ngx_cache_purge 时,需要另行安装与当前 Nginx 兼容的模块,并按该模块的文档配置清理入口。清理操作必须匹配实际缓存区和完整缓存键;本文两个示例的缓存键不同,不能共用一个未经调整的 purge 模板。清理接口应限制为本机、可信管理网络或经过鉴权的入口。
本文不在预热命令中假设存在某个 purge URL。先使用你现有缓存插件提供的清理方式,再运行:
LASTMOD=24h RECHECK=5s ./run_precache.sh
这只会筛选近 24 小时变更的 Sitemap 页面;文章修改后可能同时影响首页、分类页和归档页。如果这些页面没有准确更新 lastmod,应把它们纳入独立的 Sitemap,或根据业务安排全量预热。
七、如何阅读结果与报告
| 状态 | 含义与后续处理 |
|---|---|
HIT | 本次直接命中缓存 |
MISS | 未直接命中;检查复检结果,确认是否已写入 |
EXPIRED | 原缓存过期;通常需要回源或更新,继续观察复检 |
STALE / UPDATING | 涉及旧缓存或更新过程,程序单独统计并纳入待复检范围 |
REVALIDATED | 缓存重新验证后的状态,程序单独统计 |
BYPASS / DYNAMIC / PASS | 工具归到“不可缓存”类别;检查是否符合预期的绕过规则 |
| 无缓存头 | 确认头名称、请求路由,以及响应由哪一层返回 |
| 404 / HTTP 错误 / 请求异常 | 排查失效链接、访问限制、超时或上游故障 |
程序读取的是服务端返回的状态标记。未知或非标准值也可能被归入“不可缓存”,不能把分类结果当作独立的缓存能力检测。
首轮命中率按 HIT ÷ (HIT + MISS + EXPIRED + STALE + UPDATING + REVALIDATED) 计算,不等于 HIT 占全部 Sitemap 页面的比例。复检只覆盖首轮需要确认的页面;复检命中率是复检 HIT 数除以待复检页数,也不等于全站命中率。
| 退出码 | 含义 |
|---|---|
0 | 正常完成;不表示每个页面都已 HIT |
1 | 健康检查、Sitemap 获取、配置或报告写入等失败 |
3 | 首轮或复检的服务不可用率超过配置阈值 |
4 | 压力保护中止 |
130 | 被信号中断 |
MAX_ERROR_RATE 关注 5xx、429 和网络异常,不把所有 4xx 都计入服务不可用率;监控时还应结合 403、404 等明细和复检结果。
八、定时任务与发布集成
小批量确认稳定后,可通过 crontab -e 配置任务。以下是两个可选示例,可根据站点规模和缓存有效期选择;执行时间按服务器 cron 的时区解释:
# 每小时第 5 分钟:预热最近 2 小时变更的页面
5 * * * * LASTMOD=2h RECHECK=5s /bin/bash /opt/pre-caching/run_precache.sh >> /opt/pre-caching/cron.log 2>&1
# 每天 03:10:全量预热并复检
10 3 * * * LASTMOD=0 RECHECK=5s JSON_OUT=/opt/pre-caching/last-report.json /bin/bash /opt/pre-caching/run_precache.sh >> /opt/pre-caching/cron.log 2>&1





必须 注册 为本站用户, 登录 后才可以发表评论!