golang高性能线程安全布隆过滤器实现插件库ring的使用
Golang高性能线程安全布隆过滤器实现插件库ring的使用
ring - 高性能布隆过滤器
ring是一个提供高性能且线程安全的Go语言布隆过滤器实现的库。
使用示例
下面是一个完整的ring库使用示例:
package main
import (
"fmt"
"github.com/tannerryan/ring"
)
func main() {
// 创建一个预期包含10000个元素,假阳性率为0.1%的布隆过滤器
// 参数说明:
// - 第一个参数:预期元素数量
// - 第二个参数:目标假阳性率(0.001表示0.1%)
filter, err := ring.Init(10000, 0.001)
if err != nil {
panic(err)
}
// 添加元素到布隆过滤器
filter.Add([]byte("hello"))
filter.Add([]byte("world"))
// 检查元素是否存在
// Test()方法返回:
// - true: 元素可能存在(可能有假阳性)
// - false: 元素一定不存在
fmt.Println(filter.Test([]byte("hello"))) // 输出: true
fmt.Println(filter.Test([]byte("world"))) // 输出: true
fmt.Println(filter.Test([]byte("golang"))) // 输出: false
// 重置过滤器(清空所有元素)
filter.Reset()
// 重置后检查
fmt.Println(filter.Test([]byte("hello"))) // 输出: false
}
性能测试结果
以下是针对100万个元素,目标假阳性率为0.1%的性能测试结果:
=== RUN TestBadParameters
--- PASS: TestBadParameters (0.00s)
=== RUN TestReset
--- PASS: TestReset (0.26s)
=== RUN TestData
--- PASS: TestData (14.07s)
=== RUN TestMerge
--- PASS: TestMerge (13.78s)
=== RUN TestMarshal
--- PASS: TestMarshal (14.48s)
PASS
>> Number of elements: 1000000
>> Target false positive rate: 0.001000
>> Number of false positives: 99
>> Actual false positive rate: 0.000099
>> Number of false negatives: 0
>> Actual false negative rate: 0.000000
>> Benchmark Add(): 10000000 158 ns/op
>> Benchmark Test(): 10000000 173 ns/op
ok command-line-arguments 47.914s
高级功能示例
package main
import (
"fmt"
"github.com/tannerryan/ring"
)
func main() {
// 初始化两个布隆过滤器
filter1, _ := ring.Init(1000, 0.01)
filter2, _ := ring.Init(1000, 0.01)
// 向两个过滤器添加不同元素
filter1.Add([]byte("apple"))
filter1.Add([]byte("banana"))
filter2.Add([]byte("banana"))
filter2.Add([]byte("orange"))
// 合并两个过滤器
// 注意:只有相同配置的过滤器才能合并
err := filter1.Merge(filter2)
if err != nil {
fmt.Println("Merge error:", err)
return
}
// 检查合并后的结果
fmt.Println(filter1.Test([]byte("apple"))) // true
fmt.Println(filter1.Test([]byte("banana"))) // true
fmt.Println(filter1.Test([]byte("orange"))) // true
fmt.Println(filter1.Test([]byte("grape"))) // false
// 序列化和反序列化
data, err := filter1.MarshalBinary()
if err != nil {
fmt.Println("Marshal error:", err)
return
}
newFilter := &ring.Ring{}
err = newFilter.UnmarshalBinary(data)
if err != nil {
fmt.Println("Unmarshal error:", err)
return
}
// 检查反序列化后的过滤器
fmt.Println(newFilter.Test([]byte("apple"))) // true
fmt.Println(newFilter.Test([]byte("orange"))) // true
}
注意事项
- 布隆过滤器有假阳性率(可能误报元素存在),但不会有假阴性(如果返回不存在则一定不存在)
- 合并操作要求两个过滤器具有相同的配置(相同的大小和哈希函数)
- 添加的元素越多,实际假阳性率会逐渐接近目标假阳性率
许可证
Copyright © 2019 Tanner Ryan. 保留所有权利。此源代码的使用受BSD样式许可证约束,该许可证可以在LICENSE文件中找到。
更多关于golang高性能线程安全布隆过滤器实现插件库ring的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang高性能线程安全布隆过滤器实现插件库ring的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang高性能线程安全布隆过滤器实现
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在一个集合中。下面我将介绍如何使用Golang实现一个高性能、线程安全的布隆过滤器,并介绍ring
缓冲区的使用。
基础实现
首先,我们来看一个基础的布隆过滤器实现:
package bloom
import (
"hash"
"hash/fnv"
"sync"
)
type BloomFilter struct {
bitset []bool
size uint
hashFuncs []hash.Hash64
mutex sync.RWMutex
}
func NewBloomFilter(size uint, numHashFuncs uint) *BloomFilter {
bf := &BloomFilter{
bitset: make([]bool, size),
size: size,
}
for i := uint(0); i < numHashFuncs; i++ {
bf.hashFuncs = append(bf.hashFuncs, fnv.New64())
}
return bf
}
func (bf *BloomFilter) Add(item []byte) {
bf.mutex.Lock()
defer bf.mutex.Unlock()
for _, hashFunc := range bf.hashFuncs {
hashFunc.Reset()
hashFunc.Write(item)
hashValue := hashFunc.Sum64()
position := hashValue % uint64(bf.size)
bf.bitset[position] = true
}
}
func (bf *BloomFilter) Test(item []byte) bool {
bf.mutex.RLock()
defer bf.mutex.RUnlock()
for _, hashFunc := range bf.hashFuncs {
hashFunc.Reset()
hashFunc.Write(item)
hashValue := hashFunc.Sum64()
position := hashValue % uint64(bf.size)
if !bf.bitset[position] {
return false
}
}
return true
}
高性能优化
上面的基础实现有几个性能问题:
- 每次操作都需要创建新的hash函数
- 使用bool数组占用空间较大
- 锁粒度较大
下面是一个优化后的版本:
package bloom
import (
"hash/fnv"
"sync"
"unsafe"
)
type BloomFilter struct {
bitset []uint64
size uint64
hashFuncs []func([]byte) uint64
mutex sync.RWMutex
}
func NewBloomFilter(size uint64, numHashFuncs uint) *BloomFilter {
bf := &BloomFilter{
bitset: make([]uint64, (size+63)/64),
size: size,
}
// 预生成hash函数
h := fnv.New64a()
for i := uint(0); i < numHashFuncs; i++ {
seed := uint64(i)
bf.hashFuncs = append(bf.hashFuncs, func(data []byte) uint64 {
h.Reset()
h.Write([]byte{byte(seed)})
h.Write(data)
return h.Sum64() % size
})
}
return bf
}
func (bf *BloomFilter) Add(item []byte) {
bf.mutex.Lock()
defer bf.mutex.Unlock()
for _, hashFunc := range bf.hashFuncs {
pos := hashFunc(item)
bf.bitset[pos/64] |= 1 << (pos % 64)
}
}
func (bf *BloomFilter) Test(item []byte) bool {
bf.mutex.RLock()
defer bf.mutex.RUnlock()
for _, hashFunc := range bf.hashFuncs {
pos := hashFunc(item)
if bf.bitset[pos/64]&(1<<(pos%64)) == 0 {
return false
}
}
return true
}
使用ring缓冲区
container/ring
是Go标准库提供的环形缓冲区实现,可以用于实现滑动窗口布隆过滤器:
import "container/ring"
type SlidingBloomFilter struct {
windowSize int
current *BloomFilter
history *ring.Ring
mutex sync.Mutex
}
func NewSlidingBloomFilter(windowSize int, size uint64, numHashFuncs uint) *SlidingBloomFilter {
sbf := &SlidingBloomFilter{
windowSize: windowSize,
current: NewBloomFilter(size, numHashFuncs),
history: ring.New(windowSize),
}
// 初始化历史记录
for i := 0; i < windowSize; i++ {
sbf.history.Value = NewBloomFilter(size, numHashFuncs)
sbf.history = sbf.history.Next()
}
return sbf
}
func (sbf *SlidingBloomFilter) Add(item []byte) {
sbf.mutex.Lock()
defer sbf.mutex.Unlock()
sbf.current.Add(item)
}
func (sbf *SlidingBloomFilter) Rotate() {
sbf.mutex.Lock()
defer sbf.mutex.Unlock()
// 将当前布隆过滤器放入历史记录
sbf.history.Value = sbf.current
sbf.history = sbf.history.Next()
// 创建新的当前布隆过滤器
sbf.current = NewBloomFilter(sbf.current.size, uint(len(sbf.current.hashFuncs)))
}
func (sbf *SlidingBloomFilter) Test(item []byte) bool {
sbf.mutex.RLock()
defer sbf.mutex.RUnlock()
if sbf.current.Test(item) {
return true
}
// 检查历史记录
sbf.history.Do(func(x interface{}) {
if x != nil && x.(*BloomFilter).Test(item) {
return true
}
})
return false
}
性能对比
优化后的实现有以下改进:
- 使用uint64数组代替bool数组,节省了8倍内存
- 预生成hash函数,避免每次操作都创建
- 使用更细粒度的锁
- ring缓冲区实现了滑动窗口功能
使用示例
func main() {
// 创建布隆过滤器
bf := NewBloomFilter(1000000, 5)
// 添加元素
bf.Add([]byte("hello"))
bf.Add([]byte("world"))
// 测试元素
fmt.Println(bf.Test([]byte("hello"))) // true
fmt.Println(bf.Test([]byte("world"))) // true
fmt.Println(bf.Test([]byte("foo"))) // false
// 使用滑动窗口布隆过滤器
sbf := NewSlidingBloomFilter(10, 1000000, 5)
sbf.Add([]byte("item1"))
sbf.Rotate() // 时间窗口滑动
sbf.Add([]byte("item2"))
fmt.Println(sbf.Test([]byte("item1"))) // true (在历史窗口中)
fmt.Println(sbf.Test([]byte("item2"))) // true (在当前窗口中)
}
这个实现提供了高性能、线程安全的布隆过滤器功能,适用于需要快速判断元素是否存在的场景,如URL去重、缓存穿透防护等。