配置表单点了保存却"薛定谔生效":我踩过的缓存三件套连环坑
上周给后台加了个「站点公告」配置页,前端表单提交、后端入库、页面回显,一气呵成。结果运营同事改完公告文案,前台刷新了十几次还是旧内容。我盯着数据库里那条新记录,陷入了深深的自我怀疑。
先说我的第一版实现,简单到有点丢人:配置表就三字段 `key`、`value`、`type`,保存时 `UPDATE` 完直接 `return success('保存成功')`。前端调用配置接口时每次都 `SELECT * FROM config WHERE key = 'notice'`。本地测试毫无问题,因为就我一个人点,数据库连接池还没热起来呢。
上线后问题暴露了。我加了文件缓存,保存时清缓存,读取时先读缓存。想法很美好,直到我在本地复现了一个诡异场景:连续快速点两次保存,第二次的缓存清理居然被第一次的缓存写入给覆盖了。原来我用的 `Cache::set` 和 `Cache::delete` 不是原子操作,两个请求交错执行,最后缓存里躺着的是第一次的数据,数据库却是第二次的。用户看到的?取决于他命中了哪个缓存节点。
第二版我改成了「先清缓存再写库」,结果更惨。高并发下出现短暂的无缓存期,大量请求直接打到数据库,配置表被瞬间扫爆。MySQL 监控那个尖峰,比我心电图还刺激。
现在我的第三版是这么处理的:配置数据按 `key` 做 Hash 分桶,保存时走队列串行化,同时给缓存加版本号。读取时带版本号校验,版本不对直接穿透回源。代码丑了点,但至少不会再出现「数据库已更新,缓存还在回忆过去」的灵异事件。
还有个容易忽略的坑:ThinkPHP 的 `Config` 类在运行时会把 `config/` 目录下的配置和数据库配置合并。如果你像我一样图省事,在后台改完配置后调用 `Config::set('site.notice', $value)`,这个修改只存在于当前请求生命周期,FPM 下一个 worker 进程过来又是老配置。必须显式写文件或者走持久化缓存,别被框架的「临时设置」给骗了。
最后说个血泪教训:测试环境千万别开「缓存永不过期」。我曾经为了省事把缓存 TTL 设成 0,本地调试配置修改秒生效,上线后同样的代码在生产环境缓存死活不刷新,排查两小时才发现是 Redis 配置差异。现在我的 `.env` 里专门加了 `CACHE_FORCE_REFRESH=1` 的调试开关,提交代码前 grep 一遍,成了肌肉记忆。
你们后台配置保存都怎么保证缓存一致性的?我这种「版本号+队列」是不是过度设计了,有没有更轻量的套路?

