一、簡介
我們先思考一個(gè)常見的業(yè)務(wù)問題:如果你負(fù)責(zé)開發(fā)維護(hù)一個(gè)大型的網(wǎng)站,有一天老板找產(chǎn)品經(jīng)理要某個(gè)網(wǎng)站每個(gè)網(wǎng)頁每天的 UV(訪客量) 數(shù)據(jù),然后讓你來開發(fā)這個(gè)統(tǒng)計(jì)模塊,你會如何實(shí)現(xiàn)?
如果統(tǒng)計(jì) PV(瀏覽量)那非常好辦,給每個(gè)網(wǎng)頁一個(gè)獨(dú)立的 redis 計(jì)數(shù)器就可以了,這個(gè)計(jì)數(shù)器的 key 后綴加上當(dāng)天的日期。這樣來一個(gè)請求,incrby 一次,最終就可以統(tǒng)計(jì)出所有的 PV 數(shù)據(jù)。
但是 UV 不一樣,它要去重,同一個(gè)用戶一天之內(nèi)的多次訪問請求只能計(jì)數(shù)一次。這就要求每一個(gè)網(wǎng)頁請求都需要帶上用戶的 ID,無論是登錄用戶還是未登錄用戶都需要一個(gè)唯一 ID 來標(biāo)識。
解決方案:Redis 提供了 HyperLogLog 數(shù)據(jù)結(jié)構(gòu)就是用來解決這種統(tǒng)計(jì)問題的。HyperLogLog 提供不精確的去重計(jì)數(shù)方案,雖然不精確但是也不是非常不精確,標(biāo)準(zhǔn)誤差是 0.81%,這樣的精確度已經(jīng)可以滿足上面的 UV 統(tǒng)計(jì)需求了。
HyperLogLog 數(shù)據(jù)結(jié)構(gòu)是 Redis 的高級數(shù)據(jù)結(jié)構(gòu),它非常有用,但是令人感到意外的是,使用過它的人非常少。
二、HyperLogLog用法
具體代碼:
edis6.3:0>pfadd pf1 p1 p2 p3 p4
"1"
redis6.3:0>pfcount pf1
"4"
redis6.3:0>pfadd pf1 p3 p5 p6
"1"
redis6.3:0>pfcount pf1
pfmerge的用法:
HyperLogLog 除了上面的 pfadd 和 pfcount 之外,還提供了第三個(gè)指令 pfmerge,用于將多個(gè) pf 計(jì)數(shù)值累加在一起形成一個(gè)新的 pf 值。比如:在網(wǎng)站中我們有兩個(gè)內(nèi)容差不多的頁面,運(yùn)營說需要這兩個(gè)頁面的數(shù)據(jù)進(jìn)行合并。其中頁面的 UV 訪問量也需要合并,那這個(gè)時(shí)候 pfmerge 就可以派上用場了。
27.0.0.1:6379> pfmerge user2 user //將user中的數(shù)據(jù)合并到user2中
OK
127.0.0.1:6379> pfcount user
(integer) 2
三、實(shí)現(xiàn)案例
核心代碼:
public Result testHyperLogLog(){
String[] values = new String[1000];
int j = 0;
for (int i = 0; i < 1000000; i++) {
j = i % 1000;
values[j] = "user_" + i;
if(j == 999){
// 發(fā)送到Redis
stringRedisTemplate.opsForHyperLogLog().add("hll", values);
}
}
// 統(tǒng)計(jì)數(shù)量
Long count = stringRedisTemplate.opsForHyperLogLog().size("hll");
log.info("統(tǒng)計(jì)的用戶數(shù)量count:"+count);
return Result.ok();
}
結(jié)果展示:
100萬條數(shù)據(jù),統(tǒng)計(jì)出來有997461,誤差是0.253%,小于標(biāo)準(zhǔn)誤差是 0.81%,對于UV 統(tǒng)計(jì)需求來說,誤差率也不算高,然后我們把上面的腳本再跑一遍,也就相當(dāng)于將數(shù)據(jù)重復(fù)加入一遍,查看輸出,可以發(fā)現(xiàn),pfcount 的結(jié)果沒有任何改變,還是 997461,說明它確實(shí)具備去重功能。
四、注意事項(xiàng)
HyperLogLog 這個(gè)數(shù)據(jù)結(jié)構(gòu)不是免費(fèi)的,不是說使用這個(gè)數(shù)據(jù)結(jié)構(gòu)要花錢,它需要占據(jù)一定 12k 的存儲空間,所以它不適合統(tǒng)計(jì)單個(gè)用戶相關(guān)的數(shù)據(jù)。如果你的用戶上億,可以算算,這個(gè)空間成本是非常驚人的。但是相比 set 存儲方案,HyperLogLog 所使用的空間那真是可以使用千斤對比四兩來形容了。不過你也不必過于擔(dān)心,因?yàn)?Redis 對 HyperLogLog 的存儲進(jìn)行了優(yōu)化,在計(jì)數(shù)比較小時(shí),它的存儲空間采用稀疏矩陣存儲,空間占用很小,僅僅在計(jì)數(shù)慢慢變大,稀疏矩陣占用空間漸漸超過了閾值時(shí)才會一次性轉(zhuǎn)變成稠密矩陣,才會占用 12k 的空間。
五、源碼獲取方式
更多優(yōu)秀文章,請關(guān)注個(gè)人微信公眾號或搜索“程序猿小楊”查閱。然后回復(fù):源碼,可以獲取該項(xiàng)目對應(yīng)的源碼及表結(jié)構(gòu),開箱即可使用。
說明:后面redis相關(guān)操作的功能都會放在此文件夾中,需要相關(guān)功能的,只需要獲取最新的資源,替換項(xiàng)目即可。