背景
在互聯網公司中,每個項目都需要數據統計、分析,便於項目組利用詳細數據研究項目的整體情況,進行下一步的調整。在數據統計中,UV統計是最常見的,也是最普遍的。有的場景要求實時性很高,有點場景要求准確性很高,有的場景比較在意計算過程中的內存。不同的場景使用不同的算法,下面我們從0到1簡單介紹下UV統計領域。
什么是UV統計
假設我們的場景是商家這邊上架一系列水果,然后需要統計出一共上架幾種水果。具體如下所示:

針對這個問題,我們想到的最簡單的方式就是利用STL中的set處理。
SET
上架一個水果的時候,也同時在set中插入。最后需要統計的時候,直接計算set中一共有幾個水果即可。具體如下所示:

這種方式准確率是絕對准確的,但是這種方式耗費的內存是很大的。
假設每個水果需要 K 字節,那么如果有 M 個水果,一共需要 K * M 字節。那么我們能不能縮小這里的內存呢?
稍微損失一點准確率換取內存?具體見下面HashMap的方式
HASHMAP
這種算法在上架一個水果的時候,只需要在特定的位置置1即可,而不需要存儲這個位置上究竟是何種水果。然后在統計的時候,只需要統計hashmap里面有多少個1即可。具體如下所示:
具體如下所示:

那么如果有M個水果,這里其實只需要 M / 8 字節,相比set的方式內存直接縮小到1/8。當然Hash肯定會有沖突的,所以這里肯定有一定准確率的損失。
但是如果涉及到海量數據的UV統計,這里的內存還是很大的。
能否用上統計學進一步縮小內存呢?具體見下面的Linear Count的方式。
Linear Count
這種算法在上架一個水果的時候,完全跟hashmap一致,在相應位置置1。
然后在統計的時候,利用統計學的方式,根據hashmap中零的個數給出一個估算值。具體如下所示:

假設M為哈希桶長度,那么每次上架水果,每個桶被選中的概率為:
然后在上架N個元素后,某個桶為0的概率為:
所以在上架n個元素后,哈希桶中零的個數期望為:
所以最終:
所以Linear Count算法中,只需統計下hashmap中零的個數,然后代入上式即可。
這種算法在N很小的時候,准確率是很高的,但是N很大的時候,它的准確率急劇下降。
針對海量數據的情況,LogLog Count的算法更加魯棒
LogLog Count
這種算法跟上面幾種都不同,上架水果的時候,在相應桶里面記錄的是二進制數后面最長的連續零個數。然后統計的時候,利用統計學的方式,根據存儲中最長連續后綴零個數,得出一個估計值。具體如下所示:
它的原理如下:
這里如果只使用一個桶來估計的話,它的誤差是很大,需要用分桶平均的方式來減少它的誤差。
分桶平均
既然這里利用了分桶來減少誤差,那么這里統計的時候就必須合起來,這里有4種方式:
- 算術平均:$$UV=\frac{\sum_{j=1}^mUV_j} { m}$$
- 幾何平均:$$UV=\sqrt[m]{UV_1...UVm}$$
- 調和平均:$$UV=\frac{m}{\sum_{j=1}mUV_j{-1}}$$
- 中位數:$$UV=mediam {UV_1,...,UV_m}$$
LogLog Count利用的是算術平均的方式,所以最終估計值為:
這種算法對於基數大的情況下准確率挺高的,但是基數小的情況下准確率很低。
HyperLogLog Count
這種算法跟LogLog Count 類似,有個區別點就是它在求均值的時候利用了調和平均數,而不是算術平均數。這里最終估計值為:
然后它還引入了分段誤差修正。
誤差修正
具體可以看我github上的代碼:HyperLogLog
總結
| 准確率 | 內存 | 耗時 | |
|---|---|---|---|
| Set | 絕對准確 | K * M | O(Mlog(M)) |
| HashMap | 很高 | M/8 | O(M) |
| Linear Count | 基數小高,基數大低 | M/8 | O(M/8) |
| LogLog Count | 基數小低,基數大高 | ||
| HyperLogLog Count | 高 |
