Redis統(tǒng)計(jì)獨(dú)立用戶訪問(wèn)量的四種方案
在網(wǎng)站分析、廣告監(jiān)測(cè)、推薦系統(tǒng)等場(chǎng)景中,獨(dú)立用戶訪問(wèn)量(UV,Unique Visitor) 是一個(gè)核心指標(biāo)。UV 的關(guān)鍵在于去重——同一個(gè)用戶多次訪問(wèn)只計(jì)一次。
Redis 提供了多種數(shù)據(jù)結(jié)構(gòu)來(lái)高效實(shí)現(xiàn) UV 統(tǒng)計(jì),各有優(yōu)劣。本文將詳細(xì)對(duì)比 Set、Bitmap、HyperLogLog、incr + 日期維度(即用戶提到的兩種方式)四種方案,并通過(guò)流程圖和代碼示例幫助你選型。
一、方案概覽(附選型流程圖)

二、方案一:Set 集合(精確去重)
最直觀的方法:每個(gè)統(tǒng)計(jì)周期(如一天)維護(hù)一個(gè) Set,將每個(gè)訪問(wèn)過(guò)的用戶 ID 加入 Set,最后用 SCARD 獲取基數(shù)。
# 示例:用戶 1001 訪問(wèn)首頁(yè)
redis.sadd("uv:home:2025-04-15", "user_1001")
# 獲取當(dāng)天 UV
uv = redis.scard("uv:home:2025-04-15")優(yōu)點(diǎn):精確、支持用戶 ID 任意類型(字符串/整數(shù))。
缺點(diǎn):內(nèi)存占用高,每個(gè)用戶 ID 都需要存儲(chǔ)一份(例如 1000 萬(wàn)用戶,每個(gè) ID 按 30 字節(jié)算,需約 300MB)。
適用:用戶量?。?lt; 百萬(wàn)級(jí))或必須精確統(tǒng)計(jì)的場(chǎng)景。
三、方案二:Bitmap(位圖法,精確且內(nèi)存極?。?/h2>
當(dāng)用戶 ID 是整數(shù)且相對(duì)連續(xù)(如自增 user_id)時(shí),可以用 Bitmap 將每個(gè) user_id 映射到位偏移量,存在則置 1。
# 用戶 ID=1001 訪問(wèn),設(shè)置第 1001 位為 1
redis.setbit("uv:home:2025-04-15", 1001, 1)
# 統(tǒng)計(jì)當(dāng)日 UV(統(tǒng)計(jì) 1 的個(gè)數(shù))
uv = redis.bitcount("uv:home:2025-04-15")
內(nèi)存計(jì)算:如果有 1 億用戶,只需 1億 bit ≈ 12 MB,比 Set 節(jié)省數(shù)十倍。
優(yōu)點(diǎn):精確、內(nèi)存極小、性能高(bitcount 時(shí)間復(fù)雜度 O(n) 但 Redis 做了優(yōu)化)。
缺點(diǎn):用戶 ID 必須為整數(shù)且不太稀疏(若 ID 最大為 10 億,但實(shí)際只有 100 萬(wàn)用戶,依然會(huì)占用 125MB 的連續(xù)空間,造成浪費(fèi))。
適用:用戶 ID 是自增整數(shù)、最大 ID 可控(如 2^32 以內(nèi))、對(duì)內(nèi)存敏感且要求精確的場(chǎng)景。
四、方案三:HyperLogLog(近似去重,誤差 0.81%)
你提到的 HyperLogLog 是一種概率性數(shù)據(jù)結(jié)構(gòu),用 12KB 固定內(nèi)存即可統(tǒng)計(jì)上億級(jí)別的 UV,誤差率約為 0.81%。
# 添加元素
redis.pfadd("uv:home:2025-04-15", "user_1001", "user_1002")
# 獲取近似 UV
uv = redis.pfcount("uv:home:2025-04-15")
原理:通過(guò)哈希函數(shù)將元素映射為二進(jìn)制串,觀察低位連續(xù)零的個(gè)數(shù)來(lái)估計(jì)基數(shù)。
優(yōu)點(diǎn):內(nèi)存固定(12KB),性能極高(O(1) 添加),適合海量數(shù)據(jù)。
缺點(diǎn):不精確(誤差 ±0.81%),無(wú)法取出具體有哪些用戶(只能計(jì)數(shù)),不適合敏感計(jì)費(fèi)場(chǎng)景。
適用:大屏展示、趨勢(shì)分析、非精準(zhǔn)營(yíng)銷統(tǒng)計(jì)等可容忍誤差的場(chǎng)景。
五、方案四:incr + 日期維度(你提到的“incr自增”)
嚴(yán)格來(lái)說(shuō),單純使用 INCR 無(wú)法實(shí)現(xiàn)獨(dú)立用戶去重,因?yàn)?INCR 是累加計(jì)數(shù)器,每次訪問(wèn)都 +1,得到的是 PV(頁(yè)面訪問(wèn)量),不是 UV。
# 這樣得到的是 PV,不是 UV
redis.incr("pv:home:2025-04-15")
如何用 incr 輔助 UV?
通常做法是 incr + Set/Bitmap/HLL 組合:
- 用 Set 或 HLL 存儲(chǔ)獨(dú)立用戶(保證去重)
- 同時(shí)用 incr 記錄總訪問(wèn)次數(shù)(PV)
# 記錄 PV
redis.incr("pv:home:2025-04-15")
# 記錄 UV(使用 HLL)
redis.pfadd("uv:home:2025-04-15", user_id)
所以,你提到的“incr 通過(guò)自增方式判斷用戶的訪問(wèn)量”并不適用于 UV,應(yīng)理解為 PV 統(tǒng)計(jì)。但為了貼合你的原文,我們修正說(shuō)明:incr 適合 PV,UV 必須依賴去重結(jié)構(gòu)。
六、四種方案對(duì)比表
| 方案 | 內(nèi)存占用 | 精確性 | 支持用戶ID類型 | 時(shí)間復(fù)雜度(寫入) | 典型應(yīng)用 |
|---|---|---|---|---|---|
| Set | O(N)(每個(gè)元素完整存儲(chǔ)) | 精確 | 任意 | O(1) | 小規(guī)模精確統(tǒng)計(jì) |
| Bitmap | O(max_id) 位,連續(xù)整數(shù)時(shí)極省 | 精確 | 非負(fù)整數(shù) | O(1) | 億級(jí)整數(shù)ID,如手機(jī)號(hào)后幾位 |
| HyperLogLog | 固定 12KB | 近似(誤差 0.81%) | 任意(需哈希) | O(1) | 海量UV快速估算 |
| incr(PV) | 固定(每個(gè)key一個(gè)整數(shù)) | 精確 | 無(wú)(只是計(jì)數(shù)) | O(1) | 頁(yè)面訪問(wèn)總量(非UV) |
七、實(shí)戰(zhàn)選型建議
你的用戶 ID 是整數(shù)且密集(如 user_id 從 1 到 5000 萬(wàn))
?? 首選 Bitmap,精確且內(nèi)存最小。
用戶 ID 是字符串(如 UUID、手機(jī)號(hào)),且允許 0.81% 誤差
?? 首選 HyperLogLog,12KB 內(nèi)存統(tǒng)計(jì)上億 UV。
必須精確統(tǒng)計(jì),且用戶量較?。?lt; 500 萬(wàn))
?? 用 Set,簡(jiǎn)單可靠。
既要 PV 又要 UV
?? 組合:INCR 記錄 PV + PFADD 記錄 UV(HLL)或 SADD(Set)。
數(shù)據(jù)敏感場(chǎng)景(如計(jì)費(fèi)、反 作弊)
? 不能用 HyperLogLog,必須用 Bitmap 或 Set。
八、代碼示例:三種方案對(duì)比(Python + Redis)
import redis
r = redis.Redis(decode_responses=True)
# 模擬 100 萬(wàn)個(gè)用戶 ID(字符串)
user_ids = [f"user_{i}" for i in range(1_000_000)]
# 1. Set 方式
key_set = "uv:set"
r.delete(key_set)
for uid in user_ids:
r.sadd(key_set, uid)
print(f"Set 精確 UV: {r.scard(key_set)}")
print(f"Set 內(nèi)存: {r.memory_usage(key_set) / 1024 / 1024:.2f} MB")
# 2. HyperLogLog 方式
key_hll = "uv:hll"
r.delete(key_hll)
for uid in user_ids:
r.pfadd(key_hll, uid)
print(f"HLL 近似 UV: {r.pfcount(key_hll)}")
print(f"HLL 內(nèi)存: {r.memory_usage(key_hll)} 字節(jié)") # 固定約 12KB
# 3. Bitmap 方式(假設(shè) user_id 轉(zhuǎn)為整數(shù),此處用 i 模擬)
key_bit = "uv:bitmap"
r.delete(key_bit)
for i in range(1, 1_000_001):
r.setbit(key_bit, i, 1)
print(f"Bitmap 精確 UV: {r.bitcount(key_bit)}")
print(f"Bitmap 內(nèi)存: {r.memory_usage(key_bit) / 1024 / 1024:.2f} MB")
運(yùn)行結(jié)果參考(百萬(wàn)級(jí)):
- Set:內(nèi)存約 30~40 MB
- HLL:12 KB
- Bitmap:0.12 MB(100 萬(wàn) bit = 0.125 MB)
九、總結(jié)
| 你的原始說(shuō)法 | 修正/補(bǔ)充 |
|---|---|
| “incr 通過(guò)自增方式判斷用戶的訪問(wèn)量” | incr 得到的是 PV(總訪問(wèn)次數(shù)),不是 UV。UV 需要去重。 |
| “HyperLogLog 用來(lái)做基數(shù)統(tǒng)計(jì),誤差很小,不適合數(shù)據(jù)敏感場(chǎng)景” | ? 正確。誤差約 0.81%,內(nèi)存固定 12KB,適合海量近似統(tǒng)計(jì)。 |
最終結(jié)論:
- 對(duì)精度要求不高、數(shù)據(jù)量極大 → HyperLogLog
- 需要精確、用戶 ID 為整數(shù) → Bitmap
- 需要精確、用戶 ID 為字符串且量小 → Set
- 想要統(tǒng)計(jì) PV → incr
合理選擇數(shù)據(jù)結(jié)構(gòu),能讓你的 UV 統(tǒng)計(jì)既快又省內(nèi)存。
以上就是Redis統(tǒng)計(jì)獨(dú)立用戶訪問(wèn)量的四種方案的詳細(xì)內(nèi)容,更多關(guān)于Redis統(tǒng)計(jì)獨(dú)立用戶訪問(wèn)量的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入剖析 Redis 的三種集群方式以及實(shí)戰(zhàn)配置
本文深入解析Redis三種集群部署方式,文章包含完整的配置示例和操作指南,為Redis集群部署提供實(shí)用參考,感興趣的朋友跟隨小編一起看看吧2026-03-03
Redis如何清理過(guò)期的key以及對(duì)應(yīng)的解決方法分析
這篇文章主要介紹了Redis如何清理過(guò)期的key以及對(duì)應(yīng)的解決方法的相關(guān)資料,Redis提供了多種過(guò)期刪除策略和內(nèi)存淘汰策略,以管理緩存和臨時(shí)數(shù)據(jù),需要的朋友可以參考下2025-03-03
redis數(shù)據(jù)類型_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis數(shù)據(jù)類型,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
redis?setIfAbsent返回null的問(wèn)題及解決
這篇文章主要介紹了redis?setIfAbsent返回null的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
為何Redis使用跳表而非紅黑樹實(shí)現(xiàn)SortedSet
本篇文章主要介紹了為何Redis使用跳表而非紅黑樹實(shí)現(xiàn)SortedSet,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

