配置页那套"读写分离"的幻觉:我把缓存刷新写进了表单提交,结果配置项在后台和前台上演"平行世界"
上周给后台配置页加新功能,顺手重构了配置读写逻辑。原本想得很美:表单提交→写数据库→清缓存→返回成功。结果上线第二天,运营在群里甩了张截图——后台显示"新用户注册奖励 50 积分",前端页面还是老的 20 积分。两边各自为政,互不搭理。
排查过程挺魔幻的。我先是怀疑 Redis 集群同步延迟,连上实例一顿 `MONITOR`,发现写命令确实到了主节点。又猜是不是 ThinkPHP 的缓存标签没打对,翻 `config/cache.php` 看到 `tag_prefix` 也没动过。最后盯着 Network 面板发呆,才发现问题根本不在缓存层。
是我把缓存刷新位置放错了。代码大概长这样:
public function save(Request $request)
{
$data = $request->post();
// 先写库
ConfigModel::updateOrCreate(['key' => $data['key']], $data);
// 再清缓存
Cache::tag('system_config')->clear();
// 最后返回
return json(['code' => 1, 'msg' => '保存成功']);
}
看起来没毛病对吧?但前端表单用的是异步提交,我为了"优化体验",在 JS 里加了层本地状态缓存——用户点保存后,表格里那个数值立刻变成新输入的值,不等接口返回。而另一个同事维护的前端页面,走的是单独的 `getConfig('register_reward')` 接口,这接口里我为了"减少数据库压力",加了个 5 分钟的文件缓存。
所以真实链路是:后台表单→写库→清 Redis→返回成功。但前端页面的文件缓存纹丝不动,它根本不知道 Redis 里发生过什么。更坑的是,后台表格因为 JS 本地渲染,看起来"已经生效了",运营妹子反复确认了好几遍,直到有用户截图问"为什么注册只给 20"才暴露。
现在我的配置读写改成了三级联动:数据库是底账,Redis 是热层,但任何写入操作必须连带扫一遍"已知缓存入口"。文件缓存、本地 Storage、甚至某些页面里的 `window.__INITIAL_STATE__`,全部列进刷新清单。代码丑了不少,至少不会再出现"后台以为改了,前台不知道"的薛定谔配置。
还有个副产品:我在配置保存按钮旁边加了个小字提示"预计 30 秒内全站生效",既是给运营看的,也是给自己留的台阶。做后台的都知道,"保存成功"这四个字太有欺骗性了——它只代表数据库那一步过了,不代表用户看得见。