0%

缓存不当至服务崩溃

问题描述

论坛系统的首页推荐贴是一个访问非常频繁的数据,且更新频率较低。

为了减少中间层把大量无意义的请求发往后端接口,3个月前在缓存上做了如下调整:中间层负责记录缓存,后端只负责从 DB 中查出数据,需要更新时再去删除此缓存(两者连接了同一个 Redis)。

这就埋下了隐患,当缓存过期时,会迸发大量的请求抵达后端,直接对 DB 造成压力。

然而该 SQL 在正常走索引的情况下查询速度是毫秒级,另一方面并发量没有大到夸张。运转看似正常,没再继续做改进,也未能预估到风险的严重性。

有经验的人肯定知道,如果后端的 SQL 查询速度较慢,或因网络拥堵,中间层不能及时更新数据到缓存,必然会堆积大量的SQL查询,服务可以瞬间宕机。

论坛项目交接他人之后,其优化的过程中发现这条索引影响了其他业务的查询,也遗漏了对热数据的影响检测,对该索引执行了删除。像是启动了定时炸弹,一周后缓存自然过期,最终结果是“击穿”了 20 秒的慢查询。。。

解决方案

当发现重启 MySQL 后又迅速死掉,基本可以定位到问题是缓存穿透所至。

  1. 立即为推荐贴相关的 key 设置任意非空缓存,停掉中间层请求。
  2. 重启 MySQL。
  3. 恢复之前的索引,保证该接口下的 SQL 查询处于正常水平。

至此服务回复正常运转,但随着旧索引的添加,其他业务的优化也遭到了负影响。即刻开始了进一步优化。

后续优化

  1. 重新为此处低效的SQL设计索引。
  2. 使用锁(Redis setnx)来控制热数据的缓存重建,保证缓存过期时只有一个进程在访问数据库。
  3. 部分热数据不再过期。为了便于管理,将冷热数据存在 Redis 不同的 db 下。
  4. 删除那条“导火索”索引,恢复其他业务的优化。

总结

采用删除缓存来更新数据的方案是“学”自项目其他业务,最大优点是简化了事务中重建缓存的方式,虽然并不能真正避免脏读等问题。

热数据切不可这样操作,需要完善的缓存重建方案做后盾。

开发阶段周全地评估可能风险是十分必要的,这需要更多经验,也要努力避开思维惯性。