接口缓存不是一开就快
2016 年做接口优化时,缓存几乎是最容易被提起的方案。接口慢了,加缓存;数据库忙了,加缓存;列表页扛不住了,还是加缓存。可真正做过几轮之后,我反而越来越谨慎,因为缓存很少是“开了就完事”的提速按钮。
我自己印象最深的是,有几次线上问题不是因为没缓存,而是因为缓存加得太随意,结果系统表面更快,真实行为反而更难预测。
第一个坑:把错误结果也缓存了
最容易发生的情况是,后端接口临时异常,代码却没有把失败响应和成功响应区分开,一股脑写进缓存。接下来十分钟、半小时里,用户拿到的都是同一份错误数据。
这类问题最烦的地方在于,它会伪装成“系统很稳定”。接口响应很快,日志量也不一定大,但用户体验是持续错误的。
所以后来我只要做缓存,都会先问一句:失败结果能不能进缓存?多数情况下答案都应该是不能。
第二个坑:key 没带版本,也没带用户态
一开始为了图省事,很容易把列表接口直接写成 article_list、home_feed 这种 key。
问题是,同一份列表可能跟用户身份、分页参数、筛选条件甚至客户端版本都有关。
如果 key 没把这些边界带进去,就会出现两种后果:
- 不同用户看到了不该相同的数据
- 你改了返回结构,老缓存却还在继续被读
后来我会强迫自己在 key 里把“谁在看”“看什么版本”“是什么条件”至少表达出一部分,否则宁可先不缓存。
第三个坑:只设计读取路径,不设计失效路径
很多缓存方案的文档只写“命中后直接返回”,却没认真写“内容更新后谁负责让缓存失效”。
结果就是后台已经把文章改好了,前台还在读旧内容;运维重启服务也没用,因为缓存层根本没被触发更新。
我后来更愿意接受一个稍微短一点的 TTL,也不愿意把失效职责写得模糊不清。因为缓存真正难的从来不是读,而是变更之后的一致性。
我后来的最小规则
经历过这些坑后,我会用一套更保守的规则约束自己:
- 成功响应和失败响应分开处理
- key 至少要能表达用户态、条件或版本中的关键维度
- 写缓存之前先定义谁来删、谁来更新、最晚多久自然过期
- 如果一个接口经常变,先优化 SQL 或结构,再考虑缓存
这套规则不会让缓存“看起来很猛”,但能避免系统被一层不透明的旧数据拖住。
结尾
缓存确实能提速,但它提速的前提是你已经知道自己在缓存什么、准备承担什么一致性代价。2016 年那几次踩坑之后,我对缓存的态度就变了:它不是性能补丁,而是一层新的系统设计。你只有把它当成设计问题来看,缓存才会真正帮你,而不是反过来绑住你。
