[TOC]
什么是缓存穿透
正常情况下,查询的数据都存在,如果请求一个不存在的数据,也就是缓存和数据库都查不到这个数据,每次都会去数据库查询,这种查询不存在数据的现象我们称为缓存穿透
穿透带来的问题
如果每次都拿一个不存在的id去查询数据库,可能会导致你的数据库压力增大
解决办法
- 缓存空值
之所以发生穿透,是因为缓存中没有存储这些数据的key,从而每次都查询数据库
我们可以为这些key在缓存中设置对应的值为null,后面查询这个key的时候就不用查询数据库了
当然为了健壮性,我们要对这些key设置过期时间,以防止真的有数据 - BloomFilter
BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中
我们把有数据的key都放到BloomFilter中,每次查询的时候都先去BloomFilter判断,如果没有就直接返回null
注意BloomFilter没有删除操作,对于删除的key,查询就会经过BloomFilter然后查询缓存再查询数据库,所以BloomFilter可以结合缓存空值用,对于删除的key,可以在缓存中缓存null
缓存击穿
什么是缓存击穿
在高并发的情况下,大量的请求同时查询同一个key时,此时这个key正好失效了,就会导致同一时间,这些请求都会去查询数据库,这样的现象我们称为缓存击穿
击穿带来的问题
会造成某一时刻数据库请求量过大
解决办法
1.使用互斥锁(mutex key)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
1 | public String get(key) { |
2.”永远不过期”**
这里的“永远不过期”包含两层意思:
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
1 | String get(final String key) { |
缓存雪崩
什么是缓存雪崩
当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了
解决办法
- 如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效
- ehcache本地缓存 + Hystrix限流&降级
ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵
使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑 - 在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了
- 热点数据永不过期,参考上文