改了一个页面标题、发了一篇新文章、在后台调整了一个选项——刷新前台,纹丝不动。
清掉浏览器缓存,没用。开无痕窗口,还是旧的。你开始怀疑人生。
这时候你大概忘了服务器上还跑着一个 Redis Object Cache——它在你和数据库之间拦了一道,而你改的数据还没突破这道缓存墙。
问题场景:后台改了,前台纹丝不动
典型症状:
- 改了文章内容 → 前台还是旧版本
- 更新了主题设置(颜色、排版) → 不生效
- 安装了新插件 → 刷新后找不到
- 修改了 wp_options 表 → 读出来的还是旧值
这些问题的共同点:操作在数据库层面已经完成了,但 PHP 读到的却是 Redis 里缓存的旧数据。Nginx FastCGI Cache 可能也在中间插了一脚,不过那是另外一个话题。这篇文章聚焦 Redis Object Cache 这一层。
Redis Object Cache 到底缓存了什么
不只是页面。WordPress 的 Object Cache 机制几乎缓存所有数据库查询结果:
wp_options— 网站设置、插件配置、主题选项。这是最容易被旧缓存坑的组。wp_posts— 文章、页面的查询结果wp_postmeta— 文章的元数据(自定义字段、SEO 分数等)wp_terms/wp_term_taxonomy— 分类、标签wp_users/wp_usermeta— 用户数据transient— 临时数据(定时任务锁、插件状态标记)
你可以用 redis-cli 看一眼实际缓存了什么:
# 看一共有多少缓存 key
redis-cli DBSIZE
# 抽样看 20 个 key
redis-cli --scan --pattern '*' | head -20
输出大概长这样:
wp:default:options:alloptions
wp:default:posts:last_changed
wp:default:terms:category
wp:default:post_meta:522
wp:default:users:1
格式是 {前缀}:{站点标识}:{缓存组}:{具体key}。看到 alloptions 了吗?那是 wp_options 的整表缓存。你在后台改了一个选项,如果这个缓存没刷新,前台永远读旧值。
缓存失效机制:什么时候会自动刷新
WordPress 内部有一套缓存失效(cache invalidation)机制,理论上在你更新数据时自动清除对应的 Redis key。但现实是:有些操作你以为会触发清除,实际不会。
会自动清的情况
- 发布/更新文章 → 清除
wp_posts相关缓存 - 修改分类/标签 → 清除
wp_terms缓存 - 调用
wp_cache_delete()或wp_cache_flush() - 使用
update_option()而非直接UPDATE wp_options
不会自动清的情况
- 用 phpMyAdmin 或 MySQL CLI 直接改了数据库 → Redis 不知道
- 用
wp_insert_post()但传了'post_status' => 'draft'→ 不触发 publish 的清除钩子 - 插件通过绕过 WordPress API 写入数据
wp_options的alloptions缓存——某些老旧插件用get_option()读取后,即使update_option()更新了值,alloptions的整表缓存可能没刷新
最后一种是最常见的坑。一个站的文章发布后首页不更新,排查到最后发现是某个 SEO 插件把首页设置缓存到了 alloptions 里,而 Redis 的 alloptions 缓存生命周期没跟上。
排查步骤:先定位是哪个缓存没清
不能再盲目 FLUSHALL 了。先定位问题:
第一步:确认是不是 Redis 的问题
最粗暴但最有效的方法:
# 临时关掉 object cache
cd /www/wwwroot/你的站
mv wp-content/object-cache.php wp-content/object-cache.php.bak
# 刷新页面看问题还在不在
# 如果在 → 不是 Redis 的问题,去看 Nginx/Cloudflare
# 如果消失了 → 确认是 Redis 缓存
第二步:用 MONITOR 看实时读写
redis-cli MONITOR
然后刷新出问题的页面,观察输出。你会看到 Redis 在查什么 key:
1687654321.123456 [0 127.0.0.1:54321] "GET" "wp:default:options:alloptions"
1687654321.123789 [0 127.0.0.1:54321] "GET" "wp:default:posts:get_posts:..."
如果 GET 返回了数据但实际数据库已经更新了——这个 key 的缓存就是旧的。找到它。
第三步:检查过期时间
# 看某个 key 还有多久过期(-1 表示永不过期,-2 表示不存在)
redis-cli TTL "wp:default:options:alloptions"
# 看所有 key 的过期情况
redis-cli --scan --pattern '*' | while read k; do echo "$k $(redis-cli TTL "$k")"; done | head -30
如果你看到关键缓存 key 的 TTL 是 -1(永不过期),那基本就定位到问题了。
手动清除的正确姿势
确认是 Redis 缓存的问题后,有四种清法,按破坏性从小到大排列:
方法一:WP 后台按钮(最温和)
WP 后台 → 设置 → Redis → Flush Cache。这个按钮调用的是 wp_cache_flush(),只会清跟当前 WP 站相关的缓存组,不会影响同 Redis 实例上的其他站。
方法二:精确清除单个 key
# 用 MONITOR 定位到的 key 直接删
redis-cli DEL "wp:default:options:alloptions"
适合你非常确定只卡在某一个 key 上的情况。
方法三:PHP 代码精确清组
// 只清 wp_options 组的缓存
wp_cache_delete('alloptions', 'options');
// 清整个组的增量 key
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('你的密码');
$keys = $redis->keys('wp:default:options:*');
foreach ($keys as $key) {
$redis->del($key);
}
注意 KEYS * 在生产环境要谨慎——它遍历所有 key,数据量大时会阻塞 Redis。更好的替代方案是 SCAN:
$it = null;
while ($keys = $redis->scan($it, 'wp:default:options:*', 100)) {
foreach ($keys as $key) $redis->del($key);
}
方法四:FLUSHALL / FLUSHDB(核弹)
# FLUSHDB — 只清当前 database(默认 DB 0)
redis-cli FLUSHDB
# FLUSHALL — 清所有 16 个 database
redis-cli FLUSHALL
警告:如果你的多个站点共用同一个 Redis 实例且没有用 WP_REDIS_DATABASE 隔离,FLUSHALL 会把所有站的缓存全清了。各站在同一时间重建缓存,瞬时 DB 压力会很高。
坑:多站点同 Redis 实例的 key 冲突
当你在一台 VPS 上跑多个 WordPress 站,出于省内存的考虑让它们共用同一个 Redis 实例,缓存 key 的隔离就成了必须面对的问题。
默认状况:一个锅里吃饭
不加任何配置时,所有站共享 Redis DB 0,key 前缀都是 wp:。站 A 的 alloptions 缓存可能被站 B 的 flush 操作误清,站 C 的文章缓存和站 D 的用户缓存混在一起——不一定会错,但排查起问题来非常痛苦。
方案一:database 号隔离
Redis 自带了 16 个 database(编号 0-15),每个是独立的键空间。在 wp-config.php 里加一行:
define('WP_REDIS_DATABASE', 1); // 站 A 用 DB 0,站 B 用 DB 1
这样 FLUSHDB 只清自己的 database。但有个坑:Redis Object Cache 的 drop-in 插件(object-cache.php)可能不读取这个常量,取决于你用的是哪个 Redis 插件。部署后务必确认:
redis-cli -n 0 DBSIZE
redis-cli -n 1 DBSIZE
# 确认各站的缓存确实写入了不同的 database
方案二:前缀隔离
define('WP_REDIS_PREFIX', 'site_a:'); // 默认是 'wp:'
这样 key 变成 site_a:default:options:alloptions 而不是 wp:default:...。前缀隔离的优势在于可以配合 SCAN 精确扫描某个站的所有 key。
实际建议:两个一起用。database 号做粗隔离,前缀做标识。排查问题时一看 key 就知道是哪个站的。
密码也别忘了
define('WP_REDIS_PASSWORD', '你的密码');
这个不配置的话,Redis Object Cache 插件会在后台持续报 “Connection refused” 或者静默失败——缓存完全不走 Redis,等于装了个寂寞。
最佳实践:配置忽略组
有些数据不适合进 Redis:
counts— 文章计数,频繁变化,缓存意义不大plugins— 插件列表,更新插件后缓存不更新会导致后台白屏themes— 主题信息blog-details/site-details— 多站点模式下变化后不易察觉
define('WP_REDIS_IGNORED_GROUPS', [
'counts',
'plugins',
'themes',
'blog-details',
]);
还可以设置最大缓存时间,防止某个 key 无限期霸占内存:
define('WP_REDIS_MAXTTL', 86400); // 最多缓存 24 小时
生产环境的 Redis Object Cache 不是装了就行——prefix、database、ignore groups、TTL 这四条配置好,才能让它从“出问题时的第一个嫌疑人”变成“查问题时不用考虑的那一层”。
总结
- 前台不更新 → 先怀疑 Redis Object Cache,
mv object-cache.php object-cache.php.bak快速验证 - 用
redis-cli MONITOR实时定位哪个 key 在返回旧数据 - 清缓存从温和到核弹:后台按钮 → DEL 单个 key → FLUSHDB → FLUSHALL
- 多站共用 Redis:database 号 + 前缀双重隔离,密码别忘配
- 配置好
WP_REDIS_IGNORED_GROUPS和WP_REDIS_MAXTTL,省得以后排查
Redis 是一把好刀,但不磨就会割到自己。
相关阅读:
- Nginx FastCGI Cache 缓存清除完整指南 — 缓存栈的另一层,改完 Redis 别忘了它
- WordPress mu-plugin 实战:零插件安全加固方案 — 用 mu-plugin 管理 Redis 配置,不依赖主题
- Redis 命令参考 — MONITOR、SCAN、FLUSHDB 完整语法