布隆过滤器去重千万级url需谨慎:cap设总容量(如1200万)、fprate选0.001,add需加锁而test可并发,test为true必须二次校验redis/db,key须标准化清洗。

别用 map[string]struct{} 直接去重千万级 URL,内存会爆到 1.2+ GB;布隆过滤器只要约 12MB,但必须配对二次校验——它只敢说“很可能重复”,不能代替真实存在性判断。
怎么初始化 bloom.New() 才不翻车
填错 cap 和 fpRate,后面所有操作都白搭:
-
cap必须填「整个生命周期内最多插入多少个唯一 URL」,不是当前量。比如爬虫日增 500 万、想撑 3 天并留 20% 余量,就得填12_000_000 -
fpRate别盲目设成0.0001。从0.01降到0.001,位数组大小涨约 45%;再降到0.0001,又涨约 30%,且初始化更慢。日常 URL 去重,0.001(千分之一)是性价比拐点 - 示例:
filter := bloom.New(12_000_000, 0.001)—— 库内部自动算出最优位数和哈希轮数,不用手算公式
Add() 并发写必须加锁,Test() 可无锁但绝不等于可信
bloom.Filter 底层是 []byte,多个 goroutine 同时调 Add() 会竞态写同一位,导致位被意外清零或漏置,误判率瞬间失控:
- 写操作(
Add/TestAndAdd)必须包一层sync.RWMutex或用sync.Pool管理单例 filter - 读操作(
Test)可放心并发调用,无需同步开销——因为单字节读取在 Go 中是原子的 - 千万别用
Test()判断后再手动Add():中间有竞态窗口,两个 goroutine 可能同时通过Test返回false,然后都执行Add,造成逻辑重复
Test() 返回 true 后必须查 Redis 或 DB
这是线上事故最高发环节:filter.Test(key) 返回 true 只表示“很可能存在”,不是“一定存在”。跳过二次校验等于主动接受误判,把新数据当重复项丢掉:
-
Test()返回false→ 可直接处理(如入库、发消息) -
Test()返回true→ 必须查真实存储确认是否真重复,推荐用带 TTL 的Redis SETNX,TTL 要覆盖业务最大处理周期 - 别在
Test()为true时再往布隆里写 —— 它不负责去重,只负责快速拦截 - 校验逻辑不能省:哪怕用了最稳的
github.com/yourbasic/bloom,漏掉这步就是生产事故
Key 输入必须严格清洗,否则 filter 彻底失效
布隆过滤器对输入极其敏感:"user_123" 和 "user_123\n"、"\ufeffuser_123" 在位数组中映射到完全不同的位置:
- 每行必须做
strings.TrimSpace(),且检查是否为空 - BOM 头需用
bytes.TrimPrefix()清除,别直接拿scanner.Text()结果Add() - URL 去重建议统一标准化:去掉末尾
/、转小写、解码 query 参数,再喂给filter.Add()
真正容易被忽略的,不是参数怎么填,而是“Test() 返回 true 后那一行查 Redis 的代码”——它看起来只是兜底,实则是整个链路唯一能保证不丢数据的地方。漏了,布隆再快也没用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











