一.為什么要用到布隆過濾器?
緩存穿透:查詢一條不存在的數據,緩存中沒有,則每次請求都打到數據庫中,導致數據庫瞬時請求壓力過大,多見于爬蟲惡性攻擊因為布隆過濾器是二進制的數組,如果使用了它,可以把需要的對應的全量業務數據的key值全放到布隆過濾器中,內存占用較小,這樣就不會擊穿數據庫
二.數據訪問流程
布隆過濾器–>redis緩存–>數據庫
三.原理:
使用二進制數組,對要存入的key進行多次hash,分配到數組的不同位置,數組的值從0改成1,查詢的時候也進行多次hash,命中到數組的位置的值都是1則key可能存在,如果有一個不是1則key一定不存在
四.缺點
缺點1:
存在誤判的情況,比如:hello和你好經過hash后的值一樣,出現hash沖突。比如“你好”是存在,“hello”不存在,但他們的hash值一樣,就會通過
解決:
1.根據數據量大小設置誤判率。誤判率越小,使用的hash函數越多,hash沖突越小,但計算的時間增加,內存占用更多
2.增大布隆過濾器數組
缺點2:
刪除困難: 布隆過濾器無法直接刪除已添加的元素,因為刪除操作會影響其他元素的判斷結果。在刪除元素時,可能會導致一些位置的位被置為 0,從而影響其他元素的判斷結果,增加誤判的概率
解決:
只能是重新載入布隆過濾器了。開發定時任務,每隔幾個小時,自動創建一個新的布隆過濾器數組替換老的。注意,是幾小時,這也就意味著,這一方法在高并發的情況下有巨大缺點
五.代碼實踐
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.27.2</version></dependency>
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** redisson客戶端使用Bloom過濾器攔截無效請求,解決緩存穿透* 項目初始化的時候初始化布隆過濾器(例如把商品編號都放進去),* 用戶發過來的請求(帶商品編號)先經過布隆過濾器過濾,過濾掉的返回null**/
@Configuration
public class RedissinConfig {@Value("${redisson.address}")private String addressUrl;@Value("${redisson.password}")private String password;@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress(addressUrl).setRetryInterval(5000).setTimeout(10000).setDatabase(0).setPassword(password).setConnectTimeout(10000);return Redisson.create(config);}/*** 可以不注冊成bean,直接使用redissonClient來創建Bloom過濾器* @param redissonClient* @return*/@Beanpublic RBloomFilter<String> bloomFilter(RedissonClient redissonClient) {RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom");bloomFilter.tryInit(1000000L, 0.01);return bloomFilter;}}
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest(classes = DataApplication.class)
@Slf4j
public class BloomTest {@Autowiredprivate RedissonClient redissonClient;RBloomFilter<String> bloomFilter;@Beforepublic void init(){bloomFilter = redissonClient.getBloomFilter("bloom");bloomFilter.tryInit(10000L, 0.01);}/*** 測試耗時和誤判率*/@Testpublic void test(){long start = System.currentTimeMillis();int total=10000;for (int i = 0; i < total; i++){bloomFilter.add(String.valueOf(i));}long end= System.currentTimeMillis();log.info("插入用時:{}",end-start);int count = 0;for (int i = total; i < total+1000; i++){if(bloomFilter.contains(String.valueOf(i))){count++;log.info("誤判了:{}",i);}}log.info("插入用時:{}",System.currentTimeMillis()-end);log.info("count為:{},誤判率:{}",count,(count*1.0/1000));}/*** 獲取count*/@Testpublic void countBloomTest(){long count = bloomFilter.count();log.info("count為:{}",count);}/*** 刪除過濾器*/@Testpublic void delBloomTest(){bloomFilter.delete();}}
1萬的數據插入差不多用了4分鐘,查詢1000個數據用了24秒
在redis可視化中看到的數據如下
六.源碼解析
布隆過濾器初始化源碼,保存的信息(size,hashIterations,expectedInsertions,falseProbability),跟上圖匹配,計算位數組大小和哈希個數是數學計算公式
參考:
https://zhuanlan.zhihu.com/p/622044226