HyperLogLog

我們可以思考一個常見的業務問題:如果開發一個大型網站,要記錄每個網頁每天的UV數據,我們應該如何實現呢?

如果統計PV那非常容易,給每個網頁一個獨立的Redis計數器就可以了,這個計數器的key後綴加上當天的日期。這樣來一個請求,incrby一次,最終就可以統計出所有的PV數據。但是UV不一樣,它要去重,也許我們可以想到一個簡單的方案,爲每個網頁創建一個set集合來存儲當天訪問過此頁面的用戶ID。但是如果我們的頁面非常巨大,比如一個爆款頁面幾千萬的UV,需要一個很大的set集合來統計,這樣就非常浪費空間了。

Redis提供了HyperLogLog數據結構用來解決這種統計問題。HyperLogLog提供了不準確的去重計數功能,雖然不準確但也不是非常不準確,標準誤差爲0.81%,這樣的精確度已經可以滿足UV統計需求了。

1.使用方法

HyperLogLog提供了兩個指令pfadd(增加計數)和pfcount(獲取計數)。pfadd用法和set集合的sadd是一樣的,pfcount則類似於scard。實例代碼如下:

MyRedis:0>pfadd uv user1
1
MyRedis:0>pfadd uv user2
1
MyRedis:0>pfadd uv user3
1
MyRedis:0>pfcount uv
3
MyRedis:0>pfadd uv user3
0
MyRedis:0>pfcount uv
3
MyRedis:0>pfadd uv user4 user5 user5
1
MyRedis:0>pfcount uv
5

目前爲止,UV統計還是非常準確的。下面我們使用腳本,插入更多的數據,看看能否繼續準確:

import my_tools

client = my_tools.get_redis()
for i in range(1000):
    client.pfadd('uv2', 'user%d' % i)
    total = client.pfcount('uv2')
    if total != i + 1:
        print(total, i + 1)
        break

輸出結果爲:
99 100

從輸出結果可以看出,當我們輸入第100個元素的時候,結果出現了不一致。繼續增加數據到10W,看一下差距有多大:

import my_tools

client = my_tools.get_redis()
for i in range(20000):
    client.pfadd('uv4', 'user%d' % i)

total = client.pfcount('uv4')
print(total, 20000)


輸出結果爲:
20082 20000

HyperLogLog除了pfadd和pfcount之外,還提供了pfmerge指令,用於將多個pf的計數值累加在一起形成一個新的pf值。

2.注意事項

HyperLogLog在計數比較小時,它的存儲空間是採用稀疏矩陣空間佔用較小,當計數慢慢變大的時候,稀疏矩陣佔用的空間逐漸超過了閾值時會一次性轉變爲稠密矩陣,佔用空間大概是12K。由於HyperLogLog佔用的空間相對比較大,所以不適合統計單個用戶相關的數據。

3.實現原理

實現原理參考代碼:https://blog.51cto.com/13732225/2167661

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章