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算法
设置在缓存之前好还是缓存之后好 根据业务场景选择,如果提高用户访问效率,加在缓存之后较好
如何保证布隆过滤器中数据与数据库数据一致性,如何防止漏查 将数据库中已有的数据全部刷入布隆过滤器中
新增数据如何同步到布隆过滤器中 使用协程将新加入数据同步到布隆过滤器中