≡articles //snippets ./categories $uses ~about /search

Redis Object Cache 深度配置:为什么改了数据前端不更新

改了一个页面标题、发了一篇新文章、在后台调整了一个选项——刷新前台,纹丝不动。

清掉浏览器缓存,没用。开无痕窗口,还是旧的。你开始怀疑人生。

这时候你大概忘了服务器上还跑着一个 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 是一把好刀,但不磨就会割到自己。

相关阅读:

© 2026 Redis Object Cache 深度配置:为什么改了数据前端不更新 · 本文由 Charlie 原创撰写,发布于 sodebug.com。 未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。
GK
独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

related相关文章

#01服务器攻击日志半年复盘:5 万次 XML-RPC 探测与 0 次成功入侵13 min#02宝塔 Nginx 限流配置速查:limit_req_zone 参数详解4 min#03SEO 体检脚本:从 51 条短 meta 到一条命令扫完5 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
$ subscribe →