布隆過濾器 - Redis 布隆過濾器,Guava 布隆過濾器 BloomFilter - 代碼實踐

布隆過濾器 - Redis 布隆過濾器,Guava 布隆過濾器 BloomFilter - 代碼實踐


一、經過guava 實現的布隆過濾器

引入依賴java

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>26.0-jre</version>
        </dependency>

編寫測試代碼web

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/** @author Created by 譚健 on 2019/9/25. 星期三. 10:47. © All Rights Reserved. */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BloomFilterTest {

  /** 裏面有多少個數據 */
  private static final int EXPECT_SIZE = 100_0000;
  /** 指望的誤判率 */
  private static final double FPP = 0.0000_5;

  public static void main(String[] args) {

    BloomFilter<Integer> filter = initFilter();

    int count = 0;
    for (int i = EXPECT_SIZE; i < EXPECT_SIZE * 2; i++) {
      // 設置了一個循環,拿一些根本不存在的數據去測試,測試次數 = 總數據量
      if (filter.mightContain(i)) {
        count++;
        log.info("當前數據 {} 不存在於過濾器中,可是返回存在,誤判了。", i);
      }
    }

    log.info("一共誤判了 {} 個數據", count);
    double v = count / (EXPECT_SIZE * 1.00);
    log.info("誤判率 {}", BigDecimal.valueOf(v).setScale(6, BigDecimal.ROUND_HALF_UP));
  }

  private static BloomFilter<Integer> initFilter() {
    BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), EXPECT_SIZE, FPP);

    for (int i = 0; i < EXPECT_SIZE; i++) {
      filter.put(i);
    }
    return filter;
  }
}

輸出結果redis

11:09:24.703 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1043713 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.709 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1045293 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.716 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1084804 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.722 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1108827 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.731 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1170565 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.732 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1176553 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.733 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1181407 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.739 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1212393 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.740 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1216250 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.740 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1217755 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.755 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1302056 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.761 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1340223 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.762 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1345555 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.764 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1359757 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.765 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1362759 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.766 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1367631 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.767 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1372497 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.774 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1421526 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.778 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1452218 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.781 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1470719 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.798 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1583667 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1586562 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1587225 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1588861 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1589613 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.801 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1605520 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.802 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1612489 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.802 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1614824 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.804 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1628491 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.805 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1636461 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.806 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1649258 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.808 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1670422 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.809 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1680673 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.810 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1684041 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.812 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1700898 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.816 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1737096 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.818 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1751971 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.819 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1757162 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.819 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1757183 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.820 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1764249 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.821 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1773561 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.822 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1784175 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.830 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1827745 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.835 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1865076 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.837 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1885160 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.840 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1911780 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.840 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1918022 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.844 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1944798 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.849 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1997282 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.849 [main] INFO com.example.demo.BloomFilterTest - 當前數據 1999119 不存在於過濾器中,可是返回存在,誤判了。
11:09:24.850 [main] INFO com.example.demo.BloomFilterTest - 一共誤判了 50 個數據
11:09:24.852 [main] INFO com.example.demo.BloomFilterTest - 誤判率 0.000050

固然,誤判率並非徹底準確的,設置的誤判率,只是一個近似值。算法

二、經過 redisson 實現的布隆過濾器

此處引用 redisson 的依賴來實現數組

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.11.2</version>
        </dependency>

Redisson利用Redis實現了Java分佈式布隆過濾器(Bloom Filter)。所含最大比特數量爲2^32。分佈式

示例代碼svg

RBloomFilter<SomeObject> bloomFilter = redisson.getBloomFilter("sample");
// 初始化布隆過濾器,預計統計元素數量爲55000000,指望偏差率爲0.03
bloomFilter.tryInit(55000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));

基於集羣的數據分片布隆過濾器Sharding ,不過基於集羣的過濾器,僅限於Redisson PRO 版本中,須要收費函數

基於Redis的Redisson集羣分佈式布隆過濾器經過RClusteredBloomFilter接口,爲集羣狀態下的Redis環境提供了布隆過濾器數據分片的功能。 經過優化後更加有效的算法,經過壓縮未使用的比特位來釋放集羣內存空間。每一個對象的狀態都將被分佈在整個集羣中。所含最大比特數量爲2^64。在這裏能夠獲取更多的內部信息。測試

示例代碼優化

RClusteredBloomFilter<SomeObject> bloomFilter = redisson.getClusteredBloomFilter("sample");
// 採用如下參數建立布隆過濾器
// expectedInsertions = 255000000
// falseProbability = 0.03
bloomFilter.tryInit(255000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));

三、經過Jedis 實現的布隆過濾器

代碼以下:

import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;

import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/** @author Created by 譚健 on 2019/9/25. 星期三. 11:29. © All Rights Reserved. */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JedisBloomFilter {
  /** 裏面有多少個數據 */
  private static final int EXPECT_SIZE = 1_0000;
  /** 指望的誤判率 */
  private static final double FPP = 0.0005;
  /** bit數組長度 */
  private static final long NUM_BITS;
  /** hash函數數量 */
  private static final int NUM_HASH_FUNCTIONS;
  /** redis 中的key */
  private static final String KEY = "JedisBloomFilter:TEST";

  private static final Jedis JEDIS;

  static {
    // 計算bit數組長度
    NUM_BITS = (long) (-EXPECT_SIZE * Math.log(FPP) / (Math.log(2) * Math.log(2)));
    // 計算hash函數個數
    NUM_HASH_FUNCTIONS =
        Math.max(1, (int) Math.round((double) NUM_BITS / EXPECT_SIZE * Math.log(2)));
    // 初始化JEDIS
    JEDIS = new Jedis();
    JEDIS.auth("caishang");
    JEDIS.select(10);
  }

  public static void main(String[] args) {

    // init
    for (int i = 0; i < EXPECT_SIZE; i++) {
      long[] longs = getIndex(String.valueOf(i));
      for (long index : longs) {
        JEDIS.setbit(KEY, index, true);
      }
    }

    // 檢測
    int count = 0;
    for (int i = EXPECT_SIZE; i < EXPECT_SIZE * 2; i++) {
      if (mightContain(i)) {
        count++;
        log.info("當前數據 {} 不存在於過濾器中,可是返回存在,誤判了。", i);
      }
    }
    log.info("一共誤判了 {} 個數據", count);
    double v = count / (EXPECT_SIZE * 1.00);
    log.info("誤判率 {}", BigDecimal.valueOf(v).setScale(6, BigDecimal.ROUND_HALF_UP));
  }

  /** * 判斷某個數據是否可能存在於過濾器中 * * @return true = 可能存在 */
  private static boolean mightContain(Object i) {
    long[] longs = getIndex(String.valueOf(i));
    for (long index : longs) {
      boolean isContain = JEDIS.getbit(KEY, index);
      // 有任意一個不存在,則該值必定不存在
      if (!isContain) {
        return false;
      }
    }
    return true;
  }

  /** 根據key獲取bitmap下標 */
  private static long[] getIndex(String key) {
    long hash1 = hash(key);
    long[] result = new long[NUM_HASH_FUNCTIONS];
    for (int i = 0; i < NUM_HASH_FUNCTIONS; i++) {
      long combinedHash = hash1 + i * (hash1 >>> 16);
      if (combinedHash < 0) {
        combinedHash = ~combinedHash;
      }
      result[i] = combinedHash % NUM_BITS;
    }
    return result;
  }

  private static long hash(String key) {
    Charset charset = StandardCharsets.UTF_8;
    return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
  }
}

運行結果以下:

12:49:41.605 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 11360 不存在於過濾器中,可是返回存在,誤判了。
12:49:41.628 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 11579 不存在於過濾器中,可是返回存在,誤判了。
12:49:41.703 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 12403 不存在於過濾器中,可是返回存在,誤判了。
12:49:41.769 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 13118 不存在於過濾器中,可是返回存在,誤判了。
12:49:41.785 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 13290 不存在於過濾器中,可是返回存在,誤判了。
12:49:41.807 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 13553 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.064 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 16564 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.129 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 17295 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.183 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 17920 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.243 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 18628 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.297 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 19255 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.355 [main] INFO com.example.demo.JedisBloomFilter - 當前數據 19972 不存在於過濾器中,可是返回存在,誤判了。
12:49:42.357 [main] INFO com.example.demo.JedisBloomFilter - 一共誤判了 12 個數據
12:49:42.359 [main] INFO com.example.demo.JedisBloomFilter - 誤判率 0.001200

與設置誤判率有必定的差距,可是不大。

使用該方式做爲過濾器時,效率不過高。本代碼僅僅做爲示例,沒有進行改良,10000個數據在Redis 中就已經很大了。