1. 簡介
布隆過濾器是防止快取穿透的方案之一。布隆過濾器主要是解決大規模資料下不需要精確過濾的業務場景,如檢查垃圾郵件地址,爬蟲URL地址去重, 解決快取穿透問題等。
布隆過濾器:在一個存在一定數量的集合中過濾一個對應的元素,判斷該元素是否一定不在集合中或者可能在集合中。它的優點是空間效率和查詢時間都比一般的演算法要好的多,缺點是有一定的誤識別率和刪除困難。
想詳細瞭解的,可以檢視我的另一篇部落格Redis-快取穿透/擊穿/雪崩。
2. guava 實現
google的guava工具類已經幫我們造好了輪子,通過例項來感受一下。
2.1 匯入依賴
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
2.2 BloomFilterTest
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
/**
* 布隆過濾器簡單實現
* @author ludangxin
* @date 2021/8/16
*/
@Slf4j
public class BloomFilterTest {
/**
* 預計要插入元素個數
*/
private static final int SIZE = 1000000;
/**
* 誤判率
*/
private static final double FPP = 0.01;
/**
* 布隆過濾器
*/
private static final BloomFilter<Integer> BLOOMFILTER = BloomFilter.create(Funnels.integerFunnel(), SIZE, FPP);
public static void main(String[] args) {
//插入資料
for (int i = 0; i < 1000000; i++) {
BLOOMFILTER.put(i);
}
int count = 0;
// 過濾判斷
for (int i = 1000000; i < 3000000; i++) {
if (BLOOMFILTER.mightContain(i)) {
count++;
log.info(i + "誤判了");
}
}
log.info("總共的誤判數:" + count);
}
}
2.3 啟動測試
如上程式碼,我們設定了0.01的誤差,過濾判斷時從1000000到3000000,誤判了2 * 20000000 ≈ 20339 符合預期。
.....
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999004誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999045誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999219誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999699誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999753誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999838誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999923誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 2999928誤判了
21:40:21.529 [main] INFO com.ldx.redisson.controller.BloomFilterTest - 總共的誤判數:20339
2.4 小節
guava的工具包雖然好用,但是資料集是儲存在jvm中的,分散式環境下依然沒法使用。
3. redisson 實現
3.1 匯入依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.1</version>
</dependency>
3.2 BloomFilterWithRedisson
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* redisson 布隆過濾器實現
*
* @author ludangxin
* @date 2021/8/16
*/
@Slf4j
@RestController
@RequestMapping("bloomFilter")
@RequiredArgsConstructor
public class BloomFilterWithRedisson {
private final RedissonClient redissonClient;
/**
* 預計要插入元素個數
*/
private static final long SIZE = 1000000L;
/**
* 誤判率
*/
private static final double FPP = 0.01;
/**
* 自定義布隆過濾器的 key
*/
private static final String BLOOM_FILTER_KEY = "bloomFilter";
/**
* 向布隆過濾器中新增資料, 模擬向布隆過濾器中新增10億個資料
*/
@GetMapping
public void filter() {
// 獲取布隆過濾器
RBloomFilter<Integer> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_KEY);
// 初始化,容量為100萬, 誤判率為0.01
bloomFilter.tryInit(SIZE, FPP);
// 模擬向布隆過濾器中新增100萬個資料
for (int i = 0; i < SIZE; i++) {
bloomFilter.add(i);
}
int count = 0;
// 過濾判斷
for (int i = 1000000; i < 3000000; i++) {
if (bloomFilter.contains(i)) {
count++;
log.info(i + "誤判了");
}
}
log.info("size:" + bloomFilter.getSize());
log.info("總共的誤判數:" + count);
}
}
3.3 啟動測試
由於機器效能有限,又是單機環境,所以程式沒有跑完。
但由此也可以看出,基於redis的布隆過濾器雖然解決了分散式問題,但是效能和guava bloomfilter沒法比。