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