头图

背景

Leader:运维中心发现系统A收到大量请求,查询的数据在缓存中和数据库中都不存在,导致每次查询这条数据都会透过缓存,直接查库,最后返回空,引起缓存穿透,有没有办法缓解下这个问题?
Coder:这个比较简单,应用层缓存空对象就可以解决。
Leader:这也带来新的问题,缓存里保存了大量的空值的key,占用更多存储空间。
Coder:不行的话,那就只能考虑利用布隆过滤器了。

知识储备

1、缓存穿透:是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会透过缓存,直接查库,最后返回空。
解决办法:缓存空对象;使用布隆过滤器。
布隆过滤器是由一个固定大小的二进制向量或者位图和一系列映射函数组成的。在初始状态时,对于长度为m的位数组,它的所有位都被置为0,当有变量被加入集合时,通过k个映射函数将这个变量映射成位图中的k个点,把它们置为1。查询某个变量的时候, 我们只要看看这些点是不是都为1就可以大概率知道集合中有没有它了。如果这些点有任意一个为0,则被查询变量一定不存在;如果都是1,则被查询变量很可能存在。为何不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是有碰撞的。

2、缓存击穿:是指当缓存中某个热点数据过期了,在该热点数据重新载入缓存之前,有大量的查询请求穿过缓存,直接查询数据库。
解决办法:设置key永不过期;使用分布式锁,保证同一时刻只能有一个查询请求重新加载热点数据到缓存中,这样,其他的线程只需等待该线程运行完毕,即可重新从Redis中获取数据。

3、缓存雪崩:是指当缓存中有大量的key在同一时刻过期,或者Redis直接宕机了,导致大量的查询请求全部到达数据库,造成数据库查询压力骤增,甚至直接挂掉。
解决办法:第一种情况可将每个key的过期时间打散即可;第二种情况则使用redis的几种高可用方案部署。

实操

1.直接使用google发布的guava工具,引用依赖。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>

2.测试。

public class BloomFilterTest {
    private static int size = 1000000;
    private static double fpp = 0.01;
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
    private static int total = 1000000;

    public static void main(String[] args) {
        for (int i = 0; i < total; i++) {
            bloomFilter.put(i);
        }

        int count = 0;

        for (int i = total; i < total + 10000; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
            }
        }

        System.out.println("总共误判数:" + count);
        System.out.println("误判率:" + (1.0 * count) / 10000);
    }
}

3.结果:

总共误判数:98
误判率:0.0098

4.自定义简易布隆过滤器。

public class MyBloomFilter {
    private static final int DEFAULT_SIEZ = 256 << 22;
    private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};
    private static HashFunction[] functions = new HashFunction[seeds.length];
    private static BitSet bitSet = new BitSet(DEFAULT_SIEZ);

    public static void add(String value) {
        if (value != null) {
            for (HashFunction f : functions) {
                bitSet.set(f.hash(value), true);
            }
        }
    }

    public static boolean contains(String value) {
        if (value == null) {
            return false;
        }
        boolean ret = true;
        for (HashFunction f : functions) {
            ret = bitSet.get(f.hash(value));
            if (!ret) {
                break;
            }
        }
        return ret;
    }

    public static void main(String[] args) {
        for (int i = 0; i < seeds.length; i++) {
            functions[i] = new HashFunction(DEFAULT_SIEZ, seeds[i]);
        }

        for (int i = 0; i < 100000000; i++) {
            add(String.valueOf(i));
        }

        String id = "123456789";
        add(id);

        System.out.println(contains(id));
        System.out.println(contains("234567890"));
    }
}

class HashFunction {
    private int size;
    private int seed;

    public HashFunction(int size, int seed) {
        this.size = size;
        this.seed = seed;
    }

    public int hash(String value) {
        int result = 0;
        int len = value.length();

        for (int i = 0; i < len; i++) {
            result = seed * result + value.charAt(i);
        }
        int r = (size - 1) & result;
        return r;
    }
}

背风
1 声望0 粉丝