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
相关推荐
默默开发1 小时前
完整版:本地电脑 + WiFi 搭建 AI 自动炒股 + 自我学习系统
人工智能·学习·电脑
for_ever_love__1 小时前
Objective-C学习 NSSet 和 NSMutableSet 功能详解
开发语言·学习·ios·objective-c
haixingtianxinghai1 小时前
Redis的定期删除和惰性删除
数据库·redis·缓存
盐水冰9 小时前
【烘焙坊项目】后端搭建(12) - 订单状态定时处理,来单提醒和顾客催单
java·后端·学习
Hello小赵9 小时前
视频压缩编码学习(一)—— 基础知识大集合
学习
似水明俊德10 小时前
02-C#.Net-反射-学习笔记
开发语言·笔记·学习·c#·.net
adore.96811 小时前
3.18 复试学习
学习
留白_11 小时前
MySQL学习(9)——索引
学习
请你喝好果汁64111 小时前
生信学习笔记:ArchR 处理小麦单细胞 ATAC-seq 中的细胞数差异与 Embedding 报错调试
学习
jinanwuhuaguo12 小时前
OpenClaw、飞书、Claude Code、Codex:四维AI生态体系的深度解构与颗粒化对比分析
大数据·人工智能·学习·飞书·openclaw