一、缓存穿透
项目中的热点数据我们一般会放在 redis 中,在数据库前面加了一层缓存,减少数据库的访问,提升性能。但如果,请求的 key 在 redis 中并不存在,那请求还是会抵达数据库,这就叫缓存穿透。
我们无法避免缓存穿透,因为数据库中的数据要全部放到 redis 中不太现实,也不可能保证数据库数据和 redis 中的数据做到实时同步。但我们可以避免高频的缓存穿透。
避免高频缓存穿透的办法:
-
做好参数检验,对于一些非法参数直接挡掉,比如 id 为负数的请求直接挡掉;
-
缓存无效的 key,比如某次请求的 key 在数据库中不存在,那就将其缓存到 redis 并设置过期时间,但是这种办法不好,假如黑客每次请求都用不同的 key,那 redis 中的无用数据就会很多;
-
使用布隆过滤器;
二、布隆过滤器
1. 过滤器的作用
上面说了,如果大量不存在的 key 请求过来,还是会直接请求到数据库,如果我们能在请求数据库之前判断这个 key 在数据库到底存不存在,不存在就直接返回相关错误信息,那就可以解决缓存穿透的问题。
如何在不请求数据库的前提下判断这个 key 在数据库中存不存在呢?这就需要用到过滤器。难不成又要将数据库的所有数据缓存到过滤器中吗?当然不是,如果这样,那和将所有 key 缓存到 redis 就没啥区别了。接下来看看布隆过滤器是怎么做的。
2. 布隆过滤器原理
布隆过滤器使用了布隆算法来存储数据,明确一点, 布隆算法存储的数据不是 100% 准确的,即布隆过滤器认为这个 key 存在,实际上它也有可能不存在,如果它认为这个key 不存在,那么它一定不存在。 布隆算法是通过一定的错误率来换取空间的。
布隆算法通过 bit 数组 来标识 key 是否存在。怎么做的呢?key 经过 hash 函数的运算,得到一个数组的下标,然后将对应下标的值改成1,1就表示该 key 存在。这个 hash 函数要满足的条件有:
-
对 key 计算的结果必须在 [0, bitArray.length - 1] 之间;
-
计算出来的结果分布要足够散列;
因为要进行 hash 计算,所有布隆算法的错误率是由于 hash 碰撞导致的。所以降低 hash 碰撞的概率就可以降低错误率。怎么降低 hash 碰撞的概率呢?两种办法:
-
加大数组的长度:数组长度更长,hash 碰撞的概率自然更小;
-
增加 hash 函数的个数:假如 key 为 10 的数据,第一个 hash 函数计算出来的下标是 1,第二个 hash 函数计算出来的是 4,第三个 hash 函数计算出来的是 10,那么就要 1,4,10 这三个下标所对应的值都得是 1,才会认为 key 存在,故而也可以减少误判的情况。
3. 为什么要用 bit 数组
因为节省空间。1k = 1024byte = 1024 * 8 bit = 8192bit,即长度为8192的bit数组只需要1kb的空间。
4. 怎么用
业界大佬和民间大神已经造了很多轮子了,这里主要说三种,具体用法大家看一下相关 api 即会了。
-
可以使用 guava 中的布隆过滤器;
-
使用 hutools 工具包中的布隆过滤器;
-
redis 有 bitMap,也可以用作布隆过滤器,推荐使用 redisson 构造布隆过滤器;
三、hutools 中的布隆过滤器源码分析
这里带大家分析一下 hutools 中的布隆过滤器源码,看看人家怎么实现的。用法如下:
public static void main(String[] args) {
BitMapBloomFilter bloomFilter = new BitMapBloomFilter(5);
bloomFilter.add("aa");
bloomFilter.add("bb");
bloomFilter.add(