redis学习(3) - 布隆过滤器

目录

  • [布隆过滤器实战(基于 go-redis 位图)](#布隆过滤器实战(基于 go-redis 位图))
    • 核心概念
    • [参数速查(1% 误判)](#参数速查(1% 误判))

1970年由Burton Howard Bloom提出,旨在解决哈希表空间效率低的问题。当时计算机内存极其昂贵,需要一种概率型数据结构来高效判断元素是否存在于集合中,而无需存储完整数据。

布隆过滤器实战(基于 go-redis 位图)

  • 核心作用:高效判断"元素一定不存在"或"可能存在" - 用极小的空间代价换取时间效率,特别适合读多写少的场景。
  • 简单来说,布隆过滤器原理就是把一个key进行几次哈希,转化为一组bit,存储到一个bitmap里面,查询的时候,如果发现这个key转化出来的bit串的1在bitmap里面相应位置不都是1,说明这个key肯定没出现过,当然这里对于都是1的情况,可能存在误判(哈希冲突),比如本来key没出现过,但是计算完之后,发现在bitmap里面存在过,这时候流量就会打到数据库里面了,不过这种误判概率比较低,所以能够拦住大部分流量

核心概念

  • 位图长度 m:Redis 中用 1 个 bit 表示一个"桶",m 决定桶的总数;桶越多,冲突越少,误判率越低,但内存线性增加。
  • 哈希次数 k:每个元素进来后,用 k 个独立哈希函数算出 k 个桶位置,并把这些位设为 1;查询时同样检查这 k 位是否全为 1。
  • 误判率 p :当 k、m 固定后,可推算理论误判率;经验公式 p ≈ ( 1 − e − k n m ) k p ≈ (1 - e^{\frac{-kn}{m}})^k p≈(1−em−kn)k,n 为预期元素总量。

参数速查(1% 误判)

预估元素 n 位图长度 m 哈希次数 k 内存
100 万 1,140,000 7 ≈ 137 KiB
1 000 万 11,400,000 7 ≈ 1.34 MiB
1 亿 114,000,000 7 ≈ 13.4 MiB

代码示例如下

go 复制代码
type BloomFilter struct {
	rdb *redis.Client
	key string
	m   uint32 // 位图长度
	k   int    // 哈希长度
}

// 添加元素
func (b *BloomFilter) Add(ctx context.Context, value string) error {
	pos := b.hashPositions(value)
	pipe := b.rdb.Pipeline()
	for _, p := range pos {
		pipe.SetBit(ctx, b.key, int64(p), 1)
	}
	_, err := pipe.Exec(ctx)
	return err
}

// 可能存在
func (b *BloomFilter) Exists(ctx context.Context, value string) (bool, error) {
	pos := b.hashPositions(value)
	pipe := b.rdb.Pipeline()
	cmds := make([]*redis.IntCmd, len(pos))
	for i, p := range pos {
		cmds[i] = pipe.GetBit(ctx, b.key, int64(p))
	}
	if _, err := pipe.Exec(ctx); err != nil {
		return false, err
	}
	for _, c := range cmds {
		if c.Val() == 0 {
			return false, nil
		}
	}
	return true, nil
}

// 双重哈希生成K个位置
func (b *BloomFilter) hashPositions(value string) []uint32 {
	// 第一次哈希:决定起始桶
	h1 := fnv.New32a()
	h1.Write([]byte(value))
	base := h1.Sum32()

	// 第二次哈希:决定步长,保证 k 次位置独立且均匀
	h2 := sha1.Sum([]byte(value))
	step := binary.BigEndian.Uint32(h2[0:4])
	pos := make([]uint32, b.k) // 切片长度 = k, 体现"哈希次数"
	for i := 0; i < b.k; i++ {
		// 第i次位置 = (base + i * step) mod m
		pos[i] = (base + uint32(i)*step) % b.m
	}
	return pos
}

func TestBloom(t *testing.T) {
	filter := BloomFilter{
		rdb: redis.NewClient(&redis.Options{
			Addr: "127.0.0.1:6379",
		}),
		key: "bloom:uids",
		m:   1140000,
		k:   7,
	}
	key := "user:9527"
	err := filter.Add(t.Context(), key)
	if err != nil {
		t.Fatal(err)
	}
	exists, err := filter.Exists(t.Context(), key)
	if err != nil {
		t.Fatal(err)
	}
	t.Log("1", exists)
	if exists, err = filter.Exists(t.Context(), "user:004"); err != nil {
		t.Fatal(err)
	}
	t.Log("2", exists)
}
// 输出
// 1 true
// 2 false
相关推荐
菜的不敢吱声7 小时前
swift学习第2,3天
python·学习·swift
l04090442228 小时前
想学习VLN相关的知识,并亲手搭建一套系统,该如何入手?
学习
ANnianStriver8 小时前
redis安装包方式下载安装
数据库·redis·缓存
山土成旧客8 小时前
【Python学习打卡-Day36】实战重构:用PyTorch神经网络升级信贷预测项目
python·学习·重构
麻雀无能为力8 小时前
VAE(变分自编码器 Variational Auto-Encoder)学习笔记
笔记·学习
挽天java9 小时前
X86汇编语言期末复习
学习
北岛寒沫9 小时前
北京大学国家发展研究院 经济学原理课程笔记(第十九课 长期经济增长)
经验分享·笔记·学习
北芝科技9 小时前
AI在教育中的五大应用场景,助力教学与学习全面智能化解决方案
人工智能·学习
山沐与山9 小时前
【Redis】读写锁实战详解:读多写少场景的性能优化利器
数据库·redis·性能优化
草原上唱山歌9 小时前
推荐学习的C++书籍
开发语言·c++·学习