上一篇 下一篇 分享链接 返回 返回顶部

香港CN2 GIA服务器部署资讯站,页面缓存与对象缓存如何分层设置

发布人:Minchunlin 发布时间:2026-10-01 23:59 阅读量:4

假设一台香港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替换为实际上游地址。执行配置变更前,建议至少准备以下内容:

  1. 备份Nginx当前配置和应用环境变量。
  2. 确认资讯站已经区分公开页面、后台页面、搜索页和接口路径。
  3. 确认Redis只承担缓存,或者已经明确区分缓存、会话、队列等数据。
  4. 确认应用具备“发布文章后清理缓存”的钩子、任务或管理操作。
  5. 确认页面缓存目录位于独立路径,避免与上传文件、日志目录混用。

如果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失败,不要继续加载,应先恢复配置文件或修正路径。

六、发布内容时按顺序清理缓存

资讯站最重要的不是首次缓存,而是文章更新后能否及时显示新内容。推荐顺序如下:

  1. 在数据库事务中完成文章内容、状态和栏目关系更新。
  2. 事务提交成功后,删除文章详情、栏目列表和首页列表相关对象键。
  3. 使对应页面缓存失效。
  4. 如果启用了边缘缓存,再清理边缘层对应URL。
  5. 用未登录请求访问文章页和栏目页,确认新内容重新生成。

不要先清理页面缓存,再让应用继续读取旧对象缓存。否则页面缓存重新生成时,可能把旧对象再次写入新的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. 验证内容一致性

更新一篇测试文章后,依次确认:

  1. 文章详情页显示新标题或新内容。
  2. 首页和所属栏目页显示新的更新时间。
  3. 未登录访问仍能命中页面缓存。
  4. 登录后台访问不会看到公共缓存内容。
  5. 带preview=1的预览请求不会污染公开页面缓存。
  6. Redis中旧文章对象键已经删除或版本已变化。

八、常见失败情况与回滚方式

页面缓存导致文章更新不及时

先检查对象缓存是否已经删除。如果对象缓存仍在,页面即使重新生成也可能继续使用旧数据。处理顺序是先清理对象键,再让相关页面失效。

如果无法立即确定受影响的URL,可以暂时将页面TTL从180秒降到30秒,观察内容一致性。修改配置前应保留原值,问题确认后再恢复,避免长期降低缓存效率。

所有请求都绕过页面缓存

这通常与Cookie、Set-Cookie、查询参数或绕过规则有关。可以先用不带Cookie的请求测试:

curl -sS -D - -o /dev/null https://example.com/

再对比浏览器请求头。如果命令行能够命中,而浏览器持续绕过,通常是浏览器携带了会话Cookie。不要为了提高命中率而简单删除所有Cookie绕过规则,应先确认该Cookie是否包含用户身份或权限信息。

Redis不可用

对象缓存不可用时,应用应具备降级到数据库的能力,页面缓存仍可以继续保护公开页面。处理步骤可以是:

  1. 查看Redis服务状态和日志。
  2. 确认应用连接地址、端口和认证配置。
  3. 暂时降低页面缓存失效范围,不要立即全站清空页面缓存。
  4. 如果应用不支持自动降级,先在应用配置中关闭对象缓存,再平滑重载PHP-FPM。
  5. Redis恢复后分批预热首页、栏目页和高访问文章,不要同时让大量请求重建缓存。

如果Redis中保存了会话或队列,禁止直接执行全库清空操作。清空命令会影响范围过大,且通常无法通过缓存本身恢复。

发布后源站CPU突然升高

常见原因是页面缓存同时过期,很多请求一起进入PHP;也可能是对象缓存没有互斥锁,多个进程同时重建同一对象。Nginx侧已经配置的fastcgi_cache_lock可以减少页面缓存击穿,应用侧还应为热门对象增加短时锁,例如使用Redis的SET NX EX机制。

重建锁的有效期应覆盖一次正常查询和渲染时间,但不宜设置过长。假设一次页面生成通常需要1秒左右,可以先使用3~5秒的锁定时间,再根据日志调整。

发现个性化内容被公共缓存

这是需要立即处理的高风险问题。应先:

  1. 对受影响路径关闭页面缓存;
  2. 提高页面缓存版本号,使旧缓存不再被读取;
  3. 清理相关边缘缓存;
  4. 检查缓存键是否包含登录状态、语言、租户或权限维度;
  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是否仍能承受回源流量。

目录结构
全文