缓存雪崩和缓存穿透的现象和解决方案+boomfilter的引入

缓存雪崩和缓存穿透的现象和解决方案
1.什么是缓存雪崩
由于各种原因,redis集群整体宕机,缓存不可用,导致所有的请求都直接发到数据库,数据库抗不了这种高并发直接挂掉,数据库挂掉,就可能引发整体系统不可用.
2.缓存雪崩的解决方案
事前:redis做好高可用架构
事中:开启本地ehcache缓存+hystrix/sentinal熔断 限流 降级的保护机制,保证数据库不会被请求搞挂机,一个请求顺序为:先查ehcache->redis->hystrix/sentinal保护机制->mysql
事后:redis开启持久化机制(RDB/AOF),保证缓存挂了可以根据磁盘文件快速恢复

3.什么是缓存穿透
外来的黑客攻击,发起大量数据库没有的id进行查询,这样既然数据库都没有,缓存肯定没有,就是直接穿过缓存查询数据库,大量请求过来也会把数据库搞挂机

4.缓存穿透的解决方案
很简单,缓存空值,如果数据库没查到的值,直接根据id在缓存中设置一个NUKNOW的值,这样下次请求就直接走缓存.
但是这样也会出现一个问题,缓存会存储大量的空值,以及数据查询还会走数据库,可以在加一层BoomFilter(布隆过滤器),具体原理就是先把数据库的数据装载一份到boomfilter,查询的时候先判断redis中该值在不在,如果不在就通过boomfilter进行判断是否在数据库中,判断也没有就直接返回空值.这样可以避免大量的缓存空值,也能有效避免黑客攻击,不好的地方就是:
代码复杂度增大,
需要另外维护一个集合来存放缓存的Key,
布隆过滤器不支持删值操作
布隆过滤器有一定的误判率,想要误判率越小,那么所需的空间越大
不过正确的数据都是可以通过的,对于不存在的key,可能会出现误判,就是返回也存在数据库的情况,这个可以根据实际情况进行调整误判的概率;

附boomfilter的示例代码:

引入依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

import java.math.BigDecimal;

public class BloomFilterTest {
    private static int size = 1000000;
    //fpp代表误判率控制在多少
    private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), size,0.01);

    public static void main(String[] args) {
        // 初始化1000000条数据到过滤器中
        for (int i = 0; i < size; i++) {
            bf.put(i);
        }

        int failCount=0;
        // 匹配已在过滤器中的值,是否有匹配不上的
        for (int i = 0; i < size; i++) {
            if (!bf.mightContain(i)) {
                System.out.println("未匹配上的数据");
                failCount++;
            }
        }
        System.out.println("未匹配上的正确数据量:"+failCount);

        // 匹配不在过滤器中的10000个值,有多少匹配出来
        int count = 0;
        for (int i = size; i < size + 10000; i++) {
            if (bf.mightContain(i)) {
                count++;
            }
        }
        BigDecimal bigDecimal = new BigDecimal(count);
        BigDecimal divide = bigDecimal.divide(new BigDecimal(10000), 5, BigDecimal.ROUND_HALF_UP);
        System.out.println("误命中的数量:" + count+",误命中的概率:"+divide);

    }
}
原文地址:https://www.cnblogs.com/bin-zhao/p/14361227.html