2026-11-10 · 土拨鼠动态
土拨鼠日志:缓存里存的 0,是怎么把真数据挡在门外的
今天排一个「经验加成」的 bug,查到最后根子不在算法,也不在配置,而在一个特别容易忽略的地方:缓存里存了一个 0,而且没过期,于是真实该算出来的新结果被这个 0 挡在了门外。这不是缓存本身坏,是「该不该信缓存」这条判断被写松了。
一、现象:新装备戴上了,加成还是 0
用户戴上有经验加成的道具,去打怪,道具该带来的加成还是 0。日志看一圈,几个关键的检测点一个都没触发——正常扫描、详情打印全都是空的。
第一反应是道具 ID 不在配置表里,或者扫描范围写窄了。但真正的原因要更细一层:
- 系统里有一个「数值缓存」,算过一次就把结果记下来,下次直接读;
- 缓存里记的是上一次算出来的 0(那时候玩家身上还没这件道具);
- 判断要不要重算的条件,只看「时间有没有过期」——没过期就不重扫,继续读那个 0。
于是新的装备、新的加成,全被这个没到期的 0 缓存给「吃掉」了。玩家实际戴了道具,系统却拿着旧答案说没有。
二、根子:把「没过期」当成了「一定对」
这里的核心错误是一句话:只要缓存没过期,就默认它是对的。但对很多场景来说,「没过期」只说明「时间还够」,不说明「内容还新鲜」。
具体到这次,问题出在判断逻辑上:
- 判断条件写成了「值 ≤ 0 而且缓存没到期」,两个条件都得满足才重扫;
- 可「值 ≤ 0」恰恰是「需要重扫」的信号,结果被「缓存没到期」这条给拦住了;
- 扫出来是 0 的时候,这个 0 还被原样写回了缓存,等于把「没扫到」也当成了一个可信结果,污染了后面的判断。
换句话说,「没算出东西」和「缓存里有个旧值」这两件事被混成了一件事。前者应该触发重扫,后者只是省一次计算,两者不能共享同一条逻辑。
三、修法:让「不新鲜」优先于「有缓存」
改法是把判断顺序反过来,并且把「0」从可信结果里剔除:
- 缓存只信「正向加成」:只有算出 > 0 的有效值才允许写进缓存,算出 0 不写,避免用「没扫到」覆盖掉真实状态;
- 「值 ≤ 0」无条件重扫:只要当前值是 0,就不管缓存到没到期,都重新扫一次装备栏;
- 到期判断单独一条:缓存过期同样触发重扫,两条路都能走到重算,谁先满足谁生效。
这样「缓存」的职责就清楚了:它是用来省掉一次无谓计算的,不是用来替系统做判断的。数据能不能信,永远以「刚算出来且 > 0」为准,而不是以「时间还没到」为准。
四、这套判断在土拨鼠里的落地
缓存一致性这事,在土拨鼠里是一条硬原则:
- 状态没探测出来,就老老实实写「尚未就绪」,绝不拿一个好看的初始值糊过去,探测通过才写「正常」;
- 长任务有进度卡,实时告诉你当前在第几步、卡在哪,不会出现「它好像算过但没结果」的糊涂账;
- 工具输出被截断会明确标注,不会让一份不完整的答案冒充完整答案。
想在功能页看到状态探测和自动化这块怎么设计,可以看 功能页;想先算一笔账,定价页 有按量与真实消耗的对照;想自己上手,直接 下载 装一个跑跑。
五、一个 30 秒自查
下次你遇到「明明改了东西,系统却还停在旧结果」,按顺序问三句:
- 这里有没有缓存?缓存判断是不是只看「时间到没到期」?
- 「没算出结果」和「缓存里的旧值」,是不是被当成同一件事处理了?
- 算出来是 0 的时候,会被写回缓存、当成真答案吗?
中任意一条,基本就是这种「幽灵旧值」的来源。缓存的本分是省时间,不是替你拍板;该重扫的时候,别让一个没过期的 0 挡在门口。
土拨鼠 Marmot