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中ResponseWriter的一个坑
Apr 27 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 Golang
go语言中fallthrough的用法说明
May 06 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
Go语言基础函数基本用法及示例详解
Nov 17 Golang
Golang ort 中的sortInts 方法
Apr 24 Golang
深入理解 Golang 的字符串
May 04 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 Golang
Go语言编译原理之源码调试
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 socke 向指定页面提交数据
2008/07/23 PHP
php中防止恶意刷新页面的代码小结
2012/10/31 PHP
jQuery select操作控制方法小结
2010/05/26 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
2010/06/18 Javascript
深入理解JavaScript系列(46):代码复用模式(推荐篇)详解
2015/03/04 Javascript
浅谈javascript中call()、apply()、bind()的用法
2015/04/20 Javascript
jQuery实现鼠标划过添加和删除class的方法
2015/06/26 Javascript
利用jQuery及AJAX技术定时更新GridView的某一列数据
2015/12/04 Javascript
jQuery UI库中dialog对话框功能使用全解析
2016/04/23 Javascript
JS获取鼠标选中的文字
2016/08/10 Javascript
微信小程序 radio单选框组件详解及实例代码
2017/01/10 Javascript
js中小数向上取整数,向下取整数,四舍五入取整数的实现(必看篇)
2017/02/13 Javascript
详谈js中数组(array)和对象(object)的区别
2017/02/27 Javascript
提高JavaScript执行效率的23个实用技巧
2017/03/01 Javascript
B/S(Web)实时通讯解决方案分享
2017/04/06 Javascript
angularJs使用$watch和$filter过滤器制作搜索筛选实例
2017/06/01 Javascript
Vue动态组件实例解析
2017/08/20 Javascript
python 2.6.6升级到python 2.7.x版本的方法
2016/10/09 Python
使用Python3 编写简单信用卡管理程序
2016/12/21 Python
Python使用cx_Oracle模块操作Oracle数据库详解
2018/05/07 Python
Python实现读取SQLServer数据并插入到MongoDB数据库的方法示例
2018/06/09 Python
python利用thrift服务读取hbase数据的方法
2018/12/27 Python
如何更优雅地写python代码
2019/07/02 Python
浅谈Python小波分析库Pywavelets的一点使用心得
2019/07/09 Python
给你一面国旗 教你用python画中国国旗
2019/09/24 Python
python range实例用法分享
2020/02/06 Python
浅谈Python程序的错误:变量未定义
2020/06/02 Python
如何用Python绘制3D柱形图
2020/09/16 Python
迷你分体式空调:SoGoodToBuy
2018/08/07 全球购物
开办大学饮食联盟创业计划书
2014/01/29 职场文书
党员三严三实心得体会
2014/10/13 职场文书
化工见习报告范文
2014/10/31 职场文书
施工安全协议书
2016/03/22 职场文书
创业计划书之少年玩具店
2019/09/05 职场文书
Python各协议下socket黏包问题原理
2022/04/12 Python
python热力图实现的完整实例
2022/06/25 Python