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