每次发完文章都清一遍Redis,直到我去数了数那个库里有几个键

每次发完文章都清一遍Redis,直到我去数了数那个库里有几个键
张文保 30 分钟阅读 4,425 阅读
本文目录
  1. 我清了半年的那个键值库,里面为什么一直是零个键?
  2. 四个库,三个满的,一个空的
  3. 为什么本站一个键都没有
  4. 先搞清楚东西在哪,再谈清
  5. 顺手量到的另一件事:淘汰策略从来没生效过
  6. 命令行里那句重置为什么返回false还不报错?
  7. 前后两次读数,一个字都没变
  8. 原因是两套完全独立的实例
  9. 这台机器上根本不需要那句命令
  10. 清了里层页面还是旧的,问题出在顺序吗?
  11. 只清里层,什么都不会发生
  12. 顺序错了会发生什么
  13. 完整的顺序长什么样
  14. 一次全清到底扔掉了什么?
  15. 304MB,955个页面
  16. 重建的账该算在谁头上
  17. 什么时候该全清,什么时候不该
  18. 我以为在测冷启动,其实已经被别人预热了
  19. 后台更新和陈旧回退,会把你的清理动作推迟多久?
  20. 两个开关,一个共同后果
  21. 状态头六种取值,各说明什么
  22. 配置文件里写着3600,发出去的为什么是300?
  23. 十二处硬编码,全都没生效
  24. 还有一处更早就该发现的矛盾
  25. 怎么证明缓存真的清掉了?
  26. 那条命令为什么在脚本里静默失败
  27. 三层各自的作证命令
  28. 没有状态头的话,先把它加上
  29. HTML和图片是两套缓存,清法为什么不同?
  30. 一张图,一份HTML
  31. 但它带来一个后果
  32. 保哥的清缓存四步与作证清单
  33. 四步
  34. 作证清单
  35. 三条给自动化脚本的硬要求
  36. 常见问题解答
  37. 怎么快速判断我的站到底启没启用对象缓存?
  38. 直接把所有库都清掉不是更省事吗?
  39. 清完缓存网站变慢了,是不是清错了?
  40. 为什么改了内容之后,有的用户看到新的有的看到旧的?
  41. 命令行里那句重置既然无效,正确的做法是什么?
  42. 按URL精准清理和全清,日常该用哪个?
  43. 权威参考资料
摘要:每次发完文章都要跑一遍清缓存流程,其中一条是清那个键值库的0号库。跑了大半年,这次顺手数了一下:0号库里一直是0个键。真正装着东西的是1号库的2159个键和2号库的8377个键,而那两批键的前缀属于同一台机器上另外两个站点,跟本站毫无关系。同一轮排查里还钉死了第二件事:命令行里那句字节码缓存重置返回 bool(false),执行前后进程池那边的重启计数和命中数一个数字都没变。两件事的失败模式完全一样——不报错、不抛异常、脚本继续往下打印“已完成”。

发完文章清缓存,这是每个自建站的人都有的收尾动作。命令通常是从某篇教程里抄来的,抄完就再没看过,因为它从来不报错。

不报错这件事本身就值得怀疑一下。这次我把整条清理链路拆开量了一遍,量出来的东西比预想的难看:四层缓存里,我常年在清的那一层是空的;另一层我以为清掉了,其实那条命令连执行都没执行成功。

下面的数字全部来自2026年7月31日对一台真实生产服务器的实测,包括我自己那台。

我清了半年的那个键值库,里面为什么一直是零个键?

先把这台机器上的缓存分层摆出来。一个典型的自建站,从里往外依次是:字节码缓存(PHP把源码编译成的中间产物)、对象缓存(数据库查询结果,通常放在内存键值库里)、全页缓存(渲染好的整页HTML,放在磁盘上)、然后才是CDN和浏览器。

四个库,三个满的,一个空的

把内存键值库的每个逻辑库都数一遍:

逻辑库键数键名前缀属于谁
0号库0没有任何东西
1号库2159某站点前缀加下划线同机的另一个站
2号库8377另一套前缀同机的第三个站
3号库1某缓存插件的指标键同上

我的收尾脚本里那行命令,清的是0号库。0号库常年是0个键。这条命令跑了大半年,每次都成功,每次都清了个寂寞。

更尴尬的是另外两个库里那10536个键。它们的前缀清清楚楚地写着是另外两套系统的对象缓存,跟本站的程序完全不是一码事。如果当初我抄的教程里写的是清全部库而不是清0号库,我早就把隔壁两个站的缓存清光了,而且大概率至今都不知道。

为什么本站一个键都没有

原因很简单,也很容易被自己骗过去:本站根本没有启用对象缓存。装的那批插件里没有一个把查询结果往内存键值库里写,程序层面用的是文件缓存和全页缓存。

那为什么站点看起来一点问题都没有?因为真正挡在前面的是全页缓存那一层。绝大多数请求根本没进到PHP,nginx直接把磁盘上那份现成的HTML甩了出来,压根没有机会去查数据库,自然也就不需要对象缓存。

这就是整件事最危险的地方:一个从来没生效过的缓存层,和一个一直在正常工作的缓存层,从站点表现上完全区分不开。你清一个空库,站点很快;你不清那个空库,站点还是很快。没有任何信号会告诉你这条命令是多余的。

先搞清楚东西在哪,再谈清

逻辑库这个设计本身就是个坑。SELECT命令的官方文档说明了它的语义:一个实例里有多个用编号区分的逻辑库,默认连到0号,而这些库共享同一个实例的内存和配置,只是键空间互相隔离。文档里还专门提到,在集群模式下这个特性是不支持的。

换句话说,多个站共用一个键值实例、靠库号隔离,是一种约定式的隔离——没有权限边界,没有防护,全靠每个人都记得自己该用哪个号。而清库命令本身对库号毫不敏感,FLUSHDB的文档说得很直白,它删除当前所选库的全部键。选错号,删的就是别人的。

所以第一条判据是:动手清之前先数键。三条命令就能问清楚:数一下每个库有多少键、随机抽几个键名看前缀、确认自己的程序到底往哪个库写。这三步花不到一分钟,能省掉半年的无用功,也能避免一次误删。对象缓存那一层的具体配置和命中率该怎么读,Redis对象缓存怎么给WordPress提速?object cache原理与运维实战那篇拆得更细。

顺手量到的另一件事:淘汰策略从来没生效过

既然连上了,把这个实例的运行指标一起拉了出来,又撞见一处配置与现实的落差:

指标实测值
已用内存11.70MB
内存上限4.00GB
淘汰策略按最近最少使用淘汰全部键
命中次数990878
未命中次数89966
过期删除的键41641
因内存不足被淘汰的键0

内存上限设了4GB,实际用了11.7MB,占用率0.29%。而因内存不足被淘汰的键数是 0——这个实例从启动到现在,从来没有因为装不下而扔掉过任何一个键。

这意味着那条淘汰策略配置一次都没有被执行过。它配成什么都一样,因为触发它的前提条件从未出现。而绝大多数教程会花很大篇幅讲这几种策略怎么选,讲得都对,只是对这台机器毫无意义。

命中率倒是个有用的数字:990878除以总请求数得到 91.7%,不算差。另外那41641个过期删除的键说明有效期机制在正常工作——键是自己到点走的,不是被挤走的,这是健康的样子。

这一节想说的其实跟缓存无关:一份配置是不是合理,不能只看它写得对不对,还得看它有没有被执行过。没被执行过的正确配置,和写错了的配置,在系统表现上一样看不出来。

命令行里那句重置为什么返回false还不报错?

第二件事比第一件更普遍,因为它出现在几乎每一份部署脚本里。

前后两次读数,一个字都没变

字节码缓存的重置函数,在命令行里跑一次:

php -r 'var_dump(@opcache_reset());'
bool(false)

返回 false。不是异常,不是警告,就是一个安静的false,而绝大多数脚本写的是 php -r 'opcache_reset();'——连返回值都不看。

为了确认它到底有没有影响到真正干活的那一侧,我在网站目录里临时放了一个探针脚本,通过HTTP请求读取进程池里的状态。执行重置前后各读一次:

指标重置前命令行重置后
是否启用truetrue
已缓存脚本数42304230
命中次数415623415623
未命中次数42314231
命中率98.99%98.99%
已用内存245.8MB245.8MB
手动重启计数11

七个指标,一个都没动。最能说明问题的是最后一行——手动重启计数专门用来记录重置被调用了几次,它停在1上纹丝不动,说明那句命令根本没碰到这个进程池。

原因是两套完全独立的实例

查一下配置就明白了:

opcache.enable      => On  => On
opcache.enable_cli  => Off => Off

字节码缓存的配置项手册里,enable_cli 这一项的默认值就是关闭。而 重置函数的手册页写明它的作用是重置整个缓存的内容——问题在于“整个”指的是当前这个进程能看到的那份缓存。

命令行跑的PHP和进程池里的PHP是两个完全独立的进程,各自有各自的共享内存段。命令行那边压根没开缓存,所以它连一份可重置的东西都没有,函数老老实实返回false;而进程池那边的4230个脚本、245.8MB内存,站在命令行的角度是完全够不着的。

这个坑的杀伤力在于它的普遍程度。“部署完跑一句重置”几乎是所有PHP部署教程的标准收尾,而在默认配置下,这一句在命令行里执行等于什么都没做。

这台机器上根本不需要那句命令

还有个反转。同一份配置里还有两行:

opcache.validate_timestamps => On => On
opcache.revalidate_freq     => 60 => 60

启用了时间戳校验,间隔60秒。意思是改完文件之后最多60秒,进程池会自己发现文件变了并重新编译,不需要任何人手动干预。

所以这条链路上同时存在两个错误认知:一是以为那句命令生效了(其实没有),二是以为需要它(其实不需要)。两个错误刚好互相抵消,于是站点一直正常,也就一直没人去查。

真正需要手动重置的场景只有一种:关掉时间戳校验以换取性能。那种配置下改完文件必须显式重置,而且必须在进程池内部执行——要么通过一个HTTP端点触发,要么直接重载进程池。字节码缓存这一层的完整调优思路,PHP OPcache字节码缓存怎么调才能让站点真正快起来那篇讲过取舍。

清了里层页面还是旧的,问题出在顺序吗?

这是清缓存里最反直觉的一件事:清理的顺序必须跟读取的顺序相反,否则你会亲手把旧内容重新固化一遍。

只清里层,什么都不会发生

做一组对照。先把三个页面预热到全页缓存里,然后只清对象缓存那一层,全页缓存原封不动,再打这三个URL:

阶段全页缓存文件数首字节(秒)
预热完成(热态)40.2299 / 0.2337
清完对象缓存4(没变)0.2125 / 0.6817
再看对象缓存键数仍然是0,说明这几个请求根本没走到那一层

首字节几乎没有变化,全页缓存文件数一个没少。你清了里层,但请求压根到不了里层——它在外层就被拦住返回了。

反过来,把全页缓存清掉再打同样的URL:

页面清空后第1次(冷)第2次第3次冷热比
页面A0.98980.23390.23344.24
页面B0.87560.21350.23014.10
页面C1.23270.23190.23325.32

冷启动比命中慢4到5倍,这才是请求真正落回PHP的样子。

顺序错了会发生什么

把这两组数据合起来推演一下,就能看清顺序为什么要紧。

假设你改了内容,然后先清外层(全页缓存)、再清里层(对象缓存)。这两条命令之间总有个时间差,哪怕只有一秒。在这一秒里,任何一个访客或者爬虫敲进来,会发生什么?

  1. 外层已经空了,请求穿透到PHP。
  2. PHP去查对象缓存——里层还没清,它拿到的是旧数据
  3. PHP用旧数据渲染出一个页面,返回。
  4. nginx把这个用旧数据渲染的页面写进全页缓存。
  5. 你这才执行第二条命令,把里层清了。

结果是:里层干净了,外层刚刚被灌进去一份新鲜出炉的旧内容,而且带着完整的有效期。你的清缓存动作,亲手把旧数据的寿命续了一轮。

而按正确顺序(先里层后外层)就不会有这个窗口:里层清完之后,外层还挡着,这段时间里没有任何请求能穿透进来触发重建;等外层也清掉时,里层已经是空的,重建出来的必然是新内容。

所以规则可以压成一句话:清理顺序必须与读取链路相反,从最里层往最外层清。读取是从外往里穿透,清理就得从里往外剥。

完整的顺序长什么样

把这台机器上的四层按正确顺序排开:

顺序动作是否必需
1字节码缓存重载进程池,或依赖时间戳校验自动过期只在关闭时间戳校验时必需
2对象缓存清对应库号(先确认是哪个号)只在真的启用了才必需
3全页缓存删缓存目录,或用清理模块按URL精准删必需
4CDN与边缘调接口刷新,或等有效期自然到点看有没有挂CDN

浏览器那一层不在这个列表里,因为你清不了别人的浏览器——那一层只能靠响应头里的指令提前约定好行为,事后无法补救。这也是为什么HTML的 max-age 通常要设成0:你唯一能确保事后可清的,是自己这一侧。反向代理层的清理和过期怎么配,Nginx proxy_cache反向代理缓存怎么配那篇有更细的参数。

一次全清到底扔掉了什么?

“清缓存”听起来是个免费动作。量一下就知道它不免费。

304MB,955个页面

执行清空之前,全页缓存目录的状态:

指标数值
缓存文件数955
占用磁盘304MB
最老的一份约70分钟前生成
最新的一份执行清理前2秒生成

一条 rm -rf 下去,955变成0。955份已经渲染好的HTML全部作废,每一份都是之前某个访客用真实等待时间换来的。

清完之后打了3个URL,目录里只回来4个文件。这个数字很直观地说明了重建是多么缓慢的过程:缓存是被访客一个一个填回来的,不是自己长出来的。

重建的账该算在谁头上

量一下重建成本:清空之后连续请求5个页面,总耗时 1691毫秒,平均每个页面338毫秒。

按这个速率往外推,把955个页面全部重建需要大约 323秒的纯服务器处理时间。但这323秒不是服务器在后台默默还清的——它被拆散成955份,分给了955个倒霉的访客,每人多等0.3到1秒。

而这955个页面本身也说明了点别的:全站文章一千七百多篇,缓存里只有955份是热的,约占56%。剩下四成多的页面本来就是冷的,谁撞上谁买单,这跟清不清缓存没关系,是长尾内容的固有成本。

什么时候该全清,什么时候不该

由此得到一条相当实用的判据:

  • 改了单篇内容——只清那一个URL。用清理模块按键精准删,代价接近零。
  • 改了模板或全局配置——必须全清,因为影响面覆盖所有页面,没有别的办法。
  • 不确定影响面——先想清楚再动手。全清一次的代价是300多秒的用户等待被摊到几百个人头上,不是免费的。
  • 调试时反复全清——换个思路,用带随机参数的地址绕过缓存来验证,别拿生产缓存当调试环境。

全页缓存的键怎么设计才能做到按URL精准清理,Nginx fastcgi_cache全页缓存怎么配那篇有完整配置。

我以为在测冷启动,其实已经被别人预热了

前面那张冷热对照表里,其实还有一个页面没列进去,因为它的数据自相矛盾:清空缓存之后第一次请求,首字节 0.2332 秒,跟热态的0.2358、0.2338、0.2342几乎一模一样。

按理说清空之后第一次必然是冷启动,应该在0.9秒上下。它没有。

唯一的解释是:在我执行清空和发出第一个请求之间的那一秒里,别人已经把它请求过一遍了。可能是爬虫,可能是真实访客,也可能是某个监控。那个人替我承担了冷启动的代价,等我的测量请求到达时,缓存已经重建好了。

这条对做测量的人是个硬提醒:在一个有真实流量的生产站上,你没有办法独占一个干净的观测窗口。清空到测量之间的每一毫秒,都可能有别人在改变你要观测的状态。

补救办法有三个,按可靠性排:用一个带随机参数、别人不可能撞上的地址来测;把清空和测量之间的间隔压到最小;或者干脆多测几组,用中位数把这类污染筛掉。我这次是第三种——四组里三组正常、一组被污染,所以那一组的结论只能弃用。

后台更新和陈旧回退,会把你的清理动作推迟多久?

还有两个开关会让“清缓存”这件事的效果变得不那么立竿见影,而它们通常是默认配置的一部分,没人专门去开。

两个开关,一个共同后果

这台机器的全页缓存配置里有这么两行:

fastcgi_cache_background_update on;
fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503;

第一行的意思是:缓存过期之后,先把过期的那份发给用户,同时在后台悄悄发起一个请求去更新它。第二行的意思是:在列出的那几种情况下(后端出错、超时、正在更新、返回500或503),允许继续使用过期的缓存。

两个开关的出发点都是好的——它们让用户永远不用站着等重建,也让后端偶发抖动不至于变成用户可见的错误页。代价是:内容更新的可见时刻,被推迟了整整一轮。

具体推迟多久取决于谁先到。如果过期后第一个到达的请求触发了后台更新,那么这个人拿到的是旧的,下一个人才拿到新的。换句话说,永远有至少一个用户会看到你以为已经更新掉的内容。

状态头六种取值,各说明什么

正因为有这些机制,那个缓存状态头就不只有MISS和HIT两种值。服务器模块的官方文档里定义了完整的取值表,实际排查时会遇到的是这几个:

取值含义看到它该想什么
MISS没命中,这次是真的走了后端清理生效了,或这个页面第一次被访问
HIT命中了有效期内的缓存正常状态
EXPIRED命中了但已过期,这次重新取了有效期偏短,或访问太稀疏
UPDATING过期了,正在后台更新,先给你旧的你看到的是旧内容
STALE后端出问题,回退给你过期的那份后端可能正在故障
BYPASS规则命中,这次绕过缓存检查绕过条件是不是写宽了

验收清理效果时,只有MISS才算数。看到UPDATING或STALE说明你拿到的还是旧内容,这时候下结论说“清了没用”是冤枉的——再打一次通常就正常了。

看到BYPASS则要往另一个方向查:这个页面根本没在缓存里,你清什么都不会有变化。常见原因是绕过条件里包含了登录状态、特定的查询参数或者某些Cookie,而你恰好带着这些东西在测。用一个干净的客户端重测,是排除这种误判最快的办法。

配置文件里写着3600,发出去的为什么是300?

这是这轮排查里最意外的一条,属于顺手撞见的。

十二处硬编码,全都没生效

在服务器配置目录里搜一下缓存指令,找到 12处硬写的这一行:

add_header Cache-Control "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400" always;

而线上实际发出去的响应头是:

cache-control: public, max-age=0, s-maxage=300, stale-while-revalidate=86400

3600变成了300,差12倍。

原因是配置里还有两行用变量的写法,位置更靠后、作用域更具体,于是它赢了。真正生效的值藏在那个变量的定义里,而不是那12处看起来言之凿凿的硬编码里。

这条的教训跟清缓存本身无关,但同源:配置文件里写着什么,跟线上实际发出去什么,是两个必须分别验证的事实。唯一可靠的确认方式是发一个真实请求把响应头打出来看——这类沉默的配置差异该怎么系统性地查,curl -I查了一圈说这站没配缓存头,换成GET之后那五个头全在那篇专门讲了工具本身的坑。

还有一处更早就该发现的矛盾

同一份配置里,全页缓存的有效期是这么写的:

fastcgi_cache_valid 200 301 302 5m;
fastcgi_cache_valid 404 1m;

服务器自己只缓存 5分钟,却通过响应头告诉CDN缓存60分钟(那12处硬编码的本意)。RFC 9111对s-maxage的定义写明它专门给共享缓存用,并且会覆盖max-age,也就是说CDN认的是这个值。

两个数字不一致会导致一个尴尬局面:源站上那份5分钟就过期了,CDN那份还要留55分钟。这段时间里,你在源站上做的任何清理动作,对已经到达CDN的那一份都毫无作用。这也是为什么清缓存必须把CDN那一层单独列进流程,而不是清完源站就以为完事了。CDN层的有效期分层该怎么设计,CDN边缘缓存到底怎么配才不踩坑?回源、TTL分层与缓存键实战那篇有一套现成的分层方案。

怎么证明缓存真的清掉了?

前面所有的坑都指向同一个根因:清理动作没有回执。命令跑完打印一行“已完成”,那行字是脚本自己写的,不是缓存告诉你的。

那条命令为什么在脚本里静默失败

顺着这个思路,我把自己的收尾脚本又查了一遍,发现了第三个坑,而且这次是彻底的静默失败。

非交互方式远程执行命令时,环境变量里的可执行路径只有四个目录:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

而这台机器上那个键值库的客户端程序装在一个自定义目录下,不在这四个里面。于是:

$ redis-cli -n 0 DBSIZE
bash: redis-cli: command not found     # 退出码 127

$ /www/server/redis/src/redis-cli -n 0 DBSIZE
0                                       # 退出码 0

关键在下面这一步。当这条命令被分号串在一长串收尾命令里时:

$ redis-cli -n 0 FLUSHDB; echo "缓存已清完毕"
bash: redis-cli: command not found
缓存已清完毕

命令失败了,成功提示照常打印。因为分号只表示“接着执行下一条”,它不看上一条的退出码。整个批处理返回0,日志里一片祥和。

这个坑跟前面那个字节码重置返回false的坑,是同一个失败模式的两个实例:失败被吞掉,脚本继续,输出看起来一切正常。它们能潜伏这么久,唯一的原因就是没有人去验证结果。

三层各自的作证命令

回执必须来自被清理的对象本身,而不是执行者的自述。三层各有各的作证方式:

清理前后各跑一次合格的回执
对象缓存数当前库的键数数字从N变成0
全页缓存数缓存目录的文件数数字从N变成0
字节码缓存通过HTTP读进程池里的手动重启计数计数加1,已缓存脚本数归零
CDN请求一次看边缘状态头从HIT变成MISS

最后一行本站是现成的。清空全页缓存后连打四次同一个URL,把状态头一起记下来:

第几次缓存状态头首字节(秒)
1MISS0.4858
2HIT0.2340
3HIT0.2331
4HIT0.2337

第一次MISS、耗时0.4858秒,之后三次全部HIT、稳定在0.233秒左右。这四行是无可辩驳的证据链:清理确实生效了(第一次真的没命中),重建确实发生了(第二次开始命中),效果确实存在(差2.1倍)。

没有状态头的话,先把它加上

本站能这么方便地作证,是因为配置里有这么一行:

add_header X-FastCGI-Cache $upstream_cache_status always;

nginx的FastCGI模块文档里定义了这个变量的取值,常见的有MISS、HIT、EXPIRED、BYPASS、UPDATING、STALE等。把它挂成一个响应头,成本几乎为零,收益是此后所有的缓存问题都变成了一个可以一眼看穿的事实,而不是靠掐表猜。

如果你的站现在没有这一行,我的建议是把它当成清缓存流程的第0步:先让缓存能说话,再谈怎么清它。浏览器和CDN那一侧的对应信号,浏览器HTTP缓存头怎么配?让回头客秒开又不犯改了不更新的事故那篇也整理过一份。

HTML和图片是两套缓存,清法为什么不同?

同一个站里,两类资源走的是完全不同的缓存策略,因此“清缓存”对它们意味着完全不同的动作。

一张图,一份HTML

把同一个页面和它里面的一张封面图,响应头并排放:

响应头HTML页面封面图
Cache-Controlpublic, max-age=0, s-maxage=300, stale-while-revalidate=86400public, max-age=31536000, immutable
ETag
Last-Modified
VaryAccept-Encoding
浏览器缓存时长0秒一年,且声明不可变

差别是刻意设计的,而且设计得对。HTML随时可能更新,所以浏览器端设成0、只让共享缓存留5分钟;图片一旦上传就不会变,所以给一年并声明不可变,浏览器连条件请求都不用发。

但它带来一个后果

immutable 这个指令的含义是“在有效期内我保证不变,你连问都不用问”。后果是:这张图一旦被用户缓存,你在服务器上把它换掉也没用,那一年之内这个用户看到的都是旧图。

所以图片这一层的“清缓存”根本不是清,而是换名字——改文件名或者加内容指纹,让它变成一个全新的URL。这是唯一可靠的办法。

顺带说一下HTML那边没有ETag和Last-Modified的事。这对动态页面是合理的(每次生成的字节可能都有细微差别,发ETag反而会造成误判),代价是无法用条件请求省下重复传输。这笔账在抓取量大的站上不小,但它跟清缓存是两个独立的话题,多层缓存对首字节和抓取的联合影响,TTFB怎么优化才不白费:多层缓存如何同时左右Core Web Vitals与Google抓取那篇算过完整的账。

保哥的清缓存四步与作证清单

把这一轮踩出来的东西压成一套可以直接照抄的流程。

四步

  1. 先摸清有哪几层,各在哪。数一下每个键值库有多少键、看键名前缀是谁的、确认全页缓存目录在哪、确认字节码缓存是不是开着时间戳校验。这一步只做一次,做完写进文档。
  2. 从最里层往最外层清。字节码 → 对象 → 全页 → CDN。顺序反了会把旧内容重新固化。
  3. 每清一层,立刻取回执。前后各数一次,数字必须变。脚本自己打印的成功提示一律不算。
  4. 用真实请求做最后验收。打一次目标URL,看缓存状态头是不是MISS,再打一次看是不是变回HIT。这两次的组合能同时证明“清掉了”和“又建好了”。

作证清单

检查项合格标准不合格时最可能的原因
对象缓存键数清理后为0清错了库号,或本来就没启用
全页缓存文件数清理后为0路径写错,或权限不足
字节码手动重启计数加1在命令行执行的,没碰到进程池
缓存状态头先MISS后HIT没配状态头,或请求被绕过
页面上那处改动肉眼可见CDN那层没清
命令退出码全部为0路径没写全,命令根本没执行

三条给自动化脚本的硬要求

第一,所有命令写全路径。非交互环境的可执行路径跟你登录后看到的不是一回事,装在自定义目录里的程序一律找不到。这一条能挡掉本文里的第三个坑。

第二,用 && 而不是分号串联。分号不看退出码,前一条炸了后一条照跑;换成 &&,任何一步失败整条链就停在那里,比事后翻日志强得多。

第三,把回执打进日志,而不是打成功提示。日志里应该出现的是“键数2159 → 0”“文件数955 → 0”这样的数字对,而不是“缓存已清理完毕”。前者无法伪造,后者只是个字符串。

这三条加起来的成本,大概是给收尾脚本多写十几行。而它们要挡的,是本文里那三个各自潜伏了大半年的坑——清了个空库、重置了个不存在的缓存、命令根本没执行。三个坑的共同点是都不会报错,所以它们只能被主动验证发现,等不到自己暴露的那一天。

顺带一提,做完这轮排查我把自己的收尾脚本改了:那条清0号库的命令直接删掉了,因为它清的东西根本不存在;那条命令行重置也删掉了,因为时间戳校验会替我干;剩下的全页缓存清理换成了全路径写法,后面跟一条数文件数的命令做回执。脚本短了两行,可信度高了不止两倍。

常见问题解答

怎么快速判断我的站到底启没启用对象缓存?

两步。第一步数键:连上键值库,逐个库数键数并抽样看键名前缀,如果没有一个前缀跟你的程序对得上,那就是没启用。第二步看程序:查一下配置文件里有没有指向键值库的连接参数,以及有没有装对应的缓存插件。两步结论一致才可信——我这次就是只看了插件列表以为装了,实际上装的那批插件里没有一个往键值库写东西。

直接把所有库都清掉不是更省事吗?

非常不建议,尤其在共享服务器上。清全部库的命令会把这个实例里所有逻辑库的键一次删光,而多个站共用一个实例、靠库号隔离是很常见的部署方式。本文实测的这台机器上就有另外两个站的一万多个键躺在1号和2号库里,一条命令下去它们全没了,而且对方大概率查不出是谁干的——因为他们的站不会报错,只会突然变慢一阵子。

清完缓存网站变慢了,是不是清错了?

不是,这是正常现象,而且是可预期的。本文实测的冷热比是4到5倍,也就是说清完之后头几分钟,每个第一次访问的页面都会慢4到5倍。缓存是被访客一个一个填回来的,955个页面全部重建需要大约323秒的服务器处理时间,这些时间会摊到几百个访客头上。如果你的站流量大、页面多,建议在低峰期清,或者改用按URL精准清理而不是全清。

为什么改了内容之后,有的用户看到新的有的看到旧的?

最常见的原因是漏清了某一层,而不同用户命中的层不同。典型场景:你清了源站,但CDN那份还在有效期内,于是命中CDN的用户看到旧的、回源的用户看到新的。第二常见的是浏览器缓存——如果HTML的有效期没设成0,老访客手上那份要等自然过期。第三种是共享缓存和私有缓存的有效期设成了不一致的值,本文实测的这台机器上就有这个问题:配置里写着一小时,实际发出去的是五分钟。

命令行里那句重置既然无效,正确的做法是什么?

看你为什么要重置。如果时间戳校验是开着的(多数默认配置都是),那就什么都不用做,改完文件最多等一个校验间隔就自动生效了。如果关掉了校验以换性能,那就必须让重置发生在进程池内部:要么放一个受保护的HTTP端点、部署时请求一次,要么直接重载进程池。判断自己属于哪种情况,查一下配置里时间戳校验那一项是开还是关,一行就够。

按URL精准清理和全清,日常该用哪个?

日常绝大多数情况用精准清理。判据很简单:看这次改动的影响面。改了一篇文章的正文,影响面就是那一个URL加上可能引用它的列表页,精准清几个键就够了。改了模板、导航、全局样式或者任何会出现在每个页面上的东西,影响面是全站,那就只能全清。介于两者之间的(比如改了某个分类的描述)可以按前缀批量清。真正要避免的是养成“出问题先全清一遍”的习惯,那等于每次都让几百个访客替你的不确定性买单。

权威参考资料

分享到
标签
版权声明

本文标题:《每次发完文章都清一遍Redis,直到我去数了数那个库里有几个键》

本文链接:https://zhangwenbao.com/multilayer-cache-purge-order-and-receipt.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交