基於Redis的BloomFilter實現

前言

最近在研究布隆過濾器(若是不瞭解什麼是布隆過濾器的,推薦看這篇 如何判斷一個元素在億級數據中是否存在?瞭解),發現Guava提供了封裝好的類,可是隻能單機使用,通常如今的應用都是部署在分佈式系統的,因此想找個能夠在分佈式系統下使用的布隆過濾器,找了半天只找到一個基於redis開發的模塊項目 ReBloom,可是這個是須要額外安裝的,並且文檔裏只說了怎麼在docker下運行,沒研究過docker因此放棄了。後來找到一篇博客講怎麼利用布隆過濾器統計消息未讀數的(博客地址不記得了,是一位淘寶同窗寫的),博客最後放了一份整合redis和bloomFilter的代碼demo,詳見 BloomFilter.java,看了下實現比較簡單,可是使用方式不是我想要的,因此參考着本身整理了一份。

BloomFilterHelper

package com.doodl6.springmvc.service.cache.redis;

import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;

public class BloomFilterHelper<T> {

    private int numHashFunctions;

    private int bitSize;

    private Funnel<T> funnel;

    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
        Preconditions.checkArgument(funnel != null, "funnel不能爲空");
        this.funnel = funnel;
        bitSize = optimalNumOfBits(expectedInsertions, fpp);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    int[] murmurHashOffset(T value) {
        int[] offset = new int[numHashFunctions];

        long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
        int hash1 = (int) hash64;
        int hash2 = (int) (hash64 >>> 32);
        for (int i = 1; i <= numHashFunctions; i++) {
            int nextHash = hash1 + i * hash2;
            if (nextHash < 0) {
                nextHash = ~nextHash;
            }
            offset[i - 1] = nextHash % bitSize;
        }

        return offset;
    }

    /**
     * 計算bit數組長度
     */
    private int optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

    /**
     * 計算hash方法執行次數
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }
}

BloomFilterHelper是實現功能的關鍵,包含了計算bitmap的核心算法,其實大部分代碼都是來源於Guava庫裏面的BloomFilterStrategies類,可是由於這個類是專門爲Guava的BloomFilter類使用的,因此沒有對外暴露一些重要的算法邏輯。java

再來看怎麼結合redis一塊兒使用BloomFilterHelpergit

RedisService

package com.doodl6.springmvc.service.cache.redis;

import com.google.common.base.Preconditions;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 根據給定的布隆過濾器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能爲空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根據給定的布隆過濾器判斷值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能爲空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }

        return true;
    }
}

RedisService很簡單,只有兩個方法github

addByBloomFilter,往redis裏面添加元素redis

includeByBloomFilter,檢查元素是否在redis bloomFilter裏面算法

這裏redis的客戶端使用的是spring-data-redis封裝的,能夠在個人項目SpringMVC-Project中查看完整的使用代碼。spring

相關文章
相關標籤/搜索