去年做一套实时风控引擎,QPS 8k,要求 99.9% 请求 < 20 ms。我们先后把哈希表、布隆过滤器、跳跃表、红黑树轮番拉上线,最后留下一串“血与泪”的日志。今天把选型过程+事故细节拆开写,给后来人一个参考。
最早的核心是一张 64 GB 的内存哈希表,Key 是 uint64_t(user_id)
,Value 是一个 32 bit 的位图。上线第一周风平浪静,第二周突然报警:延迟飙到 200 ms。排查发现:
__memcmp_avx512
上打满。指标 | 正常节点 | 碰撞节点 |
---|---|---|
P99 延迟 | 8 ms | 210 ms |
CPU 利用率 | 45% | 97% |
链表长度 | 4 | 65536 |
修复:
把 bucket_count
从 2^28 调到 2^30,再把哈希种子固化(cityhash128_with_seed_v1
)。改造后延迟回到 9 ms,但内存多吃了 16 GB,老板骂了十分钟。
为了节省 16 GB,我们引入布隆过滤器:8 GB 内存,预计误判率 0.1%,用来挡掉 95% 的冷用户。结果:
事后总结:
版本 | bit 数/元素 | 哈希函数数 | 理论误判 | 实测误判 |
---|---|---|---|---|
v1 | 10 | 7 | 0.82% | 1.5% |
v2 | 14 | 10 | 0.10% | 0.25% |
v3 | 16 | 11 | 0.05% | 0.05% |
冷用户过滤完后,热用户需要按时间窗口排序。我们用 Go 自己撸了一个“极简”跳跃表,level 最大 32,期望 O(log n)。上线测试:
场景 | 本地测试 | 线上实测 |
---|---|---|
插入速率 | 100 w/s | 100 w/s |
GC STW | 0.3 ms | 40 ms |
P99 延迟 | 2 ms | 45 ms |
根因:
runtime.GOMAXPROCS(16)
+ sync.Pool
后,STW 降到 5 ms,但复杂度已经秒杀我们的耐心。结论:
“Redis 能跑,是因为它是单进程 + jemalloc,而不是跳跃表本身够快。”
最后把排序部分换成红黑树,找了一个开源的 llrb
实现(C + cgo)。
结构 | 每节点额外开销 | 总内存 | 延迟 P99 |
---|---|---|---|
跳跃表 | 24 byte | 30 GB | 45 ms |
红黑树 | 48 byte | 34 GB | 12 ms |
缓解方案:
数据结构 | 适用场景一句话 |
---|---|
哈希表 | 读多写少,key 分布均匀,内存管够 |
布隆过滤器 | 海量去重,接受误判,内存极度紧张 |
跳跃表 | 纯内存、单线程、要求区间查询 |
红黑树 | 实时排序+范围扫描,内存不是瓶颈 |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。