
你们公司的 App 突然爆火,某条短视频的播放量突破了 1 亿。
产品经理提了个极其常见的需求:“给我统计一下这个视频的 实时 UV(独立观看人数)。”
你的技术挣扎:
绝境反击:
祭出 Redis 内置的神器:HyperLogLog。
无论你丢进去 1 万个、100 万个还是 1 亿个不同的 UUID,它永远只占用 12 KB 的内存!误差率严格控制在 0.81%。
这不是魔术,这是极其精妙的数学。
1. 核心原理解剖:抛硬币的直觉
HyperLogLog 是一个概率数据结构 (Probabilistic Data Structure)。它的核心思想是:通过局部特征的极值,来宏观估算整体的数量。
通俗的比喻:抛硬币
假设你一直在抛硬币,记录每次抛出“正面”和“反面”的结果。
- 如果你告诉我,你最多连续抛出了 2 次反面。我猜你大概总共抛了 22=42^2 = 422=4 次。
- 如果你告诉我,你最多连续抛出了 10 次反面。那我估算你至少抛了 210=10242^{10} = 1024210=1024 次硬币。
放到计算机里怎么做?
2. 从 LogLog 到 Hyper:分桶与调和平均数
你肯定会发现一个致命 Bug:“如果我运气极好,抛第一次就连续出了 10 个反面呢?”
这种极端的离群点 (Outlier) 会导致 2k2^k2k 的估算极其离谱。
为了解决这个误差,算法进行了两次进化:
进化一:分桶法 (分而治之)
不要把所有鸡蛋放在一个篮子里。Redis 将 64 位哈希值的前 14 位截取下来,作为桶的编号 (Bucket Index)。
214=163842^{14} = 16384214=16384。也就是说,Redis 准备了 16384 个桶(寄存器)。
剩下的 50 位用来计算前导零的个数 kkk,并把最大的 kkk 更新到对应的桶里。
进化二:调和平均数 (Harmonic Mean) —— 这就是 Hyper 的由来
最后计算总数时,不能直接求算术平均数(因为算术平均数极易受异常大值的影响,比如国民平均收入)。
HyperLogLog 采用了调和平均数。调和平均数的特性是:它总是偏向于较小的值,能有效抵抗极大值的干扰。
最终的内存账本:
16384 个桶,每个桶需要记录最大的前导零个数 kkk。因为剩下的哈希位最多 50 位,所以 kkk 最大是 50。
要存下数字 50,只需要 6 个 bit(26=642^6 = 6426=64)。
总内存 = 16384 桶×6 bit/桶/8 (转成字节)=12288 Bytes=12KB16384 \\text{ 桶} \\times 6 \\text{ bit/桶} / 8 \\text{ (转成字节)} = 12288 \\text{ Bytes} = \\mathbf{12 KB}16384 桶×6 bit/桶/8 (转成字节)=12288 Bytes=12KB。
这就是 12KB 的由来!
3. Redis 中的实战操作
在 Redis 中,HyperLogLog 的命令前缀都是 PF,这是为了纪念该算法的发明者、法国数学家 Philippe Flajolet。
PFADD video:10086:uv "user_uuid_1" "user_uuid_2"
时间复杂度 O(1)。
2. **统计基数:PFCOUNT key [key …]**
PFCOUNT video:10086:uv
返回估算的 UV 数。
3. **合并统计 (极具实战价值):PFMERGE destkey sourcekey [sourcekey …]**
PFMERGE video:10086:weekly_uv video:10086:mon video:10086:tue …
将多个 HLL 合并成一个新的 HLL,用于计算周活、月活。
4. 三大核心实战场景
场景一:海量页面/视频 UV 统计
- 特点: 数据量巨大,只关心去重后的规模,不关心具体是谁。
- 业务: 淘宝首页每天的独立访客、B 站爆款视频的真实观看人数。使用 PFADD 记录每次访问,后台定时用 PFCOUNT 读出数据展现在仪表盘上。
场景二:复杂维度的用户聚合统计 (DAU / WAU / MAU)
- 痛点: 算周活 (WAU) 时,不能简单把 7 天的日活 (DAU) 加起来,因为一个人可能周一和周二都登录了,必须跨天去重。
- HLL 方案: 每天生成一个 dau:yyyy-mm-dd 的 HLL。到了周末,直接用 PFMERGE 把 7 天的 HLL 合并成一个 wau:week_X。底层的位运算合并极快,且合并后的数据依然完美去重,大小还是 12KB!
场景三:实时广告防刷与风控
- 业务: 统计某个 API 接口在 1 分钟内被多少个不同的独立 IP 访问过。如果独立 IP 数突增,说明遭到了代理池攻击。
- 方案: 内存极其有限的网关层,用 HLL 记录 IP 集合,以 0.81% 的极小误差,换取极高的吞吐量和极低的内存负担。
5. 避坑指南:不可违背的物理学法则
既然 HLL 这么神,为什么不把所有的 COUNT(DISTINCT) 都替换掉?
如果你的业务是财务对账、精确发奖(必须准确知道是 1000 人还是 1001 人),绝对不能用 HLL。HLL 适用于宏观的统计大盘(1000 万 UV 和 1008 万 UV 在宏观决策上没有区别)。
HLL 是单向的“黑洞”。你把 uuid 丢进去后,它就变成了二进制的概率记号。你永远无法通过 HLL 查出“今天到底有哪些 UUID 访问过”,也无法判断 SISMEMBER(某个用户今天是否登录过,请用 Bitmap 或 Bloom Filter)。
总结
HyperLogLog 是工程学与概率统计学结合的巅峰之作。
它牺牲了 0.81% 的精准度,牺牲了数据的可逆性,换来的是百万倍的内存空间节省。在“大厂”的亿级流量架构中,掌握 HLL 是从“初级增删改查”走向“海量数据架构师”的必经之路。

