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

3,055 阅读2分钟
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 - n*RedisMaxLength
   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错误率
使用Bloom13832.4/sec0%
不使用Bloom2470/sec0%

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万次的平均执行时间一亿次的平均执行时间一亿次的平均长度
MurmurHash0000000.00668680.0119473619
CityHash0000000.00661790.0112917119
crc640000000.00644590.0124247319

本次实现主要使用murmur3算法

  • 设置在缓存之前好还是缓存之后好

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

  • 如何保证布隆过滤器中数据与数据库数据一致性,如何防止漏查

将数据库中已有的数据全部刷入布隆过滤器中

  • 新增数据如何同步到布隆过滤器中

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