Cache::get()是数据缓存,仍需完整框架流程;file_put_contents()生成静态HTML文件可被Nginx直接返回,跳过PHP解析,才是真正静态页面缓存。

Cache::get() 和 file_put_contents() 不是同一类缓存,静态页面缓存必须绕过 PHP 解析阶段,直接由 Web 服务器(如 Nginx)返回 HTML 文件 —— 否则就不是真正意义上的“静态页面缓存”。
为什么不能只用 Cache::get() 做页面缓存
Webman 的 Cache 类(默认基于 Redis 或文件驱动)本质是「数据缓存」,它缓存的是 PHP 变量或序列化结构,每次请求仍需进入框架生命周期:路由匹配 → 中间件 → 控制器 → 视图渲染 → 响应输出。这个过程照常消耗 CPU、内存和数据库连接。
- 即使你把整个
render()结果塞进Cache::set('page_home', $html, 3600),后续请求仍要走一遍控制器逻辑,只是省了一次模板渲染 -
Cache::get()返回字符串后,你还得手动return response($html)->withHeader('Content-Type', 'text/html'),这没解决 IO 瓶颈 - 高并发下,大量请求同时 hit 到同一个缓存 key,若缓存失效,会触发「缓存雪崩」+「模板重复渲染」双重压力
Nginx 层静态 HTML 文件缓存的落地方式
真正的静态页面缓存,是让 Nginx 直接读取磁盘上的 .html 文件并返回,完全跳过 PHP-FPM 和 Webman 进程。关键在于:生成时机、路径映射、缓存刷新。
- 生成路径统一放在
public/static_cache/下,比如public/static_cache/home/index.html,确保该目录可被 Nginx 直接访问 - 在控制器中用
file_put_contents()写入时,注意权限:确保 Webman 进程用户(如 www-data)有写权限,且文件属主与 Nginx 进程一致 - Nginx 配置需增加 location 拦截规则,优先匹配
.html文件存在性:location / { try_files $uri $uri/ @webman; } location @webman { include fastcgi_params; fastcgi_pass 127.0.0.1:9501; } - 不要用
serialize()或var_export()存 HTML 内容,直接写原始字符串:file_put_contents($path, $html, LOCK_EX)
如何避免缓存脏数据和路径冲突
静态文件缓存一旦生成,就脱离框架控制,所以 key 设计必须带业务维度和版本标识,否则更新内容后旧文件残留,用户看到的是过期页。
- 文件名建议含哈希值,例如:
home_v2_'.md5($params_json).'.html,而不是简单拼接home_1.html - 禁止把用户 ID、token 等敏感参数暴露在文件名里,防止缓存探测(比如攻击者遍历
user_1.html、user_2.html) - 删除缓存时,别只删 Redis key,必须同步
unlink($filepath);推荐封装一个clearStaticCache($pattern)方法,用glob()批量清理 - 上线新模板或改版时,务必 bump 版本号(如从
v2改为v3),让旧缓存自然失效,比全量rm -rf更安全
静态缓存 + 请求级缓存组合防穿透
单靠磁盘文件缓存无法应对「缓存未命中 + 高并发重建」场景。比如首页 HTML 缓存过期瞬间,1000 个请求同时发现文件不存在,全部涌向控制器,触发 1000 次模板渲染和数据库查询。
立即学习“PHP免费学习笔记(深入)”;
- 在控制器入口加一层「请求内锁」:
if (context()->has('static_rendering')) { return; },配合context()->set('static_rendering', true)标记 - 首次请求生成 HTML 后,立即写入文件,并广播给其他 Worker(如果用了多进程部署),可用 Redis Pub/Sub 或本地共享内存(
shmop)实现轻量通知 - 更稳妥的做法是预热:在数据变更后(如 CMS 发布文章),主动调用
generateStaticPage('article/'.$id)提前生成,不等用户请求才建



















