1. 什么是布隆過濾器?
布隆過濾器(BloomFilter)是由一個叫“布隆”的小伙子在1970年提出的,它是一個很長的二進制向量,主要用於判斷一個元素是否在一個集合中。
在介紹原理之前,要先講一下Hash函數的概念。
我們在Java中的HashMap,HashSet其實也接觸過hashcode()這個函數,
哈希函數是可以將任意大小的輸入數據轉換成特定大小的輸出數據的函數,轉換后的數據稱為哈希值。
哈希函數有以下特點:
a. 如果根據同一個哈希函數得到的哈希值不同,那么這兩個哈希值的原始輸入值肯定不同。
b. 如果根據同一個哈希函數得到的兩個哈希值相等,兩個哈希值的原始輸入值有可能相等,有可能不相等。
布隆過濾器是由一個很長的二進制向量和一系列的哈希函數組成。那么布隆過濾器是怎么判斷一個元素是否在一個集合中的呢?
假設布隆過濾器的底層存儲結構是一個長度為16的位數組,初始狀態時,它的所有位置都設置為0。
當有變量添加到布隆過濾器中,通過K個映射函數將變量映射到位數組的K個點,並把這K個點的值設置為1(假設有三個映射函數)。
查詢某個變量是否存在的時候,我們只需要通過同樣的K個映射函數,找到對應的K個點,
判斷K個點上的值是否全都是1,如果全都是1則表示很可能存在,
如果K個點上有任何一個是0則表示一定不存在。
布隆過濾器存在一定的誤判
不能刪除布隆過濾器里的元素
因為在位數組上的同一個點有可能有多個輸入值映射,如果刪除了會影響布隆過濾器里其他元素的判斷結果。
2. 布隆過濾器的優缺點
優點:
在空間和時間方面,都有着巨大的優勢。
因為不是存完整的數據,是一個二進制向量,能節省大量的內存空間,
時間復雜度方面,是根據映射函數查詢,假設有K個映射函數,那么時間復雜度就是O(K)。
因為存的不是元素本身,而是二進制向量,所以在一些對保密性要求嚴格的場景有一定優勢。
缺點:
存在一定的誤判。
存進布隆過濾器里的元素越多,誤判率越高。
不能刪除布隆過濾器里的元素。
隨着使用的時間越來越長,因為不能刪除,存進里面的元素越來越多,占用內存越來越多,誤判率越來越高,最后不得不重置。
3. 應用場景
用於緩解緩存穿透。
緩存穿透的問題主要是因為傳進來的key在Redis中是不存在的,那么就會直接打在DB上,造成DB壓力增大。
針對這種情況,可以在Redis前加上布隆過濾器,預先把數據庫中的數據加入到布隆過濾器中,
因為布隆過濾器的底層數據結構是一個二進制向量,所以占用的空間並不是很大。
在查詢Redis之前先通過布隆過濾器判斷是否存在,如果不存在就直接返回,
如果存在的話,按照原來的流程還是查詢Redis,Redis不存在則查詢DB。
這里主要利用的是布隆過濾器判斷結果是不存在的話就一定不存在這一個特點,
但是由於布隆過濾器有一定的誤判,所以並不能說完全解決緩存穿透,但是能很大程度緩解緩存穿透的問題。
4. 布隆過濾器插件
在Redis4.0后,官方提供了布隆過濾器的插件功能,布隆過濾器可以作為一個插件加載到Redis服務器直接使用。
安裝完Redis之后,下載插件,使用git命令拉取:
git clone https://github.com/RedisBloom/RedisBloom.git
拉取下來之后會得到一個RedisBloom的項目。
然后cd到文件夾/RedisBloom,使用make命令編譯。
編譯完成后生成一個redisbloom.so文件。
在啟動Redis時,加載布隆過濾器模塊到服務器中:
./src/redis-server --loadmodule /usr/local/RedisBloom/redisbloom.so
最后使用客戶端測試一下:
$ ./src/redis-cli 127.0.0.1:6379> bf.add user kg (integer) 1 127.0.0.1:6379> bf.add user ak (integer) 1 127.0.0.1:6379> bf.exists user ak (integer) 1 127.0.0.1:6379> bf.exists user tt (integer) 0
布隆過濾器的基本指令如下:
bf.add 添加元素到布隆過濾器
bf.exists 判斷元素是否在布隆過濾器
bf.madd 添加多個元素到布隆過濾器
bf.mexists 判斷多個元素是否在布隆過濾器
Java程序怎么操作
引入依賴
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.15.0</version> </dependency>
測試程序
public static void main(String[] args) throws Exception { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient client = Redisson.create(config); RBloomFilter<String> bloomFilter = client.getBloomFilter("user"); //嘗試初始化,預計元素55000000,期望誤判率0.03 bloomFilter.tryInit(55000000L, 0.03); //添加元素到布隆過濾器中 bloomFilter.add("kg"); bloomFilter.add("ak"); bloomFilter.add("tk"); bloomFilter.add("nk"); System.out.println("布隆過濾器元素總數為:" + bloomFilter.count());//布隆過濾器元素總數為:4 System.out.println("是否包含kg:" + bloomFilter.contains("tom"));//是否包含kg:true System.out.println("是否包含app:" + bloomFilter.contains("lei"));//是否包含app:false client.shutdown(); }