目录
缓存击穿
一般我们会对缓存的key设置过期时间,在高并发下,如果在某一时刻这个key刚好过期,此时持续的大并发请求都会穿破缓存,直接命中DB,就像在一个屏障上凿开了一个洞。
缓存击穿的解决方案
1) 通过分布式锁或者队列,使得同一个key只允许一个线程到数据库查询
2)定时预先更新缓存,避免发生缓存失效的情形
不过一般情况下单个缓存的击穿很难对数据库造成压垮性的压力
缓存雪崩
缓存雪崩是指缓存中的大量的key在同一时间失效,导致所有的请求都命中DB,使得其无法负载。简单来说就是大量key同时发生缓存击穿的情形
缓存击穿针对的是某一个设置了过期时间的缓存,缓存雪崩针对的是很多设置了过期时间的缓存
缓存雪崩的解决方案
1) 通过分布式锁或者队列,使得同一个key只允许一个线程到数据库查询
2)定时预先更新缓存,避免同时发生缓存失效的情形
3)通过加随机数,使得不同的key在不同的时间过期
4)设置缓存永不过期
缓存穿透
缓存穿透,是指查询一个数据库一定不存在的数据。通常redis没有缓存的时候,我们就会去访问数据库获取数据,此时如果数据库也不存在数据,那么每次对该key的请求都会命中数据库,在高并发情形下,就会导致数据库压力过大从而崩溃。
如果每次请求的数据都是同一个key,那么可以通过缓存空数据或者特殊字符串,比如&&来避免缓存穿透(需要对这个缓存设置一个过期时间,否则数据库新增了这一数据也无法获取到对应值)
但是如果每次请求的key都不一样,那么我们就需要其他的方法来判断一个key是否存在于数据库中,不能直接通过查询数据库的方式来判断
因为缓存的往往是大量的数据,直接存储所有的数据显然费空间又费时间,为了节省存储空间,可以采用BitMap(位图)的方式来标记这个key是否出现过
对于每一个元素,我们通过哈希函数对key进行运算,得到一个下标,将其对应的位图上的值改为1,就表示该元素在这个集合存在。因为哈希碰撞的存在,我们可以采用
1.扩大数组的长度或者说位图容量 (空间消耗加大)
2.引入很多个哈希函数 (耗时)
来减小哈希碰撞的概率
实际使用中,我们既要节省空间,又要很高的计算效率,就必须在位图容量和函数个数之间找到一个最优解,布隆过滤器就帮助我们解决了这个问题
布隆过滤器
1970年由Burton Howard Bloom提出,其目的就是为了快速判断一个集合里是否存在某一个元素
bloom过滤器实际是由一个长位图和k个不同的哈希函数组成的
其过程主要分为三步:
1.创建一个二进制数组,所有的值都初始化为0
2.将集合里的每一个元素都通过k个hash函数运算其在位图的下标
3.将对应的下标的值改为1
判断某个元素是否存在于集合的时候,我们只需要判断元素通过k个hash函数计算出来的下标在位图上是否都为1就可以了,如果全部为1,则表示该元素很可能存在;否则该元素就一定不存在(这是因为hash碰撞无法避免,会导致有一定的误判率,会将不存在的元素误判为存在)
这种把本来不存在布隆过滤器中的元素误判为存在的情况,我们把它叫做假阳性(False Positive Probability,FPP)。
Funnel<String> funnel = new Funnel<String>() {
@Override
public void funnel(String from, PrimitiveSink into) {
into.putString(from, Charsets.UTF_8);
}
};
// 创建符合条件的布隆过滤器 误判率为0.1%,集合元素最大预计为一万
BloomFilter<String> filter = BloomFilter.create(funnel, 10000, 0.001);
filter.put("chenpp");
System.out.println(filter.mightContain("chenpp"));
System.out.println(filter.mightContain("chenpp11"));
如果在缓存中使用布隆过滤器,其流程图如下:
首先每次在数据库新增数据的时候,都需要把数据同步到BloomFilter中
在查询数据的时候,先在布隆过滤器查询,如果返回说没有,那数据库中也肯定没有,就不需要查了,只有返回为存在的数据才需要查询缓存和DB