文章目录一、HyperLogLog 到底解决什么问题二、PFADD游客经过闸机三、PFCOUNT大屏显示的是估算值四、它为什么只用大约 12 KB五、哈希与连续零为什么能猜出人数六、稀疏与稠密小客流不必立刻铺满大屏七、为什么 TYPE 是 string八、PFMERGE合并多天与多个园区九、Set 与 HyperLogLog 怎么选十、按天分桶、TTL 与归档十一、并发、持久化与 Cluster1. 并发写入2. 持久化与复制3. Cluster 同槽十二、生产环境常见误区1. 把 PFADD 返回值当“是否新用户”2. 认为 0.81% 是每次查询的硬性误差上限3. 需要精确计费却为了省内存选择 HLL4. 以为 HLL 能返回游客名单5. 把每日估算值直接相加6. 忘记给时间分桶设置 TTL7. 用普通 String 命令修改 HLL 内容8. 在生产环境执行 PFDEBUG十三、完整 redis-cli 实验十四、快速判断口诀参考资料周六早上九点“星光游乐园”刚开门售票系统的大屏就开始跳数字。游客小林早上刷身份证进园中午出去吃饭下午又回来坐过山车晚上为了看烟花再次入园。闸机一共响了三次可运营经理真正想知道的不是“今天闸机响了多少次”而是“今天到底来了多少个不同的人”。这就是两个经常被混在一起的指标PV访问次数小林进园三次就记三次UV独立访客数小林无论进出多少次只记一个人。如果游乐园只有几十名游客拿一本签到册记下每个人的编号再做去重就行。可当一个大型平台每天需要统计几千万个用户、设备、搜索词或广告访客时“记住每一个人”会变成一座越来越大的票据仓库。Redis HyperLogLog 提供了另一种思路不保存完整游客名单只保留一组足以估算人数的统计特征。它像一个记性很奇怪的检票员你问他“游客 10086 来过吗”他答不上来但你问“今天大约来了多少个不同游客”他只用很小的记事板就能给出误差通常不到 1% 的答案。图 1闸机记录了很多进出事件而运营大屏关注的是“不同游客的大约人数”。本文命令均在 Redis 8.6.1 的独立临时实例中验证实验端口为 6406结束后已经停止不会连接或修改日常使用的 6379。一、HyperLogLog 到底解决什么问题HyperLogLog 解决的是基数统计问题。基数可以简单理解为一个集合里不同元素的数量。[小林, 小美, 小林, 老王, 小美] 总记录数5 去重后的元素小林、小美、老王 基数3在游乐园故事中对应关系如下游乐园Redis HyperLogLog含义游客身份证号Element用来去重的稳定标识检票闸机PFADD把一次观察交给统计器客流大屏PFCOUNT查看不同游客的估算数量合并多个园区报表PFMERGE生成多个 HLL 的并集闸机里的小计数格Register保存哈希特征不保存游客名单图 2HyperLogLog 记住的是游客编号经过哈希后的统计特征不是游客本人或完整编号。常见使用场景包括网站每日、每周、每月 UV活跃设备数和独立 IP 数不同搜索词、商品访客或广告触达人数多个机房、城市或业务分区的去重汇总只关心数量级、不需要成员明细的大规模统计。它不适合余额、库存、计费、中奖名单和权限名单因为这些业务不能接受“差不多”。二、PFADD游客经过闸机使用PFADD把一个或多个元素加入 HyperLogLogPFADD lab:hll:{park}:uv visitor:42 PFADD lab:hll:{park}:uv visitor:43 visitor:44第一次加入visitor:42通常返回 1再次加入同一个元素通常返回 0127.0.0.1:6406PFADD lab:hll:{park}:small visitor:42(integer)1127.0.0.1:6406PFADD lab:hll:{park}:small visitor:42(integer)0很多人看到这里会顺手写出这样的判断PFADD 返回 1 新游客 PFADD 返回 0 老游客这恰恰是 HyperLogLog 最容易踩的坑。PFADD返回 1只代表至少一个内部寄存器发生了变化返回 0只代表这次元素没有让内部状态发生变化。一个从未出现过的新元素也可能因为它提供的哈希特征不够“特别”无法刷新任何寄存器于是返回 0。本文实验先加入 10000 名游客再继续加入新游客找到了这样一个确定的反例New element whose PFADD returned 0: visitor:10003visitor:10003在精确 Set 中是新成员SADD返回 1但它的PFADD返回 0。所以请记住PFADD的返回值用于判断 HLL 内部状态是否改变不能用于判断某个用户是否首次出现。PFADD对每个元素的处理为 O(1)。一次命令加入 N 个元素总工作量随 N 增长。三、PFCOUNT大屏显示的是估算值查看一个 HyperLogLog 的基数使用PFCOUNTPFCOUNT lab:hll:{park}:uv假设 Set 精确保存了 10000 名游客Redis 8.6.1 在本文数据集上的结果如下Exact Set cardinality: 10000 HyperLogLog estimate: 9951 Observed absolute error: 0.4900%少了 49 人这不是 Redis 丢数据而是概率型数据结构的正常行为。Redis 官方给出的 HyperLogLog 标准误差约为0.81%。这里的“0.81%”不是承诺每一次结果都精确落在固定范围内也不是说每 100 人固定少算或多算 0.81 人。它描述的是算法误差的统计特征。如果业务说“报表允许千分之几到百分之一左右的估算误差”HyperLogLog 很合适如果财务说“少一个人也不行”请使用精确结构或数据库统计。PFCOUNT还能直接接收多个 Key返回它们并集的估算基数PFCOUNT\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24它不会把两个 Key 的估算值直接相加因为昨天和今天可能有同一批游客。Redis 会合并寄存器特征后再估算并集因此能够处理跨天重复。四、它为什么只用大约 12 KB如果用 Set 统计游客Redis 必须保存每个游客编号。人数从 1 万增长到 1 亿内存也会跟着增长。HyperLogLog 不保存游客编号。它对每个元素计算 64 位哈希然后完成两件事从哈希中取 14 位选择 16384 个寄存器中的一个在剩余哈希片段中统计连续零的长度只保留该寄存器见过的最大值。每个寄存器只需 6 bit16384 × 6 bit 98304 bit 12288 byte ≈ 12 KB再加上 16 字节头部和 Redis 对象开销本文的MEMORY USAGE实测为HyperLogLog memory usage: 12848 bytes Set memory usage: 575606 bytes在这组 1 万名游客的实验中Set 约占 575 KBHLL 约占 12.8 KB。数据继续增加时Set 还会增长而稠密 HLL 的主体仍保持在 12 KB 左右。这就是它最大的价值用固定量级的空间换取可控的统计误差。五、哈希与连续零为什么能猜出人数想象检票员抛硬币第一次就是正面概率是 1/2连续两个反面后才出现正面概率大约是 1/4连续十个反面后才出现正面概率大约是 1/1024。连续出现很多个零是一件比较罕见的事。如果观察样本中出现了“连续 14 个零”这样的哈希特征通常意味着检票员已经看过相当多的不同游客。但只用一个最大连续零值运气影响会很大第一位游客可能碰巧就很特殊。因此 HyperLogLog 把游客分散到 16384 个寄存器让每个寄存器独立记录最大值最后通过调和平均和偏差修正得到整体估算。图 3哈希的一部分选择寄存器另一部分提供连续零特征寄存器只保留历史最大值。资料经常称它为“统计前导零”。Redis 8.6 源码具体从剩余哈希片段的一端连续数零。哈希值可以视为均匀随机位串因此从哪一端计数不影响这里的概率直觉。这里还有一个很重要的工程结论两个相同元素会产生相同哈希落到相同寄存器并提供相同连续零长度所以重复上报不会持续增加估算值。六、稀疏与稠密小客流不必立刻铺满大屏16384 个寄存器全部按 6 bit 展开需要约 12 KB。这已经很小但如果一个 Key 只看过三五名游客立刻分配完整面板仍有些浪费。Redis 因此提供两种内部表示sparse稀疏大量寄存器还是 0 时用游程编码压缩连续的零dense稠密数据增多后把 16384 个 6 bit 寄存器紧密排列。可以把它想成游乐园刚开门时检票员只在纸上记“前 5000 个格子都是 0”客流大了以后再展开完整电子面板直接读写每个格子。本文隔离实验使用内部调试命令观察到PFDEBUG small encoding (isolated lab only): sparse PFDEBUG large encoding (isolated lab only): densePFDEBUG属于管理员内部调试命令官方标记为 dangerous。它只适合隔离实验生产环境不要为了好奇去执行。七、为什么 TYPE 是 stringHyperLogLog 在概念上是一种独立数据结构但 Redis 并没有为它增加新的顶层对象类型而是把二进制内容编码在 String 中。TYPE lab:hll:{park}:uv OBJECT ENCODING lab:hll:{park}:uv本文实测TYPE: string OBJECT ENCODING: raw这里的raw与前面的 sparse/dense 并不矛盾OBJECT ENCODING看到的是 Redis String 的外层编码sparse/dense 描述的是 String 字节内容里的 HLL 内部格式。技术上官方文档提到 HLL 可以通过GET取出二进制值再通过SET恢复。但普通业务代码不应随意APPEND、SETRANGE或覆盖它否则可能制造一个格式损坏的 HLL。最安全的原则是同一个 Key 一旦作为 HyperLogLog 使用就只让 PF 系列命令管理它。命令为什么都以PF开头这是为了纪念 HyperLogLog 论文作者之一 Philippe Flajolet。PFADD不是 “Probability Filter Add”而是一枚藏在命令名里的致敬彩蛋。八、PFMERGE合并多天与多个园区游乐园每天维护一个 HLLlab:hll:{park}:2026-08-23 lab:hll:{park}:2026-08-24 lab:hll:{park}:2026-08-25如果只是临时查看多天并集可以直接PFCOUNT\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24如果要生成可复用的周报 Key使用PFMERGEPFMERGE lab:hll:{park}:week-34\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24 PFCOUNT lab:hll:{park}:week-34本文实验中8 月 23 日加入游客 160008 月 24 日加入游客 400110000两天存在 2000 名重复游客。结果如下Day 1 estimate: 6007 Day 2 estimate: 5957 Multi-key PFCOUNT union: 9951 PFMERGE result: OK Merged weekly estimate: 9951绝不能把 6007 和 5957 直接相加。两天合计不是 11964 个不同游客HLL 的并集估算为 9951接近真实的 10000。图 4每日客流中有重复游客PFMERGE合并的是统计特征而不是把每日数字简单相加。九、Set 与 HyperLogLog 怎么选游乐园有两个房间档案室 Set保存每一张游客卡可以查“谁来过”客流估算器 HLL只有一块小面板只能给出“约来了多少人”。图 5Set 用空间换精确和明细HyperLogLog 用少量误差换固定量级内存。问题选择今天大约有多少独立访客HyperLogLog用户 10086 今天是否访问过Set、Bitmap 或数据库列出今天所有访客Set、数据库或日志系统精确统计付费用户数量精确结构统计上亿设备的大致去重数HyperLogLog同时需要近似总量与具体名单HLL 明细系统各司其职如果 ID 连续、需要判断某个用户是否出现可以看看已有的 Redis Bitmap 文章。Bitmap 也省内存但它的空间受最大 offset 影响HyperLogLog 不保存成员状态只估算数量。十、按天分桶、TTL 与归档不要把所有历史 UV 永远塞进一个 Key否则你只能得到“开站以来总人数”很难回答今天、昨天和本周的问题。常见设计是按时间分桶uv:{park}:2026-08-24 uv:{park}:2026-08-25 uv:{park}:2026-W35 uv:{park}:2026-08每日 Key 接收实时PFADD周/月 Key通过PFMERGE生成。原始日 Key 保留一段时间后过期EXPIRE lab:hll:{park}:2026-08-24604800HyperLogLog 没有成员级 TTL。TTL 作用于整个 Key时间到后整张“当天客流统计板”被删除不是单独忘掉某个游客。如果报表需要长期保存可以定时把最终估算值写入数据库或数据仓库。Redis HLL 更适合实时和近实时统计不应被误认为完整审计日志。十一、并发、持久化与 Cluster1. 并发写入单条PFADD命令在 Redis 中原子执行多个应用实例可以并发向同一个 HLL 上报。需要注意的是Hot Key 仍可能把流量集中到单个 Redis 节点高峰场景可先按城市、业务线或时间窗口拆分再合并统计。2. 持久化与复制HLL 本质上是 Redis 数据RDB、AOF、主从复制都会像处理其他 Key 一样处理它。是否能接受持久化间隔中的数据丢失取决于业务要求重要 UV 通常还会保留原始事件日志便于重新计算。3. Cluster 同槽PFMERGE和多 KeyPFCOUNT涉及多个 Key。在 Redis Cluster 中相关 Key 必须位于同一个 Hash Slot因此示例统一使用{park}lab:hll:{park}:2026-08-23 lab:hll:{park}:2026-08-24 lab:hll:{park}:week-34大括号内的{park}是 Hash Tag保证这些 Key 使用同一段内容计算槽位。不要把所有城市都强塞进同一个{park}否则会把全局流量集中到一个分片。更合理的设计是{beijing}、{shanghai}分城市统计需要全局报表时在应用层或离线系统汇总。十二、生产环境常见误区1. 把 PFADD 返回值当“是否新用户”新元素也可能返回 0。HLL 不支持成员存在性判断。2. 认为 0.81% 是每次查询的硬性误差上限它是标准误差不是逐次保证。关键业务必须用自己的数据规模和分布验证。3. 需要精确计费却为了省内存选择 HLL广告结算、抽奖、库存和资金不能用近似值。4. 以为 HLL 能返回游客名单它早已丢掉了原始元素只保留寄存器状态无法枚举或反查成员。5. 把每日估算值直接相加跨天有重复用户应使用多 KeyPFCOUNT或PFMERGE计算并集。6. 忘记给时间分桶设置 TTL单个稠密 HLL 只有约 12 KB但每天、每城市、每页面创建大量 Key 后总量仍然需要治理。7. 用普通 String 命令修改 HLL 内容外层类型虽然是 String内容却有严格格式。业务写入只使用 PF 系列命令。8. 在生产环境执行 PFDEBUG它是内部管理员调试命令官方标记为 dangerous。观察编码应在隔离实验中完成。图 6先问业务能否接受近似再检查是否需要成员明细、时间分桶和 Cluster 同槽。十三、完整 redis-cli 实验下面给出一组核心命令。请在测试实例执行不要在生产环境随意FLUSHDB或使用PFDEBUG。# 1. 基础加入与重复加入PFADD lab:hll:{park}:small visitor:42 PFADD lab:hll:{park}:small visitor:42 PFCOUNT lab:hll:{park}:small# 2. 查看外层类型与内存TYPE lab:hll:{park}:uv OBJECT ENCODING lab:hll:{park}:uv MEMORY USAGE lab:hll:{park}:uv# 3. 查看两天并集PFCOUNT\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24# 4. 生成周统计PFMERGE lab:hll:{park}:week-34\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24 PFCOUNT lab:hll:{park}:week-34# 5. 给每日 Key 设置 7 天 TTLEXPIRE lab:hll:{park}:2026-08-23604800TTL lab:hll:{park}:2026-08-23本文完整自动化实验得到的关键输出Redis version: 8.6.1 Exact Set cardinality: 10000 HyperLogLog estimate: 9951 Observed absolute error: 0.4900% HyperLogLog memory usage: 12848 bytes Set memory usage: 575606 bytes TYPE: string OBJECT ENCODING: raw PFDEBUG small encoding (isolated lab only): sparse PFDEBUG large encoding (isolated lab only): dense Estimate before duplicate replay: 9951 Estimate after duplicate replay: 9951 New element whose PFADD returned 0: visitor:10003 Multi-key PFCOUNT union: 9951 PFMERGE result: OK Merged weekly estimate: 9951 ALL ASSERTIONS PASSED Port 6406 released这些数字是 Redis 8.6.1 在本文固定数据集上的一次实测用于理解相对差异换版本、元素或哈希分布后估算值可能不同。十四、快速判断口诀最后用一段游乐园口诀收尾游客次数看 PV不同游客才是 UVSet 记住每张票HLL 只看特殊号PFADD 零或一不是新老身份证十六K格十二KB少量误差换空间多天人数别相加PFMERGE 来做并集要名单就别选它要计费更不能差Cluster 多 Key 同槽时间分桶记得清。HyperLogLog 的价值不在于“神奇地算得完全正确”而在于它诚实地接受一点误差把原本随人数膨胀的去重问题压缩到固定量级的内存中。当你只需要知道“今天大约来了多少个不同的人”它就像游乐园门口那块小巧而高效的客流估算器当你需要知道“具体是谁、是否来过、该收多少钱”请把问题交给 Set、Bitmap、数据库或明细日志。参考资料Redis HyperLogLog 官方文档PFADD 命令PFCOUNT 命令PFMERGE 命令PFDEBUG 命令Redis 8.6 hyperloglog.c 源码Redis Cluster 规范个人小游戏