基数统计 HyperLogLog
本教程共 40 篇 · 第 12 篇 · 更新于 2026-08-02
12. 基数统计 HyperLogLog
本节目标:
- 理解「基数(cardinality)」的含义与 HyperLogLog 的近似统计思路
- 掌握
PFADD/PFCOUNT/PFMERGE三个核心命令- 理解 HyperLogLog「占用内存极小但不存元素本身」的取舍
- 用「页面 UV 统计」「多站点去重合并」等实战巩固
- 明确它与 Set(精确去重)的适用边界
先说一个概念:基数(cardinality) 是指一个集合里「不重复元素」的数量。例如数据集 {1, 3, 5, 7, 5, 7, 8},去重后是 {1, 3, 5, 7, 8},基数就是 5。统计基数在业务里非常常见:一个页面被多少个不同用户访问过(UV)、一个关键词被多少个不同 IP 搜索过、直播间有多少个独立观众。
问题在于:当用户量上亿时,用 Set 精确存每个用户 ID,内存会爆炸。HyperLogLog(HLL)就是为解决这个问题而生的——它用一种概率算法,在只占用约 12 KB 内存的前提下,估算出接近 2^64 个不同元素的基数,标准误差约 0.81%。
12-1 三个核心命令
向 HyperLogLog 添加元素用 PFADD:
命令:
127.0.0.1:6379> PFADD uv:2026-08-02 "user:1001" "user:1002" "user:1003"
127.0.0.1:6379> PFADD uv:2026-08-02 "user:1001"
输出:
(integer) 1
(integer) 0
返回值 1 表示「本次添加可能改变了基数估计」(即大概率遇到了新元素),0 表示基数估计没有变化。user:1001 第二次加入,不再是新用户,所以返回 0。
估算基数用 PFCOUNT:
命令:
127.0.0.1:6379> PFCOUNT uv:2026-08-02
输出:
(integer) 3
这里得到的是「约 3 个独立用户」。注意它是估算值,重复添加同一元素不会让结果变大。
把多个 HyperLogLog 合并成一个用 PFMERGE(并集去重):
命令:
127.0.0.1:6379> PFADD uv:pc "user:1001" "user:2001"
127.0.0.1:6379> PFADD uv:mobile "user:1002" "user:2001"
127.0.0.1:6379> PFMERGE uv:all uv:pc uv:mobile
127.0.0.1:6379> PFCOUNT uv:all
输出:
(integer) 3
合并后 user:2001 只算一次,所以全平台独立用户是 3 个。PFMERGE 后的结果同样可以继续被 PFCOUNT 或再次 PFMERGE,且合并操作本身不破坏原始 key。
12-2 HyperLogLog 的本质取舍
理解 HLL 最重要的是认清它的取舍:
- 极省内存:无论你统计 1 万个还是 10 亿个元素,每个 HLL key 固定约 12 KB。
- 不存元素本身:HLL 只记录「为了估算基数所需的统计信息」,你无法取回「都有哪些元素」——这是它和 Set 最根本的区别。
- 结果是近似的:标准误差约 0.81%,对「UV 大概多少」够用,但对「精确去重名单」无能为力。
- 可合并:多个 HLL 可以无损合并(并集去重),方便分片统计后汇总。
12-3 实战场景
场景一:页面 / 接口 UV 统计。 用户每次访问就 PFADD uv:page:xxx:<日期> <userId>,要展示当日 UV 时 PFCOUNT 即可。一天一个 key,配合过期时间自动清理。
场景二:分渠道去重汇总。 把 PC、移动、小程序各自的 UV 用 PFADD 分别记录,再用 PFMERGE 得到全平台 UV,避免把所有用户 ID 堆在一起。
场景三:搜索词独立 IP 数、直播间独立观众数等一切「只关心数量、不关心具体是谁」的大基数统计。
12-4 HyperLogLog 与 Set 怎么选
这是高频疑问,记住一句话:要名单用 Set,只要数量用 HLL。
| 需求 | 选 Set | 选 HyperLogLog |
|---|---|---|
| 想知道「有哪些」独立元素 | ✅ | ❌(不保存元素) |
| 只想知道「大概多少个」独立元素 | 小数据量可行 | ✅(上亿也不怕) |
| 内存紧张、数据量巨大 | ❌ | ✅(约 12 KB 恒定) |
| 需要精确值(零误差) | ✅ | ❌(约 0.81% 误差) |
提示:HyperLogLog 自 Redis 2.8.9 起即内置,不属于「需要额外模块」的类型;在 Redis 8.x 中它仍是开箱即用的概率型数据结构之一(同类还有 Bloom / Cuckoo / T-Digest / Top-K / Count-Min Sketch,详见第 15 章)。本教程正文统一以 Redis 8.x(最新稳定版)为准。
12-5 常见误区
- 误区:PFCOUNT 返回精确值。 它是估算值,误差约 0.81%,不要拿它做对账级的精确统计。
- 误区:PFADD 后能用 SMEMBERS 之类取元素。 HLL 不保存原始元素,无法回取成员列表。
- 误区:HLL 比 Set 省内存就处处用它。 当需要「精确去重 + 列出成员」时,HLL 做不到,该用 Set 还得用 Set。
- 误区:PFMERGE 会把源 key 清空。 不会,
PFMERGE dest src1 src2是把并集写入dest,源 key 保持不变。
12.x HLL 的原理直觉与适用边界
HyperLogLog 为什么能用约 12 KB 估算上亿基数?直觉上它并不「记录每个元素」,而是观察「这些元素的哈希值里,前缀连续出现多少个 0」这种极端事件的概率——如果经常看到很长的连续 0 前缀,说明元素基数很大;反之则很小。通过多个这样的「桶」做调和平均并修正偏差,就能在极小空间内给出误差约 0.81% 的估算。也正是因为它只保留统计用的比特图案、不保留原始元素,所以既不能回取成员列表,也不能回答「某个具体元素是否在集合里」(要判断成员是否存在,请用 Set 或 Bloom Filter)。
把 HLL 和另外两种「省内存」方案放在一起对比会更清晰:Bitmap 用 1 bit 表示一个布尔状态,适合「是否来过」且要逐位操作;Set 精确存成员、内存随成员数线性增长;HLL 则在「只想知道大概多少独立元素」时把内存压到恒定约 12 KB。所以当业务诉求是「精确名单 + 精确计数」选 Set,「逐位布尔 + 可能要名单」选 Bitmap,「只求近似去重数量」选 HLL。
还有一个工程细节:HLL 的误差是概率性的,且 PFCOUNT 的返回值可能在不同实例、不同时间略有波动(因为内部估算本身带随机性)。在需要「对账级精确值」的场景(如财务口径的日活),HLL 不能替代精确统计,它定位是「监控与趋势」级别的大数估算。
12-6 小结
- 基数 = 集合里不重复元素的数量;HLL 是估算基数的概率算法。
- 三个命令:
PFADD(添加)、PFCOUNT(估算基数)、PFMERGE(合并多个 HLL 的并集)。 - 优点:约 12 KB 固定内存、支持合并、可估算至 2^64;缺点:不存元素、结果近似(误差约 0.81%)。
- 适用:UV、独立访客、独立搜索词等大基数「只计数不列名」场景。
提示:关于版本——本教程正文统一以 Redis 8.x(最新稳定版)为准;官方 redis.io 下载页另标 8.8 为 “Latest stable”,而 GitHub 上 redis/redis 的最新发布 tag 为 8.10.0,二者同属 8.x,命令与类型差异对教材影响极小。