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
相关推荐
用户3074596982072 天前
Redis 延时队列详解
redis
烤代码的吐司君2 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
leeyi4 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
云技纵横5 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy6 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
用户31693538118312 天前
Java连接Redis
redis
通信小呆呆14 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
H__Rick14 天前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee14 天前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
小小工匠14 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化