布隆過濾器的原理、redis布隆過濾器的安裝和使用

布隆過濾器

本質上布隆過濾器是一種數據結構,可以用來告訴你 “某樣東西一定不存在或者可能存在”。
如果我們平時常用的List,set,map ,tree 來實現同樣的效果,set和map都是採用map的數據結構,時間複雜度是O1級別。但是map 需要保存所有存在的數據,當數據量非常大的時候,消耗的內存是非常大的。布隆過濾器可以極大減小這種內存消耗,但同樣會造成部分不存在的數據,誤判爲存在。可以通過底層維護的bit數組的長度,來調整數據的誤判率。

布隆過濾器是一個 bit 向量或者說 bit 數組:
當我們輸入 “線性代數” ,通過計算3個不同的hash計算公式,將值映射到1,3,5這個幾個數組上面去,當我們輸入 “線性代數”這 個值,映射到1,3,5上面的時候,發現這幾個位置都被映射過了,就可以判斷 線性代數 代數可能存在。
在這裏插入圖片描述

當我們再輸入 “高等數學” ,通過計算3個不同的hash計算公式,將值映射到1,8,9這個幾個數組上面去,這個時候 1,3,5,8,9這幾個位置都有被映射過了。

在這裏插入圖片描述
如果我們判斷 “概率論” 是否存在的時候,將值進行三種hash運算映射到1,2,3 。發現2沒有被映射過,所以可以判斷 “概率論” 不存在。 但是也有可能當輸入“線性代數” 到映射到1,5,8這三個位置都被映射過了,此時發生誤判,認爲 “線性代數” 可能存在。

利用布隆過濾器減少磁盤 IO 或者網絡請求,因爲一旦一個值必定不存在的話,我們可以不用進行後續昂貴的查詢請求。一般運用場景防止緩存穿透,對垃圾郵件、短信的過濾,推薦非重複消息。

布隆過濾器中的google過濾器相對於redis布隆過濾器有以下的缺點
基於JVM內存的一種布隆過濾器
重啓即失效
本地內存無法用在分佈式場景
不支持大數據量存儲

redis布隆 也有相對於google過濾器,需要依賴redis,性能稍差的缺點。

redis布隆過濾器的安裝

其中添加過濾器依賴於redis單機部署redis集羣(4.0.14版本)
1.下載redisbloom插件(redis官網下載即可)

https://github.com/RedisLabsModules/redisbloom/

這裏使用v1.1.1版本

//新建目錄
mkdir redisbloom

cd redisbloom

//下載redis 插件
wget https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz

//解壓文件
tar -zxvf v1.1.1.tar.gz

//進入解壓後的目錄
cd RedisBloom-1.1.1/

//編譯
make

在這裏插入圖片描述

//進入集羣目錄,添加redis布隆過濾器的插件
cd /usr/local/cluster/7001/redis-4.0.14

vi redis.conf 
cd /usr/local/cluster/7002/redis-4.0.14
 vi redis.conf
 .......


在redis中找到

################################## MODULES #####################################

# Load modules at startup. If the server is not able to load modules
# it will abort. It is possible to use multiple loadmodule directives.
#
# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so
#添加 布隆過濾插件
loadmodule /usr/local/cluster/redisbloom/RedisBloom-1.1.1/rebloom.so

在這裏插入圖片描述

重啓redis

 ./stop.sh
./start.sh

單機版直接啓動

./redis-server ../redis.conf

在這裏插入圖片描述

//進入到redis 7000節點
./redis-cli -p 7000 -c

//添加過濾器值
192.168.25.128:7001> bf.add calvinBloom 111
(integer) 1
192.168.25.128:7001> bf.add calvinBloom 222
(integer) 1
192.168.25.128:7001> bf.add calvinBloom 333
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 111
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 222
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 333

三、布隆過濾器的使用

1、引入依賴

      <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>com.redislabs</groupId>
            <artifactId>jrebloom</artifactId>
            <version>1.2.0</version>
        </dependency>

2、加入redis.properties配置文件

#客戶端超時時間單位是毫秒 默認是2000
redis.timeout=10000
#最大空閒數
redis.maxIdle=300
#連接池的最大數據庫連接數。設爲0表示無限制,如果是jedis 2.4以後用redis.maxTotal
#redis.maxActive=600
#控制一個pool可分配多少個jedis實例,用來替換上面的redis.maxActive,如果是jedis 2.4以後用該屬性
redis.maxTotal=2000
#最大建立連接等待時間。如果超過此時間將接到異常。設爲-1表示無限制。
redis.maxWaitMillis=1000

redis.nodes=192.168.25.128:7000,192.168.25.128:7001,192.168.25.128:7002,192.168.25.128:7003,192.168.25.128:7004,192.168.25.128:7005,192.168.25.128:7006,192.168.25.128:7007


3、引入redis的配置文件

@Configuration
@PropertySource("classpath:conf/redis.properties")
public class RedisConfig {

 
    @Value("${redis.maxIdle}")
    private Integer maxIdle;

    @Value("${redis.timeout}")
    private Integer timeout;
 
    @Value("${redis.maxTotal}")
    private Integer maxTotal;
 
    @Value("${redis.maxWaitMillis}")
    private Integer maxWaitMillis;


    @Value("${redis.nodes}")
    private String clusterNodes;


    /**
     * jedis的正常創建
     * @return
     */
    @Bean
    public JedisCluster getJedisCluster(){
        String[] cNodes = clusterNodes.split(",");
        HashSet<HostAndPort> nodes = new HashSet<>();
        //分割集羣節點
        for (String node : cNodes) {
            String[] hp = node.split(":");
            nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
        }
            JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setMaxTotal(maxTotal);

        //創建集羣對象
        JedisCluster jedisCluster = new JedisCluster(nodes, timeout, jedisPoolConfig);
        return jedisCluster;
    }

    /**
     * bloom過濾器的創建
     * @return
     */
    @Bean
    public ClusterClient initClusterClient() {

        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        // 最大連接數
        config.setMaxTotal(300);
        // 池中保留的最大空閒連接數
        config.setMaxIdle(100);
        // 池中保留的最小空閒連接數
        config.setMinIdle(100);
        // 最大等待時間
        config.setMaxWaitMillis(5 * 1000);
        config.setTestWhileIdle(true);
        config.setTestOnBorrow(true);

        String[] cNodes = clusterNodes.split(",");
        HashSet<HostAndPort> nodes = new HashSet<>();
        //分割集羣節點
        for (String node : cNodes) {
            String[] hp = node.split(":");
            nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
        }


        ClusterClient clusterClient = new ClusterClient(nodes, 300 * 1000, 300 * 1000, 10, config);// 創建REDIS集羣
        return clusterClient;
    }
    

}

4、創建BloomJedisService 接口

public interface BloomJedisService {

    public boolean createFilter(final String name, final long initCapacity, final double errorRate);

    public boolean[] addMulti(final String name, final byte[]... values);

    public boolean[] addMulti(final String name, final String... values);

    public boolean add(final String name, final String value);

    public boolean add(final String name, final byte[] value);

    public boolean exists(final String name, final String value);

    public boolean exists(final String name, final byte[] value);

    public boolean delete(final String name) ;

    public boolean[] existsMulti(final String name, final byte[] value);

    public boolean[] existsMulti(final String name, final String... values);

    public long expire(final String key, final int seconds);

    public long expireAt(final String key, final long unixTime);

    public long ttl(final String key) ;
    /**
     * key是否存在
     *
     * @param key
     * @return
     */
    public boolean exists(final String key);
}

5、創建BloomJedisServiceImpl 的具體實現

@Service
public class BloomJedisServiceImpl implements BloomJedisService {


    public static String prefix = "1_BF_";

    @Autowired
    private ClusterClient clusterClient;

    @Override
    public boolean createFilter(final String name, final long initCapacity, final double errorRate) {
        return clusterClient.createFilter(prefix + name, initCapacity, errorRate);
    }

    @Override
    public boolean[] addMulti(final String name, final byte[]... values) {
        return clusterClient.addMulti(prefix + name, values);
    }
    @Override
    public boolean[] addMulti(final String name, final String... values) {
        return clusterClient.addMulti(prefix + name, values);
    }

    @Override
    public boolean add(final String name, final String value) {
        return clusterClient.add(prefix + name, value);
    }

    @Override
    public boolean add(final String name, final byte[] value) {
        return clusterClient.add(prefix + name, value);
    }

    @Override
    public boolean exists(final String name, final String value) {
        return !clusterClient.add(prefix + name, value);
    }

    @Override
    public boolean exists(final String name, final byte[] value) {
        return clusterClient.add(prefix + name, value);
    }

    @Override
    public boolean delete(final String name) {
        return clusterClient.delete(prefix + name);
    }

    @Override
    public boolean[] existsMulti(final String name, final byte[] value) {
        return clusterClient.existsMulti(prefix + name, value);
    }

    @Override
    public boolean[] existsMulti(final String name, final String... values) {
        return clusterClient.existsMulti(prefix + name, values);
    }

    @Override
    public long expire(final String key, final int seconds) {
        return clusterClient.expire(prefix + key, seconds);
    }

    @Override
    public long expireAt(final String key, final long unixTime) {
        return clusterClient.expireAt(prefix + key, unixTime);
    }

    @Override
    public long ttl(final String key) {
        return clusterClient.ttl(prefix + key);
    }

    /**
     * key是否存在
     *
     * @param key
     * @return
     */
    @Override
    public boolean exists(final String key) {
        return clusterClient.exists(prefix + key);
    }


}

6、測試功能

    private static final String testFilter = "test_filter2";

    @Test
    public void testBloom(){
    //創建過濾器(,這裏爲了測試比較明顯,將概率設置大一點)
        bloomJedisService.createFilter(testFilter, 50, 0.01);

        for (int i = 0; i < 1000; i++) {
            bloomJedisService.add(testFilter, ""+i);
        }
        int j=0;
        for (int i = 1001; i < 2000; i++) {
            boolean exists = bloomJedisService.exists(testFilter, "" + i);
            if(exists){
                System.out.println("有誤判"+(++j));
            }
        }

    }

測試結果 1000/19=0.02 ,基本能接受

有誤判1
有誤判2
有誤判3
有誤判4
有誤判5
有誤判6
有誤判7
有誤判8
有誤判9
有誤判10
有誤判11
有誤判12
有誤判13
有誤判14
有誤判15
有誤判16
有誤判17
有誤判18
有誤判19
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章