布隆过滤器调研与设计实现(Go)

214 阅读3分钟

1.定义: 布隆过滤器是一种类似于集合的数据结构,与传统的类似于集合的数据结构(例如哈希表或树)相比,其空间效率更高。布隆过滤器可以百分百确定集合中未包含某些内容,但是它不能百分百确定集合中是否包含某些内容。 2.类型: 基于本地内存的布隆过滤器 基于文件的布隆过滤器 基于redis的布隆过滤器(分布式可用)(插件式(不考虑),redis自带BIT) 3.应用场景: 防止缓存穿透(设计)

4.实现:

var bloomInstance *RedisBloomFilter

const RedisMaxLength = 8 * 512 * 1024 * 1024

type BloomFilter interface { Put([]byte) error PutString(string) error Has([]byte) (bool, error) HasString(string) (bool, error) getKeyAndOffset(offset uint) (string, uint) }

func BloomInstance() *RedisBloomFilter { if bloomInstance != nil{ return bloomInstance } Init() return bloomInstance }

type RedisBloomFilter struct { keyPrefix string n uint k uint }

//对数据进行哈希计算 func HashData(data []byte) [4]uint64 { a1 := []byte{1} // to grab another bit of data hasher := murmur3.New128() hasher.Write(data) // #nosec v1, v2 := hasher.Sum128() hasher.Write(a1) // #nosec v3, v4 := hasher.Sum128() return [4]uint64{ v1, v2, v3, v4, } }

//创建一个redis布隆过滤器 func NewRedisBloomFilter(m uint, p float64, kp string) *RedisBloomFilter { n, k := bloom.EstimateParameters(m, p) filter := &RedisBloomFilter{ n: n, k: k, keyPrefix: kp, } cli := redis.RedisInstance() key, value := filter.getKeyAndOffset(n) cli.Do("SETBIT", key, value, 0) return filter }

func Init(){

fmt.Println(os.Args[0]) key := conf.GetConfig().MustValue("bloom","key_prefix","") if key == ""{ panic("bloom:get key fail") } data_number := conf.GetConfig().MustValue("bloom","data_number","") false_positive := conf.GetConfig().MustValue("bloom","false_positive","") m,_ :=strconv.ParseUint(data_number,10,64) p,_ :=strconv.ParseFloat(false_positive,64)

bloomInstance = NewRedisBloomFilter(uint(m),p,key) fmt.Println("redis init successful")

}

//将数据放入布隆过滤器 func (filter *RedisBloomFilter) Put(data []byte) error { cli := redis.RedisInstance() h := HashData(data) for i := uint(0); i < filter.k; i++ { key, value := filter.getKeyAndOffset(uint(location(h, i) % uint64(filter.n))) _, err := cli.Do("SETBIT", key, value, 1) if err != nil { return err } } return nil }

//将字符串放入布隆过滤器 func (filter *RedisBloomFilter) PutString(data string) error { return filter.Put([]byte(data)) }

//判断数据是否在布隆过滤器中 func (filter *RedisBloomFilter) Has(data []byte) (bool, error) { cli := redis.RedisInstance() h := HashData(data) for i := uint(0); i < filter.k; i++ { key, value := filter.getKeyAndOffset(uint(location(h, i) % uint64(filter.n))) bitValue,err := cli.GetBitInt(key, value) if err != nil { return false, err } if bitValue == 0 { return false, nil } } return true, nil } //判断字符串是否在布隆过滤器中 func (filter *RedisBloomFilter) HasString(data string) (bool, error) { return filter.Has([]byte(data)) }

func (filter RedisBloomFilter) getKeyAndOffset(offset uint) (string, uint) { n := uint(offset / RedisMaxLength) thisOffset := offset - nRedisMaxLength key := fmt.Sprintf("%s:%d", filter.keyPrefix, n) return key, thisOffset }

func location(h [4]uint64, i uint) uint64 { ii := uint64(i) return h[ii%2] + ii*h[2+(((ii+(ii%2))%4)/2)] }

5.压测报告:

配置:内存16G 工具:JMeter

测试使用100000线程

压测功能结果(查询不存在数据): QPS 错误率 使用Bloom 13832.4/sec 0% 不使用Bloom 2470/sec 0% QPS(使用Bloom)/QPS(不使用Bloom)≈5.60

结论:使用布隆过滤器提升了用户输入不存在数据返回的响应速度,同时也可以保护数据库免受缓存穿透的影响

6.问题:

初始bitset大小设置问题 func EstimateParameters(n uint, p float64) (m uint, k uint) { m = uint(math.Ceil(-1 * float64(n) * math.Log(p) / math.Pow(math.Log(2), 2))) k = uint(math.Ceil(math.Log(2) * float64(m) / float64(n))) return }

哈希算法选择问题 哈希算法碰撞率(常用的)

10万 50万 100万 500万 1000万 1亿 1000万次的平均执行时间 一亿次的平均执行时间 一亿次的平均长度 MurmurHash 0 0 0 0 0 0 0.0066868 0.01194736 19 CityHash 0 0 0 0 0 0 0.0066179 0.01129171 19 crc64 0 0 0 0 0 0 0.0064459 0.01242473 19

本次实现主要使用murmur3算法

设置在缓存之前好还是缓存之后好 根据业务场景选择,如果提高用户访问效率,加在缓存之后较好

如何保证布隆过滤器中数据与数据库数据一致性,如何防止漏查 将数据库中已有的数据全部刷入布隆过滤器中

新增数据如何同步到布隆过滤器中 使用协程将新加入数据同步到布隆过滤器中