victoriaMetrics库布隆过滤器初始化及使用详解


Posted in Golang onApril 05, 2022

victoriaMetrics库布隆过滤器

代码路径:/lib/bloomfilter

概述

victoriaMetrics的vmstorage组件会接收上游传递过来的指标,在现实场景中,指标或瞬时指标的数量级可能会非常恐怖,如果不限制缓存的大小,有可能会由于cache miss而导致出现过高的slow insert

为此,vmstorage提供了两个参数:maxHourlySeriesmaxDailySeries,用于限制每小时/每天添加到缓存的唯一序列。

唯一序列指表示唯一的时间序列,如metrics{label1="value1",label2="value2"}属于一个时间序列,但多条不同值的metrics{label1="value1",label2="value2"}属于同一条时间序列。victoriaMetrics使用如下方式来获取时序的唯一标识:

func getLabelsHash(labels []prompbmarshal.Label) uint64 {
	bb := labelsHashBufPool.Get()
	b := bb.B[:0]
	for _, label := range labels {
		b = append(b, label.Name...)
		b = append(b, label.Value...)
	}
	h := xxhash.Sum64(b)
	bb.B = b
	labelsHashBufPool.Put(bb)
	return h
}

限速器的初始化

victoriaMetrics使用了一个类似限速器的概念,限制每小时/每天新增的唯一序列,但与普通的限速器不同的是,它需要在序列级别进行限制,即判断某个序列是否是新的唯一序列,如果是,则需要进一步判断一段时间内缓存中新的时序数目是否超过限制,而不是简单地在请求层面进行限制。

hourlySeriesLimiter = bloomfilter.NewLimiter(*maxHourlySeries, time.Hour)
dailySeriesLimiter = bloomfilter.NewLimiter(*maxDailySeries, 24*time.Hour)

下面是新建限速器的函数,传入一个最大(序列)值,以及一个刷新时间。该函数中会:

  • 初始化一个限速器,限速器的最大元素个数为maxItems
  • 则启用了一个goroutine,当时间达到refreshInterval时会重置限速器
func NewLimiter(maxItems int, refreshInterval time.Duration) *Limiter {
	l := &Limiter{
		maxItems: maxItems,
		stopCh:   make(chan struct{}),
	}
	l.v.Store(newLimiter(maxItems)) //1
	l.wg.Add(1)
	go func() {
		defer l.wg.Done()
		t := time.NewTicker(refreshInterval)
		defer t.Stop()
		for {
			select {
			case <-t.C:
				l.v.Store(newLimiter(maxItems))//2
			case <-l.stopCh:
				return
			}
		}
	}()
	return l
}

限速器只有一个核心函数Add,当vmstorage接收到一个指标之后,会(通过getLabelsHash计算该指标的唯一标识(h),然后调用下面的Add函数来判断该唯一标识是否存在于缓存中。

如果当前存储的元素个数大于等于允许的最大元素,则通过过滤器判断缓存中是否已经存在该元素;否则将该元素直接加入过滤器中,后续允许将该元素加入到缓存中。

func (l *Limiter) Add(h uint64) bool {
	lm := l.v.Load().(*limiter)
	return lm.Add(h)
}
func (l *limiter) Add(h uint64) bool {
	currentItems := atomic.LoadUint64(&l.currentItems)
	if currentItems >= uint64(l.f.maxItems) {
		return l.f.Has(h)
	}
	if l.f.Add(h) {
		atomic.AddUint64(&l.currentItems, 1)
	}
	return true
}

上面的过滤器采用的是布隆过滤器,核心函数为HasAdd,分别用于判断某个元素是否存在于过滤器中,以及将元素添加到布隆过滤器中。

过滤器的初始化函数如下,bitsPerItem是个常量,值为16。bitsCount统计了过滤器中的总bit数,每个bit表示某个值的存在性。bits以64bit为单位的(后续称之为slot,目的是为了在bitsCount中快速检索目标bit)。计算bits时加上63的原因是为了四舍五入向上取值,比如当maxItems=1时至少需要1个unit64的slot。

func newFilter(maxItems int) *filter {
	bitsCount := maxItems * bitsPerItem
	bits := make([]uint64, (bitsCount+63)/64)
	return &filter{
		maxItems: maxItems,
		bits:     bits,
	}
}

为什么bitsPerItem为16?这篇文章给出了如何计算布隆过滤器的大小。在本代码中,k为4(hashesCount),期望的漏失率为0.003(可以从官方的filter_test.go中看出),则要求总存储和总元素的比例为15,为了方便检索slot(64bit,为16的倍数),将之设置为16。

if p &gt; 0.003 {
		t.Fatalf("too big false hits share for maxItems=%d: %.5f, falseHits: %d", maxItems, p, falseHits)
	}

victoriaMetrics库布隆过滤器初始化及使用详解

下面是过滤器的Add操作,目的是在过滤器中添加某个元素。Add函数中没有使用多个哈希函数来计算元素的哈希值,转而改变同一个元素的值,然后对相应的值应用相同的哈希函数,元素改变的次数受hashesCount的限制。

  • 获取过滤器的完整存储,并转换为以bit单位
  • 将元素h转换为byte数组,便于xxhash.Sum64计算
  • 后续将执行hashesCount次哈希,降低漏失率
  • 计算元素h的哈希
  • 递增元素h,为下一次哈希做准备
  • 取余法获取元素的bit范围
  • 获取元素所在的slot(即uint64大小的bit范围)
  • 获取元素所在的slot中的bit位,该位为1表示该元素存在,为0表示该元素不存在
  • 获取元素所在bit位的掩码
  • 加载元素所在的slot的数值
  • 如果w & mask结果为0,说明该元素不存在,
  • 将元素所在的slot(w)中的元素所在的bit位(mask)置为1,表示添加了该元素
  • 由于Add函数可以并发访问,因此bits[i]有可能被其他操作修改,因此需要通过重新加载(14)并通过循环来在bits[i]中设置该元素的存在性
func (f *filter) Add(h uint64) bool {
	bits := f.bits
	maxBits := uint64(len(bits)) * 64 //1
	bp := (*[8]byte)(unsafe.Pointer(&h))//2
	b := bp[:]
	isNew := false
	for i := 0; i < hashesCount; i++ {//3
		hi := xxhash.Sum64(b)//4
		h++ //5
		idx := hi % maxBits //6
		i := idx / 64 //7
		j := idx % 64 //8
		mask := uint64(1) << j //9
		w := atomic.LoadUint64(&bits[i])//10
		for (w & mask) == 0 {//11
			wNew := w | mask //12
			if atomic.CompareAndSwapUint64(&bits[i], w, wNew) {//13
				isNew = true//14
				break
			}
			w = atomic.LoadUint64(&bits[i])//14
		}
	}
	return isNew
}

看懂了Add函数,Has就相当简单了,它只是Add函数的缩减版,无需设置bits[i]

func (f *filter) Has(h uint64) bool {
	bits := f.bits
	maxBits := uint64(len(bits)) * 64
	bp := (*[8]byte)(unsafe.Pointer(&h))
	b := bp[:]
	for i := 0; i < hashesCount; i++ {
		hi := xxhash.Sum64(b)
		h++
		idx := hi % maxBits
		i := idx / 64
		j := idx % 64
		mask := uint64(1) << j
		w := atomic.LoadUint64(&bits[i])
		if (w & mask) == 0 {
			return false
		}
	}
	return true
}

总结

由于victoriaMetrics的过滤器采用的是布隆过滤器,因此它的限速并不精准,在源码条件下, 大约有3%的偏差。但同样地,由于采用了布隆过滤器,降低了所需的内存以及相关计算资源。此外victoriaMetrics的过滤器实现了并发访问。

在大流量场景中,如果需要对请求进行相对精准的过滤,可以考虑使用布隆过滤器,降低所需要的资源,但前提是过滤的结果能够忍受一定程度的漏失率。

以上就是victoriaMetrics库布隆过滤器初始化及使用详解的详细内容,更多关于victoriaMetrics库布隆过滤器的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
golang 实现菜单树的生成方式
Apr 28 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
Go语言并发编程 sync.Once
Oct 16 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
golang语言指针操作
Apr 14 Golang
Go获取两个时区的时间差
Apr 20 Golang
Golang入门之计时器
May 04 Golang
Golang并发工具Singleflight
May 06 Golang
Go gRPC进阶教程gRPC转换HTTP
Jun 16 Golang
GoFrame框架数据校验之校验结果Error接口对象
Jun 21 Golang
如何解决goland,idea全局搜索快捷键失效问题
golang为什么要统一错误处理
简单聊聊Golang中defer预计算参数
Mar 25 #Golang
Go 中的空白标识符下划线
golang生成vcf通讯录格式文件详情
golang实现浏览器导出excel文件功能
Golang使用Panic与Recover进行错误捕获
Mar 22 #Golang
You might like
PHP写的求多项式导数的函数代码
2012/07/04 PHP
PHP使用gmdate实现将一个UNIX 时间格式化成GMT文本的方法
2015/03/19 PHP
js 实现无干扰阴影效果 简单好用(附文件下载)
2009/12/27 Javascript
jQuery学习笔记之 Ajax操作篇(二) - 数据传递
2014/06/23 Javascript
jquery控制背景音乐开关与自动播放提示音的方法
2015/02/06 Javascript
如何处理JSON中的特殊字符
2016/11/30 Javascript
使用jQuery的ajax方法向服务器发出get和post请求的方法
2017/01/13 Javascript
bootstrap IE8 兼容性处理
2017/03/22 Javascript
深入浅析Vue不同场景下组件间的数据交流
2017/08/15 Javascript
JavaScript实现单击网页任意位置打开新窗口与关闭窗口的方法
2017/09/21 Javascript
父组件中vuex方法更新state子组件不能及时更新并渲染的完美解决方法
2018/04/25 Javascript
JavaScript中的null和undefined用法解析
2019/09/30 Javascript
WEEX环境搭建与入门详解
2019/10/16 Javascript
在vue-cli3.0 中使用预处理器 (Sass/Less/Stylus) 配置全局变量操作
2020/08/10 Javascript
[01:00] DOTA2英雄背景故事第五期之重力引力法则谜团
2020/07/16 DOTA
py中的目录与文件判别代码
2008/07/16 Python
python 动态获取当前运行的类名和函数名的方法
2014/04/15 Python
pygame学习笔记(5):游戏精灵
2015/04/15 Python
给Python中的MySQLdb模块添加超时功能的教程
2015/05/05 Python
Python统计日志中每个IP出现次数的方法
2015/07/06 Python
利用Python yagmail三行代码实现发送邮件
2018/05/11 Python
python制作填词游戏步骤详解
2019/05/05 Python
Tensorflow限制CPU个数实例
2020/02/06 Python
已安装tensorflow-gpu,但keras无法使用GPU加速的解决
2020/02/07 Python
HTML5 Canvas自定义圆角矩形与虚线示例代码
2013/08/02 HTML / CSS
英国最大的宝石首饰超市:QP Jewellers
2018/09/23 全球购物
Vilebrequin美国官方网上商店:法国豪华泳装品牌
2020/02/22 全球购物
nohup的用法
2014/08/10 面试题
《美丽的南沙群岛》教学反思
2014/04/27 职场文书
歌颂祖国的演讲稿
2014/05/04 职场文书
医生见习报告范文
2014/11/03 职场文书
长城导游词300字
2015/01/30 职场文书
《这片土地是神圣的》教学反思
2016/02/16 职场文书
Python操作CSV格式文件的方法大全
2021/07/15 Python
微软Win11什么功能最惊艳? Windows11新功能特性汇总
2021/11/21 数码科技
CSS实现五种常用的2D转换
2021/12/06 HTML / CSS