记一次奇葩的Discuz双站缓存乒乓效应排查

症状

两台独立服务器,跑着同一套Discuz代码,共享同一个MySQL数据库(主从架构,读写分离)。

诡异的是——两台服务器不能同时正常访问。

  • 去A站后台清缓存 → A站正常了 → B站白屏
  • 去B站后台清缓存 → B站正常了 → A站白屏

就像乒乓球一样,这边好那边就坏,永远只能活一个。

更迷惑的是,清完缓存后一切正常,直到下一次缓存自动更新时才出问题。

排查

错误日志里的关键线索

MySQL报错duplicate key '1-1'

这是MySQL的复合主键冲突。1-11 来自 $_config['server']['id'] = 1(Discuz中所有站点的server_id都是1),另一个1是缓存行的ID。

PHP报错:501/503间歇性出现,PHP-FPM日志里大量32秒慢查询。

数据库缓存表中的并发写入

Discuz默认的缓存类型是 sql,也就是所有缓存数据都存放在同一个数据库表 dzx_common_syscache 中。

两站的缓存配置:

项目A站B站
缓存类型sqlsql
写数据库本地3306远程3316
读数据库本地3306本地从库

两站后台清缓存时,会执行 DELETE FROM dzx_common_syscache,然后各自逐条INSERT重建缓存

如果A站的PHP进程正在写某一行,B站的PHP进程恰好也在写同一行——duplicate key,必有一方写入失败。

写入失败的那一方,因为缺少了关键的系统设置缓存行,就会直接白屏

清缓存为什么能临时修复

因为手动在后台"工具→更新缓存"是单线程执行的,不会产生并发冲突。

但线上的缓存更新请求是两个PHP进程同时在写同一个数据库表,必打架。

根本原因

两站共享一张缓存表 + 同时并发写入 = 必然冲突

数据库级别的缓存,本身就设计给单站点用的。两个独立服务器各自跑PHP进程,同时往同一张表里写,不出问题才怪。

解决方案

最简单的临时方案:两站使用不同的缓存方式。

我的选择是把B站从 sql 缓存改为 file 文件缓存。

// 改前
$_config['cache']['type'] = 'sql';

// 改后
$_config['cache']['type'] = 'file';

为什么这样可行:

  • B站不再读写数据库中的 dzx_common_syscache
  • B站的缓存存到本地 data/cache/ 目录下的文件里
  • 两站不再竞争同一张数据库表
  • 文件缓存比SQL缓存还要快(不需要走网络和数据库解析)

长期建议

如果条件允许,最规范的方案是引入Redis做独立缓存层

A站 → Redis缓存
B站 → Redis缓存(不同db instance)

这样两站完全隔离,互不干扰,而且Redis的读写性能远高于数据库查询和文件I/O。

经验教训

  1. Discuz的sql缓存不能跨多站共享——这是设计上单站点用的
  2. 乒乓球效应(一方正常→另一方必挂) 的根因=并发写入同一个数据库表导致的duplicate key
  3. 清缓存能临时恢复但会引爆对方 — 因为清完缓存后两边同时重建,冲突概率更高
  4. 改文件缓存是最快的隔离方案,不改架构不停机,一行配置搞定

记一次折腾了一整晚的排查经历,希望能帮到遇到同样问题的站长。

Last modification:May 28th, 2026 at 12:19 am
如果觉得我的文章对你有用,请随意赞赏