问题描述
论坛系统的首页推荐贴是一个访问非常频繁的数据,且更新频率较低。
为了减少中间层把大量无意义的请求发往后端接口,3个月前在缓存上做了如下调整:中间层负责记录缓存,后端只负责从 DB 中查出数据,需要更新时再去删除此缓存(两者连接了同一个 Redis)。
这就埋下了隐患,当缓存过期时,会迸发大量的请求抵达后端,直接对 DB 造成压力。
然而该 SQL 在正常走索引的情况下查询速度是毫秒级,另一方面并发量没有大到夸张。运转看似正常,没再继续做改进,也未能预估到风险的严重性。
有经验的人肯定知道,如果后端的 SQL 查询速度较慢,或因网络拥堵,中间层不能及时更新数据到缓存,必然会堆积大量的SQL查询,服务可以瞬间宕机。
论坛项目交接他人之后,其优化的过程中发现这条索引影响了其他业务的查询,也遗漏了对热数据的影响检测,对该索引执行了删除。像是启动了定时炸弹,一周后缓存自然过期,最终结果是“击穿”了 20 秒的慢查询。。。
解决方案
当发现重启 MySQL 后又迅速死掉,基本可以定位到问题是缓存穿透所至。
- 立即为推荐贴相关的 key 设置任意非空缓存,停掉中间层请求。
- 重启 MySQL。
- 恢复之前的索引,保证该接口下的 SQL 查询处于正常水平。
至此服务回复正常运转,但随着旧索引的添加,其他业务的优化也遭到了负影响。即刻开始了进一步优化。
后续优化
- 重新为此处低效的SQL设计索引。
- 使用锁(Redis setnx)来控制热数据的缓存重建,保证缓存过期时只有一个进程在访问数据库。
- 部分热数据不再过期。为了便于管理,将冷热数据存在 Redis 不同的 db 下。
- 删除那条“导火索”索引,恢复其他业务的优化。
总结
采用删除缓存来更新数据的方案是“学”自项目其他业务,最大优点是简化了事务中重建缓存的方式,虽然并不能真正避免脏读等问题。
热数据切不可这样操作,需要完善的缓存重建方案做后盾。
开发阶段周全地评估可能风险是十分必要的,这需要更多经验,也要努力避开思维惯性。