香港CN2 GIA服务器部署资讯站,页面缓存与对象缓存如何分层设置
假设一台香港CN2 GIA服务器承载一个以文章详情页、栏目页和首页为主的资讯站,峰值约为每分钟1000次请求,其中70%来自未登录访客。较合适的分层方式是:边缘缓存可选,Nginx页面缓存负责公开HTML,应用对象缓存负责文章数据、栏目列表和查询结果,数据库只处理缓存未命中的请求。文章页、栏目页等公共内容优先使用页面缓存;登录后台、搜索、投稿、评论和带个性化数据的请求必须绕过页面缓存,但仍可使用对象缓存。
在这类部署中,可以先使用约60~180秒的页面缓存、约300~900秒的对象缓存,并通过“更新数据库 → 删除或更新对象缓存 → 使页面缓存失效”的顺序保证内容一致性。香港CN2 GIA服务器提供的是源站访问链路条件,不能代替应用层缓存;如果每次请求仍然执行PHP逻辑和数据库查询,页面响应时间与数据库压力仍会受到影响。
一、先区分四类缓存对象
缓存策略的关键不是缓存越多越好,而是让不同生命周期的数据进入合适的层级。
| 缓存层级 | 主要内容 | 适合场景 | 参考有效期 | 失效方式 |
|---|---|---|---|---|
| 边缘缓存 | 静态文件、公开HTML | 访问来源较分散、重复访问明显 | 30~180秒 | 按URL或标签清理 |
| 页面缓存 | 已渲染的完整HTML | 首页、文章页、栏目页 | 60~300秒 | 发布内容时清理或切换版本号 |
| 对象缓存 | 文章对象、栏目列表、导航、查询结果 | 页面动态渲染、多个页面重复读取同一数据 | 300~1800秒 | 删除指定键或按版本号失效 |
| 数据库 | 未缓存的数据和写入操作 | 后台、低频查询、缓存未命中 | 不适用 | 依靠事务和数据更新逻辑 |
边缘缓存不是必选项。若资讯站访问量还不大,先把源站页面缓存和对象缓存配置正确,通常更容易定位问题。页面缓存和对象缓存也不能相互替代:

- 页面缓存命中时,请求可能直接返回HTML,PHP和数据库都不必执行。
- 页面缓存未命中时,对象缓存可以减少PHP到数据库的查询次数。
- 页面中包含登录状态、用户权限或实时数据时,不能把完整页面放进公共页面缓存,只能缓存其中的公共对象或片段。
二、用一组参考数据判断分层价值
下面是一组估算场景,不代表某台香港CN2 GIA服务器的实测结果:
- 峰值请求量:1000次/分钟;
- 公开文章、栏目和首页请求:700次/分钟;
- 静态资源请求:200次/分钟;
- 后台、搜索、接口和其他动态请求:100次/分钟;
- 未启用页面缓存时,约800次/分钟会进入PHP应用;
- 单次动态页面平均触发6次数据库读取。
如果页面缓存命中率达到75%,700次公开页面请求中只有约175次需要进入PHP。假设对象缓存命中率为85%,每次页面生成仍然需要读取的数据库对象会明显减少,数据库不再承受所有页面请求的完整查询链路。
可以比较三种方案:
| 方案 | PHP请求压力 | 数据库压力 | 内容新鲜度控制 | 适用性 |
|---|---|---|---|---|
| 只使用页面缓存 | 公开页面压力低 | 页面未命中时仍可能较高 | 依赖页面清理 | 内容结构简单、公开访问为主 |
| 只使用对象缓存 | 所有页面仍需渲染 | 中等 | 细粒度较好 | 个性化页面较多 |
| 页面缓存+对象缓存 | 公开页面最低 | 页面未命中时仍较低 | 需要设计失效顺序 | 多数资讯站的默认方案 |
需要注意,命中率不是越高越好。如果为了提高命中率,把带用户信息的页面也缓存起来,可能出现权限错乱或隐私数据泄露。因此,公开内容优先追求页面缓存命中率,动态内容优先追求对象缓存命中率,二者分别设置边界。
三、部署前的准备条件
以下示例以常见的Linux服务器、Nginx、PHP-FPM和Redis对象缓存为前提。不同发行版的配置路径和PHP-FPM套接字名称可能不同,先确认实际环境:
nginx -v
php -v
ls -l /run/php/ 2>/dev/null
redis-cli -h 127.0.0.1 -p 6379 ping
如果使用的是其他PHP运行方式,应将后文的fastcgi_pass替换为实际上游地址。执行配置变更前,建议至少准备以下内容:
- 备份Nginx当前配置和应用环境变量。
- 确认资讯站已经区分公开页面、后台页面、搜索页和接口路径。
- 确认Redis只承担缓存,或者已经明确区分缓存、会话、队列等数据。
- 确认应用具备“发布文章后清理缓存”的钩子、任务或管理操作。
- 确认页面缓存目录位于独立路径,避免与上传文件、日志目录混用。
如果Redis同时保存登录会话,不要直接修改其淘汰策略。更稳妥的做法是使用独立Redis实例或独立数据库,并在应用中设置独立的键前缀。
四、先设计缓存键和失效规则
对象缓存最容易出现的问题不是Redis不可用,而是键设计混乱,导致不同语言、站点、分页或权限的数据互相覆盖。
建议为对象键加入站点、版本和对象类型,例如:
info_site:article:v3:zh:123
info_site:category:v2:news:page:1
info_site:nav:v1:main
info_site:query:v1:latest:page:1
其中:
info_site用于区分业务;article、category、nav用于区分对象类型;v3表示数据结构或序列化格式版本;zh、分类编号、分页参数用于区分内容维度;- 不要把未过滤的用户输入直接拼接进缓存键。
参考TTL可以这样设置:
| 对象 | 参考TTL | 说明 |
|---|---|---|
| 文章详情 | 600秒 | 发布或修改时主动清理 |
| 首页文章列表 | 60~120秒 | 更新频率通常较高 |
| 栏目列表 | 120~300秒 | 需要配合文章发布清理 |
| 导航、站点配置 | 900~1800秒 | 修改配置后主动清理 |
| 搜索结果 | 30~120秒 | 查询组合多,不宜长期保留 |
| 热门排行 | 30~60秒 | 允许短时间内存在近似结果 |
如果应用支持类似环境变量,可以采用如下形式。变量名称需要根据实际框架调整:
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DB=2
CACHE_PREFIX=info_site:
查询缓存应当作为对象缓存的一部分,而不是无条件缓存所有SQL结果。只有参数有限、结果变化不频繁的查询适合缓存,例如“最新文章列表”“某栏目第一页”。搜索关键词、用户权限、草稿状态等查询应当绕过公共查询缓存,或者将用户和权限信息纳入缓存键。
五、配置Nginx页面缓存
页面缓存建议只覆盖公开的GET和HEAD请求。下面是Nginx配置片段,适用于Nginx加PHP-FPM的常见部署方式。
fastcgi_cache_path和map放在http级别,缓存相关指令放在对应的server或PHP页面location中:
http {
fastcgi_cache_path /var/cache/nginx/info_page
levels=1:2
keys_zone=info_page:100m
max_size=2g
inactive=30m
use_temp_path=off;
map $request_method $skip_method {
default 1;
GET 0;
HEAD 0;
}
map $http_cookie $skip_cookie {
default 0;
~*(session|logged_in|comment_author|admin) 1;
}
map $request_uri $skip_uri {
default 0;
~^/admin(?:/|$) 1;
~^/login(?:/|$) 1;
~^/account(?:/|$) 1;
~^/api(?:/|$) 1;
~^/search(?:/|$) 1;
~^/submit(?:/|$) 1;
}
map $args $skip_args {
default 0;
~*(^|&)(preview|nocache|token)= 1;
}
server {
listen 80;
server_name example.com;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_cache info_page;
fastcgi_cache_methods GET HEAD;
fastcgi_cache_key "$scheme|$host|$request_uri";
fastcgi_cache_valid 200 180s;
fastcgi_cache_valid 301 600s;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
fastcgi_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
fastcgi_cache_bypass $skip_method $skip_cookie $skip_uri $skip_args;
fastcgi_no_cache $skip_method $skip_cookie $skip_uri $skip_args;
fastcgi_no_cache $upstream_http_set_cookie;
add_header X-Page-Cache $upstream_cache_status always;
}
}
}
上面的PHP-FPM套接字只是示例,必须先通过以下命令确认实际名称:
ls /run/php/php*-fpm.sock
如果输出的是php8.1-fpm.sock或其他名称,应替换配置中的路径。X-Page-Cache响应头适合测试阶段观察MISS、HIT、BYPASS等状态,正式环境可以移除,或者只在内部测试域名上保留。
页面缓存目录需要由Nginx工作进程可写。假设工作进程用户是www-data,可以执行:
ps -o user= -C nginx | sort -u
sudo install -d -o www-data -g www-data /var/cache/nginx/info_page
如果实际工作进程用户不是www-data,应根据核验结果调整。这个目录只用于页面缓存,不要把上传目录或应用源代码目录设置为缓存目录。
修改完成后先检查语法,再平滑加载:
sudo nginx -t
sudo systemctl reload nginx
reload通常不会中断已有连接;如果nginx -t失败,不要继续加载,应先恢复配置文件或修正路径。
六、发布内容时按顺序清理缓存
资讯站最重要的不是首次缓存,而是文章更新后能否及时显示新内容。推荐顺序如下:
- 在数据库事务中完成文章内容、状态和栏目关系更新。
- 事务提交成功后,删除文章详情、栏目列表和首页列表相关对象键。
- 使对应页面缓存失效。
- 如果启用了边缘缓存,再清理边缘层对应URL。
- 用未登录请求访问文章页和栏目页,确认新内容重新生成。
不要先清理页面缓存,再让应用继续读取旧对象缓存。否则页面缓存重新生成时,可能把旧对象再次写入新的HTML页面。
使用版本号进行全站页面失效
如果没有Nginx缓存清理模块,可以将页面缓存键加入版本号。应用或Nginx配置使用类似:
page:v17:https|example.com|/article/123
发布全站重要内容时,将版本号从v17改为v18。新请求会使用新键,旧页面缓存不会再被读取,随后等待其自然过期。这个方式的优点是风险低,不需要直接删除缓存目录;缺点是短时间内会产生一批新缓存文件。
如果使用Redis保存页面版本号,可以让应用在生成页面时读取:
info_site:page_version = 18
发布后只更新这个版本号,或者同时清理受影响的对象键。对于首页和栏目页,可以采用短TTL;对于大量文章详情页,则更适合使用文章ID和栏目ID建立定向失效关系。
七、验证页面缓存和对象缓存是否真正生效
1. 验证页面缓存
使用未登录、无特殊Cookie的请求测试同一URL两次:
curl -sS -D - -o /dev/null https://example.com/article/123
curl -sS -D - -o /dev/null https://example.com/article/123
正常情况下,第一次可能显示:
X-Page-Cache: MISS
第二次可能显示:
X-Page-Cache: HIT
如果持续显示BYPASS,优先检查:
- 请求是否为
POST; - 是否携带了登录或会话Cookie;
- URL是否命中了后台、搜索或接口规则;
- 应用是否返回了
Set-Cookie; preview、token等参数是否触发绕过。
如果第一次和第二次都是MISS,检查缓存目录权限、缓存键是否包含变化参数,以及响应状态是否为可缓存的200。
2. 验证对象缓存
先确认Redis可用:
redis-cli -h 127.0.0.1 -p 6379 ping
redis-cli -h 127.0.0.1 -p 6379 info stats \
| grep -E 'keyspace_hits|keyspace_misses'
查看业务键时使用扫描,不要在生产Redis上使用可能阻塞服务的全量匹配命令:
redis-cli -h 127.0.0.1 -p 6379 --scan \
--pattern 'info_site:*' | head -n 20
对象缓存命中率可以按下面的方式估算:
命中率 = keyspace_hits ÷ (keyspace_hits + keyspace_misses)
例如,假设一段观察周期内有8000次命中、2000次未命中,则命中率约为80%。这个数字只能作为判断线索,不能脱离具体对象类型解读:文章详情达到80%可能合理,刚发布的首页列表只有40%也可能是正常现象。
3. 验证内容一致性
更新一篇测试文章后,依次确认:
- 文章详情页显示新标题或新内容。
- 首页和所属栏目页显示新的更新时间。
- 未登录访问仍能命中页面缓存。
- 登录后台访问不会看到公共缓存内容。
- 带
preview=1的预览请求不会污染公开页面缓存。 - Redis中旧文章对象键已经删除或版本已变化。
八、常见失败情况与回滚方式
页面缓存导致文章更新不及时
先检查对象缓存是否已经删除。如果对象缓存仍在,页面即使重新生成也可能继续使用旧数据。处理顺序是先清理对象键,再让相关页面失效。
如果无法立即确定受影响的URL,可以暂时将页面TTL从180秒降到30秒,观察内容一致性。修改配置前应保留原值,问题确认后再恢复,避免长期降低缓存效率。
所有请求都绕过页面缓存
这通常与Cookie、Set-Cookie、查询参数或绕过规则有关。可以先用不带Cookie的请求测试:
curl -sS -D - -o /dev/null https://example.com/
再对比浏览器请求头。如果命令行能够命中,而浏览器持续绕过,通常是浏览器携带了会话Cookie。不要为了提高命中率而简单删除所有Cookie绕过规则,应先确认该Cookie是否包含用户身份或权限信息。
Redis不可用
对象缓存不可用时,应用应具备降级到数据库的能力,页面缓存仍可以继续保护公开页面。处理步骤可以是:
- 查看Redis服务状态和日志。
- 确认应用连接地址、端口和认证配置。
- 暂时降低页面缓存失效范围,不要立即全站清空页面缓存。
- 如果应用不支持自动降级,先在应用配置中关闭对象缓存,再平滑重载PHP-FPM。
- Redis恢复后分批预热首页、栏目页和高访问文章,不要同时让大量请求重建缓存。
如果Redis中保存了会话或队列,禁止直接执行全库清空操作。清空命令会影响范围过大,且通常无法通过缓存本身恢复。
发布后源站CPU突然升高
常见原因是页面缓存同时过期,很多请求一起进入PHP;也可能是对象缓存没有互斥锁,多个进程同时重建同一对象。Nginx侧已经配置的fastcgi_cache_lock可以减少页面缓存击穿,应用侧还应为热门对象增加短时锁,例如使用Redis的SET NX EX机制。
重建锁的有效期应覆盖一次正常查询和渲染时间,但不宜设置过长。假设一次页面生成通常需要1秒左右,可以先使用3~5秒的锁定时间,再根据日志调整。
发现个性化内容被公共缓存
这是需要立即处理的高风险问题。应先:
- 对受影响路径关闭页面缓存;
- 提高页面缓存版本号,使旧缓存不再被读取;
- 清理相关边缘缓存;
- 检查缓存键是否包含登录状态、语言、租户或权限维度;
- 确认未登录和已登录请求返回内容分别正确。
页面缓存只适合公共内容。用户昵称、权限菜单、订单状态、草稿内容等数据应通过动态请求或用户专属对象缓存处理。
配置加载失败需要回滚
修改Nginx配置前先备份实际配置文件,例如:
sudo cp /etc/nginx/conf.d/info-cache.conf \
/etc/nginx/conf.d/info-cache.conf.bak
具体路径以nginx -T输出为准。若测试失败,恢复备份后再次检查:
sudo cp /etc/nginx/conf.d/info-cache.conf.bak \
/etc/nginx/conf.d/info-cache.conf
sudo nginx -t
sudo systemctl reload nginx
直接删除页面缓存文件只能降低缓存命中率,不会删除数据库内容,但仍可能造成短时间源站压力。生产环境不建议未经确认就执行目录级删除;优先使用TTL、版本号或应用级定向失效。
哪些变量会改变这套配置
如果资讯站的访问结构保持“公开文章页占多数、内容更新频率中等、登录用户比例较低”,页面缓存加对象缓存通常是香港CN2 GIA服务器上的稳妥起点。但以下变量变化后,参数也需要调整:
- 文章每分钟大量更新时,首页和栏目页TTL应缩短,并加强发布事件清理;
- 登录、会员或权限页面占比升高时,应缩小页面缓存范围,更多依赖对象缓存;
- 搜索和筛选组合很多时,不宜长期保存查询结果,应缩短TTL并限制缓存键数量;
- 数据库写入频繁时,不能只依赖TTL,应采用明确的对象失效和页面版本机制;
- 访问量突然集中到少数热门文章时,应启用页面缓存锁和对象重建锁;
- Redis内存不足时,应先区分缓存与会话数据,再调整淘汰策略,不能直接扩大公共缓存范围;
- 启用边缘缓存后,必须把源站页面失效、对象失效和边缘URL清理串成同一条发布流程。
最终判断标准不是某一个固定命中率,而是:公开页面是否能稳定命中、动态页面是否没有泄露个性化内容、文章发布后是否在预期时间内更新,以及缓存失效时数据库和PHP-FPM是否仍能承受回源流量。