
文章目錄一、HyperLogLog 到底解決什么問題二、PFADD游客經(jīng)過閘機(jī)三、PFCOUNT大屏顯示的是估算值四、它為什么只用大約 12 KB五、哈希與連續(xù)零為什么能猜出人數(shù)六、稀疏與稠密小客流不必立刻鋪滿大屏七、為什么 TYPE 是 string八、PFMERGE合并多天與多個(gè)園區(qū)九、Set 與 HyperLogLog 怎么選十、按天分桶、TTL 與歸檔十一、并發(fā)、持久化與 Cluster1. 并發(fā)寫入2. 持久化與復(fù)制3. Cluster 同槽十二、生產(chǎn)環(huán)境常見誤區(qū)1. 把 PFADD 返回值當(dāng)“是否新用戶”2. 認(rèn)為 0.81% 是每次查詢的硬性誤差上限3. 需要精確計(jì)費(fèi)卻為了省內(nèi)存選擇 HLL4. 以為 HLL 能返回游客名單5. 把每日估算值直接相加6. 忘記給時(shí)間分桶設(shè)置 TTL7. 用普通 String 命令修改 HLL 內(nèi)容8. 在生產(chǎn)環(huán)境執(zhí)行 PFDEBUG十三、完整 redis-cli 實(shí)驗(yàn)十四、快速判斷口訣參考資料周六早上九點(diǎn)“星光游樂園”剛開門售票系統(tǒng)的大屏就開始跳數(shù)字。游客小林早上刷身份證進(jìn)園中午出去吃飯下午又回來坐過山車晚上為了看煙花再次入園。閘機(jī)一共響了三次可運(yùn)營經(jīng)理真正想知道的不是“今天閘機(jī)響了多少次”而是“今天到底來了多少個(gè)不同的人”。這就是兩個(gè)經(jīng)常被混在一起的指標(biāo)PV訪問次數(shù)小林進(jìn)園三次就記三次UV獨(dú)立訪客數(shù)小林無論進(jìn)出多少次只記一個(gè)人。如果游樂園只有幾十名游客拿一本簽到冊(cè)記下每個(gè)人的編號(hào)再做去重就行??僧?dāng)一個(gè)大型平臺(tái)每天需要統(tǒng)計(jì)幾千萬個(gè)用戶、設(shè)備、搜索詞或廣告訪客時(shí)“記住每一個(gè)人”會(huì)變成一座越來越大的票據(jù)倉庫。Redis HyperLogLog 提供了另一種思路不保存完整游客名單只保留一組足以估算人數(shù)的統(tǒng)計(jì)特征。它像一個(gè)記性很奇怪的檢票員你問他“游客 10086 來過嗎”他答不上來但你問“今天大約來了多少個(gè)不同游客”他只用很小的記事板就能給出誤差通常不到 1% 的答案。圖 1閘機(jī)記錄了很多進(jìn)出事件而運(yùn)營大屏關(guān)注的是“不同游客的大約人數(shù)”。本文命令均在 Redis 8.6.1 的獨(dú)立臨時(shí)實(shí)例中驗(yàn)證實(shí)驗(yàn)端口為 6406結(jié)束后已經(jīng)停止不會(huì)連接或修改日常使用的 6379。一、HyperLogLog 到底解決什么問題HyperLogLog 解決的是基數(shù)統(tǒng)計(jì)問題?;鶖?shù)可以簡單理解為一個(gè)集合里不同元素的數(shù)量。[小林, 小美, 小林, 老王, 小美] 總記錄數(shù)5 去重后的元素小林、小美、老王 基數(shù)3在游樂園故事中對(duì)應(yīng)關(guān)系如下游樂園Redis HyperLogLog含義游客身份證號(hào)Element用來去重的穩(wěn)定標(biāo)識(shí)檢票閘機(jī)PFADD把一次觀察交給統(tǒng)計(jì)器客流大屏PFCOUNT查看不同游客的估算數(shù)量合并多個(gè)園區(qū)報(bào)表PFMERGE生成多個(gè) HLL 的并集閘機(jī)里的小計(jì)數(shù)格Register保存哈希特征不保存游客名單圖 2HyperLogLog 記住的是游客編號(hào)經(jīng)過哈希后的統(tǒng)計(jì)特征不是游客本人或完整編號(hào)。常見使用場(chǎng)景包括網(wǎng)站每日、每周、每月 UV活躍設(shè)備數(shù)和獨(dú)立 IP 數(shù)不同搜索詞、商品訪客或廣告觸達(dá)人數(shù)多個(gè)機(jī)房、城市或業(yè)務(wù)分區(qū)的去重匯總只關(guān)心數(shù)量級(jí)、不需要成員明細(xì)的大規(guī)模統(tǒng)計(jì)。它不適合余額、庫存、計(jì)費(fèi)、中獎(jiǎng)名單和權(quán)限名單因?yàn)檫@些業(yè)務(wù)不能接受“差不多”。二、PFADD游客經(jīng)過閘機(jī)使用PFADD把一個(gè)或多個(gè)元素加入 HyperLogLogPFADD lab:hll:{park}:uv visitor:42 PFADD lab:hll:{park}:uv visitor:43 visitor:44第一次加入visitor:42通常返回 1再次加入同一個(gè)元素通常返回 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很多人看到這里會(huì)順手寫出這樣的判斷PFADD 返回 1 新游客 PFADD 返回 0 老游客這恰恰是 HyperLogLog 最容易踩的坑。PFADD返回 1只代表至少一個(gè)內(nèi)部寄存器發(fā)生了變化返回 0只代表這次元素沒有讓內(nèi)部狀態(tài)發(fā)生變化。一個(gè)從未出現(xiàn)過的新元素也可能因?yàn)樗峁┑墓L卣鞑粔颉疤貏e”無法刷新任何寄存器于是返回 0。本文實(shí)驗(yàn)先加入 10000 名游客再繼續(xù)加入新游客找到了這樣一個(gè)確定的反例New element whose PFADD returned 0: visitor:10003visitor:10003在精確 Set 中是新成員SADD返回 1但它的PFADD返回 0。所以請(qǐng)記住PFADD的返回值用于判斷 HLL 內(nèi)部狀態(tài)是否改變不能用于判斷某個(gè)用戶是否首次出現(xiàn)。PFADD對(duì)每個(gè)元素的處理為 O(1)。一次命令加入 N 個(gè)元素總工作量隨 N 增長。三、PFCOUNT大屏顯示的是估算值查看一個(gè) HyperLogLog 的基數(shù)使用PFCOUNTPFCOUNT lab:hll:{park}:uv假設(shè) Set 精確保存了 10000 名游客Redis 8.6.1 在本文數(shù)據(jù)集上的結(jié)果如下Exact Set cardinality: 10000 HyperLogLog estimate: 9951 Observed absolute error: 0.4900%少了 49 人這不是 Redis 丟數(shù)據(jù)而是概率型數(shù)據(jù)結(jié)構(gòu)的正常行為。Redis 官方給出的 HyperLogLog 標(biāo)準(zhǔn)誤差約為0.81%。這里的“0.81%”不是承諾每一次結(jié)果都精確落在固定范圍內(nèi)也不是說每 100 人固定少算或多算 0.81 人。它描述的是算法誤差的統(tǒng)計(jì)特征。如果業(yè)務(wù)說“報(bào)表允許千分之幾到百分之一左右的估算誤差”HyperLogLog 很合適如果財(cái)務(wù)說“少一個(gè)人也不行”請(qǐng)使用精確結(jié)構(gòu)或數(shù)據(jù)庫統(tǒng)計(jì)。PFCOUNT還能直接接收多個(gè) Key返回它們并集的估算基數(shù)PFCOUNT\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24它不會(huì)把兩個(gè) Key 的估算值直接相加因?yàn)樽蛱旌徒裉炜赡苡型慌慰?。Redis 會(huì)合并寄存器特征后再估算并集因此能夠處理跨天重復(fù)。四、它為什么只用大約 12 KB如果用 Set 統(tǒng)計(jì)游客Redis 必須保存每個(gè)游客編號(hào)。人數(shù)從 1 萬增長到 1 億內(nèi)存也會(huì)跟著增長。HyperLogLog 不保存游客編號(hào)。它對(duì)每個(gè)元素計(jì)算 64 位哈希然后完成兩件事從哈希中取 14 位選擇 16384 個(gè)寄存器中的一個(gè)在剩余哈希片段中統(tǒng)計(jì)連續(xù)零的長度只保留該寄存器見過的最大值。每個(gè)寄存器只需 6 bit16384 × 6 bit 98304 bit 12288 byte ≈ 12 KB再加上 16 字節(jié)頭部和 Redis 對(duì)象開銷本文的MEMORY USAGE實(shí)測(cè)為HyperLogLog memory usage: 12848 bytes Set memory usage: 575606 bytes在這組 1 萬名游客的實(shí)驗(yàn)中Set 約占 575 KBHLL 約占 12.8 KB。數(shù)據(jù)繼續(xù)增加時(shí)Set 還會(huì)增長而稠密 HLL 的主體仍保持在 12 KB 左右。這就是它最大的價(jià)值用固定量級(jí)的空間換取可控的統(tǒng)計(jì)誤差。五、哈希與連續(xù)零為什么能猜出人數(shù)想象檢票員拋硬幣第一次就是正面概率是 1/2連續(xù)兩個(gè)反面后才出現(xiàn)正面概率大約是 1/4連續(xù)十個(gè)反面后才出現(xiàn)正面概率大約是 1/1024。連續(xù)出現(xiàn)很多個(gè)零是一件比較罕見的事。如果觀察樣本中出現(xiàn)了“連續(xù) 14 個(gè)零”這樣的哈希特征通常意味著檢票員已經(jīng)看過相當(dāng)多的不同游客。但只用一個(gè)最大連續(xù)零值運(yùn)氣影響會(huì)很大第一位游客可能碰巧就很特殊。因此 HyperLogLog 把游客分散到 16384 個(gè)寄存器讓每個(gè)寄存器獨(dú)立記錄最大值最后通過調(diào)和平均和偏差修正得到整體估算。圖 3哈希的一部分選擇寄存器另一部分提供連續(xù)零特征寄存器只保留歷史最大值。資料經(jīng)常稱它為“統(tǒng)計(jì)前導(dǎo)零”。Redis 8.6 源碼具體從剩余哈希片段的一端連續(xù)數(shù)零。哈希值可以視為均勻隨機(jī)位串因此從哪一端計(jì)數(shù)不影響這里的概率直覺。這里還有一個(gè)很重要的工程結(jié)論兩個(gè)相同元素會(huì)產(chǎn)生相同哈希落到相同寄存器并提供相同連續(xù)零長度所以重復(fù)上報(bào)不會(huì)持續(xù)增加估算值。六、稀疏與稠密小客流不必立刻鋪滿大屏16384 個(gè)寄存器全部按 6 bit 展開需要約 12 KB。這已經(jīng)很小但如果一個(gè) Key 只看過三五名游客立刻分配完整面板仍有些浪費(fèi)。Redis 因此提供兩種內(nèi)部表示sparse稀疏大量寄存器還是 0 時(shí)用游程編碼壓縮連續(xù)的零dense稠密數(shù)據(jù)增多后把 16384 個(gè) 6 bit 寄存器緊密排列??梢园阉氤捎螛穲@剛開門時(shí)檢票員只在紙上記“前 5000 個(gè)格子都是 0”客流大了以后再展開完整電子面板直接讀寫每個(gè)格子。本文隔離實(shí)驗(yàn)使用內(nèi)部調(diào)試命令觀察到PFDEBUG small encoding (isolated lab only): sparse PFDEBUG large encoding (isolated lab only): densePFDEBUG屬于管理員內(nèi)部調(diào)試命令官方標(biāo)記為 dangerous。它只適合隔離實(shí)驗(yàn)生產(chǎn)環(huán)境不要為了好奇去執(zhí)行。七、為什么 TYPE 是 stringHyperLogLog 在概念上是一種獨(dú)立數(shù)據(jù)結(jié)構(gòu)但 Redis 并沒有為它增加新的頂層對(duì)象類型而是把二進(jìn)制內(nèi)容編碼在 String 中。TYPE lab:hll:{park}:uv OBJECT ENCODING lab:hll:{park}:uv本文實(shí)測(cè)TYPE: string OBJECT ENCODING: raw這里的raw與前面的 sparse/dense 并不矛盾OBJECT ENCODING看到的是 Redis String 的外層編碼sparse/dense 描述的是 String 字節(jié)內(nèi)容里的 HLL 內(nèi)部格式。技術(shù)上官方文檔提到 HLL 可以通過GET取出二進(jìn)制值再通過SET恢復(fù)。但普通業(yè)務(wù)代碼不應(yīng)隨意APPEND、SETRANGE或覆蓋它否則可能制造一個(gè)格式損壞的 HLL。最安全的原則是同一個(gè) Key 一旦作為 HyperLogLog 使用就只讓 PF 系列命令管理它。命令為什么都以PF開頭這是為了紀(jì)念 HyperLogLog 論文作者之一 Philippe Flajolet。PFADD不是 “Probability Filter Add”而是一枚藏在命令名里的致敬彩蛋。八、PFMERGE合并多天與多個(gè)園區(qū)游樂園每天維護(hù)一個(gè) HLLlab:hll:{park}:2026-08-23 lab:hll:{park}:2026-08-24 lab:hll:{park}:2026-08-25如果只是臨時(shí)查看多天并集可以直接PFCOUNT\lab:hll:{park}:2026-08-23\lab:hll:{park}:2026-08-24如果要生成可復(fù)用的周報(bào) 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本文實(shí)驗(yàn)中8 月 23 日加入游客 160008 月 24 日加入游客 400110000兩天存在 2000 名重復(fù)游客。結(jié)果如下Day 1 estimate: 6007 Day 2 estimate: 5957 Multi-key PFCOUNT union: 9951 PFMERGE result: OK Merged weekly estimate: 9951絕不能把 6007 和 5957 直接相加。兩天合計(jì)不是 11964 個(gè)不同游客HLL 的并集估算為 9951接近真實(shí)的 10000。圖 4每日客流中有重復(fù)游客PFMERGE合并的是統(tǒng)計(jì)特征而不是把每日數(shù)字簡單相加。九、Set 與 HyperLogLog 怎么選游樂園有兩個(gè)房間檔案室 Set保存每一張游客卡可以查“誰來過”客流估算器 HLL只有一塊小面板只能給出“約來了多少人”。圖 5Set 用空間換精確和明細(xì)HyperLogLog 用少量誤差換固定量級(jí)內(nèi)存。問題選擇今天大約有多少獨(dú)立訪客HyperLogLog用戶 10086 今天是否訪問過Set、Bitmap 或數(shù)據(jù)庫列出今天所有訪客Set、數(shù)據(jù)庫或日志系統(tǒng)精確統(tǒng)計(jì)付費(fèi)用戶數(shù)量精確結(jié)構(gòu)統(tǒng)計(jì)上億設(shè)備的大致去重?cái)?shù)HyperLogLog同時(shí)需要近似總量與具體名單HLL 明細(xì)系統(tǒng)各司其職如果 ID 連續(xù)、需要判斷某個(gè)用戶是否出現(xiàn)可以看看已有的 Redis Bitmap 文章。Bitmap 也省內(nèi)存但它的空間受最大 offset 影響HyperLogLog 不保存成員狀態(tài)只估算數(shù)量。十、按天分桶、TTL 與歸檔不要把所有歷史 UV 永遠(yuǎn)塞進(jìn)一個(gè) Key否則你只能得到“開站以來總?cè)藬?shù)”很難回答今天、昨天和本周的問題。常見設(shè)計(jì)是按時(shí)間分桶uv:{park}:2026-08-24 uv:{park}:2026-08-25 uv:{park}:2026-W35 uv:{park}:2026-08每日 Key 接收實(shí)時(shí)PFADD周/月 Key通過PFMERGE生成。原始日 Key 保留一段時(shí)間后過期EXPIRE lab:hll:{park}:2026-08-24604800HyperLogLog 沒有成員級(jí) TTL。TTL 作用于整個(gè) Key時(shí)間到后整張“當(dāng)天客流統(tǒng)計(jì)板”被刪除不是單獨(dú)忘掉某個(gè)游客。如果報(bào)表需要長期保存可以定時(shí)把最終估算值寫入數(shù)據(jù)庫或數(shù)據(jù)倉庫。Redis HLL 更適合實(shí)時(shí)和近實(shí)時(shí)統(tǒng)計(jì)不應(yīng)被誤認(rèn)為完整審計(jì)日志。十一、并發(fā)、持久化與 Cluster1. 并發(fā)寫入單條PFADD命令在 Redis 中原子執(zhí)行多個(gè)應(yīng)用實(shí)例可以并發(fā)向同一個(gè) HLL 上報(bào)。需要注意的是Hot Key 仍可能把流量集中到單個(gè) Redis 節(jié)點(diǎn)高峰場(chǎng)景可先按城市、業(yè)務(wù)線或時(shí)間窗口拆分再合并統(tǒng)計(jì)。2. 持久化與復(fù)制HLL 本質(zhì)上是 Redis 數(shù)據(jù)RDB、AOF、主從復(fù)制都會(huì)像處理其他 Key 一樣處理它。是否能接受持久化間隔中的數(shù)據(jù)丟失取決于業(yè)務(wù)要求重要 UV 通常還會(huì)保留原始事件日志便于重新計(jì)算。3. Cluster 同槽PFMERGE和多 KeyPFCOUNT涉及多個(gè) Key。在 Redis Cluster 中相關(guān) Key 必須位于同一個(gè) Hash Slot因此示例統(tǒng)一使用{park}lab:hll:{park}:2026-08-23 lab:hll:{park}:2026-08-24 lab:hll:{park}:week-34大括號(hào)內(nèi)的{park}是 Hash Tag保證這些 Key 使用同一段內(nèi)容計(jì)算槽位。不要把所有城市都強(qiáng)塞進(jìn)同一個(gè){park}否則會(huì)把全局流量集中到一個(gè)分片。更合理的設(shè)計(jì)是{beijing}、{shanghai}分城市統(tǒng)計(jì)需要全局報(bào)表時(shí)在應(yīng)用層或離線系統(tǒng)匯總。十二、生產(chǎn)環(huán)境常見誤區(qū)1. 把 PFADD 返回值當(dāng)“是否新用戶”新元素也可能返回 0。HLL 不支持成員存在性判斷。2. 認(rèn)為 0.81% 是每次查詢的硬性誤差上限它是標(biāo)準(zhǔn)誤差不是逐次保證。關(guān)鍵業(yè)務(wù)必須用自己的數(shù)據(jù)規(guī)模和分布驗(yàn)證。3. 需要精確計(jì)費(fèi)卻為了省內(nèi)存選擇 HLL廣告結(jié)算、抽獎(jiǎng)、庫存和資金不能用近似值。4. 以為 HLL 能返回游客名單它早已丟掉了原始元素只保留寄存器狀態(tài)無法枚舉或反查成員。5. 把每日估算值直接相加跨天有重復(fù)用戶應(yīng)使用多 KeyPFCOUNT或PFMERGE計(jì)算并集。6. 忘記給時(shí)間分桶設(shè)置 TTL單個(gè)稠密 HLL 只有約 12 KB但每天、每城市、每頁面創(chuàng)建大量 Key 后總量仍然需要治理。7. 用普通 String 命令修改 HLL 內(nèi)容外層類型雖然是 String內(nèi)容卻有嚴(yán)格格式。業(yè)務(wù)寫入只使用 PF 系列命令。8. 在生產(chǎn)環(huán)境執(zhí)行 PFDEBUG它是內(nèi)部管理員調(diào)試命令官方標(biāo)記為 dangerous。觀察編碼應(yīng)在隔離實(shí)驗(yàn)中完成。圖 6先問業(yè)務(wù)能否接受近似再檢查是否需要成員明細(xì)、時(shí)間分桶和 Cluster 同槽。十三、完整 redis-cli 實(shí)驗(yàn)下面給出一組核心命令。請(qǐng)?jiān)跍y(cè)試實(shí)例執(zhí)行不要在生產(chǎn)環(huán)境隨意FLUSHDB或使用PFDEBUG。# 1. 基礎(chǔ)加入與重復(fù)加入PFADD lab:hll:{park}:small visitor:42 PFADD lab:hll:{park}:small visitor:42 PFCOUNT lab:hll:{park}:small# 2. 查看外層類型與內(nèi)存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. 生成周統(tǒng)計(jì)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 設(shè)置 7 天 TTLEXPIRE lab:hll:{park}:2026-08-23604800TTL lab:hll:{park}:2026-08-23本文完整自動(dòng)化實(shí)驗(yàn)得到的關(guān)鍵輸出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這些數(shù)字是 Redis 8.6.1 在本文固定數(shù)據(jù)集上的一次實(shí)測(cè)用于理解相對(duì)差異換版本、元素或哈希分布后估算值可能不同。十四、快速判斷口訣最后用一段游樂園口訣收尾游客次數(shù)看 PV不同游客才是 UVSet 記住每張票HLL 只看特殊號(hào)PFADD 零或一不是新老身份證十六K格十二KB少量誤差換空間多天人數(shù)別相加PFMERGE 來做并集要名單就別選它要計(jì)費(fèi)更不能差Cluster 多 Key 同槽時(shí)間分桶記得清。HyperLogLog 的價(jià)值不在于“神奇地算得完全正確”而在于它誠實(shí)地接受一點(diǎn)誤差把原本隨人數(shù)膨脹的去重問題壓縮到固定量級(jí)的內(nèi)存中。當(dāng)你只需要知道“今天大約來了多少個(gè)不同的人”它就像游樂園門口那塊小巧而高效的客流估算器當(dāng)你需要知道“具體是誰、是否來過、該收多少錢”請(qǐng)把問題交給 Set、Bitmap、數(shù)據(jù)庫或明細(xì)日志。參考資料Redis HyperLogLog 官方文檔PFADD 命令PFCOUNT 命令PFMERGE 命令PFDEBUG 命令Redis 8.6 hyperloglog.c 源碼Redis Cluster 規(guī)范個(gè)人小游戲