系统运维 / 网站服务

pre-caching:让网站缓存提前就绪

浅时光博客 · 11月7日 · 2026年 · · 4,085 字 · 约 14 分钟 122次已读
AI 文章摘要 IT 智汇坊
pre-caching是一个开源工具,旨在提前加载网站缓存,以减少新页面发布、旧缓存清理或缓存过期后的首次访问延迟。它通过主动发出GET请求,模拟用户访问,触发CDN或Nginx的缓存机制,并通过响应头和复检结果确认缓存状态。适用于内容更新后预热、缓存清理后重建、定期增量预热和缓存规则排查场景。工作原理基于并发控制、QPS限制和Sitemap支持,不执行JavaScript,不自动下载资源,受请求速度和压力保护约束。部署需安装Go和Git,构建二进制文件,配置参数包括并发数、超时和复检规则。与Nginx缓存结合时,无需额外插件,直接使用Nginx模块或第三方清理工具。更新内容后,需执行清理、预热和复检流程,输出JSON报告用于监控和留档。

网站开启缓存后,仍有一类访问容易变慢:新页面发布、旧缓存清理,或者缓存过期后的第一次请求。此时,反向代理通常还没有可用的页面副本,需要回源生成内容。

pre-caching 的用途,就是把这次请求提前完成。它读取网站 Sitemap,按设定的速度访问页面,触发 CDN 或 Nginx 的缓存机制,再通过响应头和复检结果确认缓存状态。对于博客、文档站和其他公开内容站点,可以把它放进发布流程或定时任务,让页面在访客到来前尽量准备好。

项目地址:dqzboy/pre-caching

一、项目解决什么问题


Nginx 开启页面缓存后,一般会经历以下过程:

pre-caching_00

pre-caching 主动发出这些请求,把部分缓存未命中的回源工作安排到发布后或低峰期。实际收益取决于源站耗时、缓存规则、缓存容量,以及访客访问的 URL 和预热 URL 是否一致,不能据此承诺固定的加速倍数。

它适合以下场景:

  • 发布后预热:内容或站点版本更新后,提前访问相关页面。
  • 缓存清理后重建:清除旧缓存后,重新填充公开页面缓存。
  • 定期增量预热:根据 Sitemap 的 lastmod 筛选近期变更页面。
  • 缓存规则排查:查看哪些页面命中、未命中、绕过缓存,或者缺少缓存状态头。
  • 运行结果留档:输出 JSON 报告,交给已有监控或部署脚本处理。

二、pre-caching 如何工作


pre-caching_01

当前版本支持 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

四、常用参数与使用方式


请求与速度

参数脚本环境变量默认值说明
-sitemapSITEMAP_URL必填Sitemap 或 Sitemap 索引地址
-cacheheaderCACHE_HEADER脚本为 x-cache留空只统计 HTTP 结果,不做缓存分类
-sizeCONCURRENCY1并发 worker 数
-rateRATE1全局 QPS 上限;0 表示不限速
-delayDELAY1000请求完成后的全局最小休息,单位毫秒
-rest-ratioREST_RATIO1非 HIT 请求按耗时休息的倍数,与最小休息取较大值
-timeoutTIMEOUT10单请求超时,单位秒
-retryRETRY1符合条件的失败重试次数;超时不重试
-verifyVERIFY_SSLfalse / 0是否验证 HTTPS 证书
  • 以上参数和默认值来自当前源码及启动脚本;动态站可以先保留默认速度,根据实际 CPU、PHP-FPM 队列和响应耗时调整。

筛选、复检与报告

参数脚本环境变量默认值说明
-limitLIMIT0只处理前 N 页;0 为全部
-excludeEXCLUDE空逗号分隔,按完整 URL 的子串匹配,并非正则
-lastmodLASTMOD0仅处理最近变更页面,如 24h;0 为全部
-recheckRECHECK0首轮结束后等待多久再复检,如 5s、1m
-jsonJSON_OUT空JSON 报告路径
-logLOG_FILE程序或脚本目录下的 pre-cache.log传 none 关闭程序文件日志
-max-error-rateMAX_ERROR_RATE0服务不可用率阈值,单位为百分比;0 关闭

lastmod、recheck 等时长使用 Go 的 duration 格式;一天写成 24h。Sitemap 缺少有效 lastmod 的页面仍会请求,因此增量预热是否能减少工作量,取决于 Sitemap 是否提供可靠的修改时间。

压力保护

参数脚本环境变量默认值说明
-max-latencyMAX_LATENCY3s达到慢响应阈值后冷却;默认启用首次超时保护
-cooldownCOOLDOWN10s慢响应后的全局冷却时间
-max-slowMAX_SLOW3连续慢响应的中止次数
-max-failMAX_FAIL3连续失败的中止次数,重试尝试也计数
-slow-ratioSLOW_RATIO3平均耗时超过基线倍数后降低并发
-slow-thresholdSLOW_THRESHOLD2s慢页面榜单阈值,仅影响展示

默认保护开启时,首次请求超时即可中止后续工作。客户端超时后,源站可能仍在处理该请求;遇到保护中止,应先定位慢页面和源站瓶颈,再决定是否重跑。1

当前版本还会观察超时页面:同一 URL 连续 3 个自然日超时后才加入 slow-pages.txt,后续运行自动排除;同一天反复超时只算一天。可通过 NO_LEARN=1 关闭自动学习。

五、如何结合 Nginx 缓存使用


1、先确定缓存所在的位置

  • 常说的“Nginx 缓存插件”,可能对应不同组件:
站点结构或需求对应组件作用
Nginx 反向代理 Node.js、Go、Java 等 HTTP 服务proxy_cache缓存上游 HTTP 响应
Nginx 直接连接 PHP-FPM,例如 WordPressfastcgi_cache缓存 FastCGI 页面响应
需要按 URL 清理旧缓存第三方 ngx_cache_purge 或已有清理工具删除指定缓存项
主动填充缓存、检查状态pre-caching发起页面请求并复检

proxy_cache 与 fastcgi_cache 是对应 Nginx 模块提供的功能,不需要为了预热而额外安装第三方缓存插件;实际安装包是否包含所需模块,可用 nginx -V 查看构建信息。ngx_cache_purge 属于另行安装的第三方模块,不能假设所有 Nginx 都支持它的清理指令。

下面两种方案按实际架构选择一种。对于已有站点,应合并进当前配置,保留已有证书、重定向、日志和路由设置。示例用 HTTP 展示缓存配置;生产站点的缓存规则应加在真正处理 HTTPS 内容请求的 server / 文章来源(Source):浅时光博客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 可能只会命中旧内容。因此,发布流程可以按以下顺序组织:

pre-caching_03

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
0 条回应

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