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 相关文章推荐
win10下go mod配置方式
Apr 25 Golang
golang import自定义包方式
Apr 29 Golang
Golang: 内建容器的用法
May 05 Golang
Golang 编译成DLL文件的操作
May 06 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
Golang二维数组的使用方式
May 28 Golang
go开发alertmanger实现钉钉报警
Jul 16 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
Go结合Gin导出Mysql数据到Excel表格
Aug 05 Golang
如何解决goland,idea全局搜索快捷键失效问题
golang为什么要统一错误处理
简单聊聊Golang中defer预计算参数
Mar 25 #Golang
Go 中的空白标识符下划线
golang生成vcf通讯录格式文件详情
golang实现浏览器导出excel文件功能
Golang使用Panic与Recover进行错误捕获
Mar 22 #Golang
You might like
php is_file()和is_dir()用于遍历目录时用法注意事项
2010/03/02 PHP
PHP获取当前页面URL函数实例
2014/10/22 PHP
curl和libcurl的区别简介
2015/07/01 PHP
PHP会员找回密码功能的简单实现
2016/09/05 PHP
PHP new static 和 new self详解
2017/02/19 PHP
php通过各种函数判断0和空
2020/07/04 PHP
JavaScript 浏览器对象模型BOM使用介绍
2015/04/13 Javascript
jQuery编程中的一些核心方法简介
2015/08/14 Javascript
实用jquery操作表单元素的简单代码
2016/07/04 Javascript
基于jQuery实现表格的查看修改删除
2016/08/01 Javascript
js动态生成form 并用ajax方式提交的实现方法
2016/09/09 Javascript
vue.js将unix时间戳转换为自定义时间格式
2017/01/03 Javascript
详解微信小程序支付流程与梳理
2019/07/16 Javascript
[33:23]VG vs Pain 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
简单介绍Python中的readline()方法的使用
2015/05/24 Python
Python的网络编程库Gevent的安装及使用技巧
2016/06/24 Python
Python实现多进程共享数据的方法分析
2017/12/04 Python
Python3实现发送QQ邮件功能(html)
2017/12/15 Python
python3 图片referer防盗链的实现方法
2018/03/12 Python
pygame游戏之旅 创建游戏窗口界面
2018/11/20 Python
python使用phoenixdb操作hbase的方法示例
2019/02/28 Python
Python Web框架之Django框架cookie和session用法分析
2019/08/16 Python
浅谈Tensorflow 动态双向RNN的输出问题
2020/01/20 Python
python+selenium定时爬取丁香园的新型冠状病毒数据并制作出类似的地图(部署到云服务器)
2020/02/09 Python
python剪切视频与合并视频的实现
2020/03/03 Python
css3 transform导致子元素固定定位变成绝对定位的方法
2020/03/06 HTML / CSS
CK美国官网:Calvin Klein
2016/08/26 全球购物
瑞士设计师家具和家居饰品网上商店:Bruno Wickart
2019/03/18 全球购物
main 主函数执行完毕后,是否可能会再执行一段代码,给出说明
2012/12/05 面试题
团支部推优材料
2014/05/21 职场文书
2014国庆节餐厅促销活动策划方案
2014/09/16 职场文书
安全生产月宣传标语
2014/10/06 职场文书
四川省传达学习贯彻党的群众路线教育实践活动总结大会精神新闻稿
2014/10/26 职场文书
阿凡达观后感
2015/06/10 职场文书
高中语文教材(文学文化常识大全一)
2019/08/13 职场文书
Go Plugins插件的实现方式
2021/08/07 Golang