什么是缓存穿透
缓存没有生效,大量请求访问某一个key的数据 , 实际缓存中并不存在该key的缓存 , 然后都转而降级查询DB 去了,最终的结果是DB查询压力增大。
关键词:缓存未生效,导致DB压力骤增
可能的发生场景
- 某一个很偏僻的数据, 因为程序bug或者某种重试机制 , 或者某一活动突然请求查询该数据 , 使用了这种接口;
- 操作有误 ,活动前提是需要先做一个操作产生数据然后活动才能开始;
- 设计上不合理,有些数据永远不会变,所以就直接放到缓存中永不过期,但是又是热点数据,后来因为某些问题导致缓存丢失;
- 缓存生成本身需要耗费大量的时间和资源;
- 黑客攻击, 故意访问不存在的数据,拖垮系统 ;
- 爬虫爬取数据
解决办法
- 对没有命中的空值进行缓存,过期时间根据实际情况设置
- 尽量避免人为操作失误导致,对于可能发生热点访问的缓存数据多演练
- 系统监控要做好,尽量保证在第一时间感知问题并解决,识别出爬虫并禁止访问/返回固定数据
什么是缓存雪崩
缓存失效后由于需要重新生成缓存,在生成缓存的时间内接收到的请求大量请求,引起系统性能骤然下降。
解决办法
更新锁机制
对缓存更新进行加锁保护,保证只有一个线程能够进行缓存的更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
分布式集群的业务系统要实现更新锁机制,需要使用分布式锁,如zookeeper。
后台更新机制
缓存本身的有效期设置为永久,后台线程定时更新缓存。
后台定时更新机制需要考虑一种情况,当缓存系统内存不足时需要踢掉一些缓存数据,从缓存被踢掉,到下一次定时更新缓存的这段时间内,业务线程读取缓存返回空值,会给人一种数据丢了的感觉,解决方式如下:
- 后台线程除了定时定时更新缓存外还需要频繁地读取缓存,如果发现缓存被踢了,就立刻更新缓存。(读取间隔不宜过长,否则业务线程还是读不到数据,用户体验一般)
- 业务线程发现缓存失效后,通过消息队列发送消息通知后台线程更新缓存。可能存在多个业务线程发消息更新缓存的情况,不过更新线程可以判断缓存是否已经存在,存在就不会执行更新操作,这种方式虽然实现复杂度稍高一些,但是用户体验好,更新及时。
后台更新既适合单机多线程的情况也适合分布式集群的场景,相比更新锁机制更简单一些。
后台更新机制还适合业务刚上线的时候缓存预热。
缓存预热 是指系统上线后将相关的缓存直接加载到缓存系统,而不是等待用户方位才触发缓存加载。
缓存热点
虽然缓存系统本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大。例如,某明星微博发布“我们”来宣告恋爱了,短时间内上千万的用户都会来围观。
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力
以微博为例,对于粉丝数超过 100 万的明星,每条微博都可以生成 100 份缓存,缓存的数据是一样的,通过在缓存的 key 里面加上编号进行区分,每次读缓存时都随机读取其中某份缓存。
缓存副本设计有一个细节需要注意,就是不同的缓存副本不要设置统一的过期时间,否则就会出现所有缓存副本同时生成同时失效的情况,从而引发缓存雪崩效应。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值。