redis常用數據類型 HyperLoglog


 

1.HyperLoglog簡介

HyperLoglog是redis新支持的兩種類型中的另外一種(上一種是位圖類型Bitmaps)。主要適用場景是海量數據的計算。特點是速度快。占用空間小。

同樣是用於計算,HyperLoglog在適用場景方面與Bitmaps方面有什么不同呢。我個人的理解是,Bitmaps更適合用於驗證的大數據,比如簽到,

記錄某用戶是不是當天進行了簽到,簽到了多少天的時候。也就是說,你不光需要記錄數據,還需要對數據進行驗證的時候使用Bitmaps。

HyperLoglog則用於只記錄的時候,比如訪問的uv統計。

2.HyperLoglog相關命令

1)

命令:PFADD key element [element ...]

時間復雜度:O(1)

命令描述:將除了第一個參數以外的參數存儲到以第一個參數為變量名的HyperLogLog結構中。這個命令的一個副作用是它可能會更改這個HyperLogLog的內部來反映在每添加一個唯一的對象時估計的基數(集合的基數)。如果一個HyperLogLog的估計的近似基數在執行命令過程中發了變化, PFADD返回1,否則返回0,如果指定的key不存在,這個命令會自動創建一個空的HyperLogLog結構(指定長度和編碼的字符串)。如果在調用該命令時僅提供變量名而不指定元素也是可以的,如果這個變量名存在,則不會有任何操作,如果不存在,則會創建一個數據結構。

返回值:如果 HyperLogLog 的內部被修改了,那么返回 1,否則返回 0 。

 

(2

命令:PFCOUNT key [key ...]

命令描述:當參數為一個key時,返回存儲在HyperLogLog結構體的該變量的近似基數,如果該變量不存在,則返回0。當參數為多個key時,返回這些HyperLogLog並集的近似基數,這個值是將所給定的所有key的HyperLoglog結構合並到一個臨時的HyperLogLog結構中計算而得到的。HyperLogLog可以使用固定且很少的內存(每個HyperLogLog結構需要12K字節再加上key本身的幾個字節)來存儲集合的唯一元素。返回的可見集合基數並不是精確值, 而是一個帶有 0.81% 標准錯誤(standard error)的近似值。

返回值:PFADD添加的唯一元素的近似數量。

 

(3

命令:PFMERGE destkey sourcekey [sourcekey ...]

命令描述:將多個 HyperLogLog 合並(merge)為一個 HyperLogLog , 合並后的 HyperLogLog 的基數接近於所有輸入 HyperLogLog 的可見集合(observed set)的並集。合並得出的 HyperLogLog 會被儲存在目標變量(第一個參數)里面, 如果該鍵並不存在, 那么命令在執行之前, 會先為該鍵創建一個空的。

返回值:這個命令只會返回 OK。

 

Redis HyperLogLog

 

 

Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。

在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。

但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。


什么是基數?

比如數據集 {1, 3, 5, 7, 5, 7, 8}, 那么這個數據集的基數集為 {1, 3, 5 ,7, 8}, 基數(不重復元素)為5。 基數估計就是在誤差可接受的范圍內,快速計算基數。


 

pfadd key element [element...] 將所有元素參數添加到 HyperLogLog 數據結構中,如果至少有個元素被添加返回 1, 否則返回 0

pfcount key [key...] 返回給定 HyperLogLog 的基數估算值。返回給定 HyperLogLog 的基數值,如果多個 HyperLogLog 則返回基數估值之和

pfmerge destkey sourcekey [sourcekey...] 將多個 HyperLogLog 合並為一個 HyperLogLog ,合並后的 HyperLogLog 的基數估算值是通過對所有 給定 HyperLogLog 進行並集計算得出的。

 

 

 

基數的應用實例

下面通過一個實例說明基數在電商數據分析中的應用。

假設一個淘寶網店在其店鋪首頁放置了10個寶貝鏈接,分別從Item01到Item10為這十個鏈接編號。店主希望可以在一天中隨時查看從今天零點開始到目前這十個寶貝鏈接分別被多少個獨立訪客點擊過。所謂獨立訪客(Unique Visitor,簡稱UV)是指有多少個自然人,例如,即使我今天點了五次Item01,我對Item01的UV貢獻也是1,而不是5。

用術語說這實際是一個實時數據流統計分析問題。

要實現這個統計需求。需要做到如下三點:

1、對獨立訪客做標識

2、在訪客點擊鏈接時記錄下鏈接編號及訪客標記

3、對每一個要統計的鏈接維護一個數據結構和一個當前UV值,當某個鏈接發生一次點擊時,能迅速定位此用戶在今天是否已經點過此鏈接,如果沒有則此鏈接的UV增加1

下面分別介紹三個步驟的實現方案

對獨立訪客做標識

客觀來說,目前還沒有能在互聯網上准確對一個自然人進行標識的方法,通常采用的是近似方案。例如通過登錄用戶+cookie跟蹤的方式:當某個用戶已經登錄,則采用會員ID標識;對於未登錄用戶,則采用跟蹤cookie的方式進行標識。為了簡單起見,我們假設完全采用跟蹤cookie的方式對獨立訪客進行標識。

記錄鏈接編號及訪客標記

這一步可以通過JavaScript埋點及記錄accesslog完成,具體原理和實現方案可以參考我之前的一篇文章:網站統計中的數據收集原理及實現

實時UV計算

可以看到,如果將每個鏈接被點擊的日志中訪客標識字段看成一個集合,那么此鏈接當前的UV也就是這個集合的基數,因此UV計算本質上就是一個基數計數問題。

在實時計算流中,我們可以認為任何一次鏈接點擊均觸發如下邏輯(偽代碼描述):

  1. cand_counting(item_no, user_id) {
  2. if (user_id is not in the item_no visitor set) {
  3. add user_id to item_no visitor set;
  4. cand[item_no]++;
  5. }
  6. }

邏輯非常簡單,每當有一個點擊事件發生,就去相應的鏈接被訪集合中尋找此訪客是否已經在里面,如果沒有則將此用戶標識加入集合,並將此鏈接的UV加1。

雖然邏輯非常簡單,但是在實際實現中尤其面臨大數據場景時還是會遇到諸多困難,下面一節我會介紹兩種目前被業界普遍使用的精確算法實現方案,並通過分析說明當數據量增大時它們面臨的問題。

傳統的基數計數實現

接着上面的例子,我們看一下目前常用的基數計數的實現方法。

基於B樹的基數計數

對上面的偽代碼做一個簡單分析,會發現關鍵操作有兩個:查找-迅速定位當前訪客是否已經在集合中,插入-將新的訪客標識插入到訪客集合中。因此,需要為每一個需要統計UV的點(此處就是十個寶貝鏈接)維護一個查找效率較高的數據結構,又因為實時數據流的關系,這個數據結構需要盡量在內存中維護,因此這個數據結構在空間復雜度上也要比較適中。綜合考慮一種傳統的做法是在實時計算引擎采用了B樹來組織這個集合。下圖是一個示意圖:

之所以選用B樹是因為B樹的查找和插入相關高效,同時空間復雜度也可以接受(關於B樹具體的性能分析請參考這里)。

這種實現方案為一個基數計數器維護一棵B樹,由於B樹在查找效率、插入效率和內存使用之間非常平衡,所以算是一種可以接受的解決方案。但是當數據量特別巨大時,例如要同時統計幾萬個鏈接的UV,如果要將幾萬個鏈接一天的訪問記錄全部維護在內存中,這個內存使用量也是相當可觀的(假設每個B樹占用1M內存,10萬個B樹就是100G!)。一種方案是在某個時間點將內存數據結構寫入磁盤(雙十一和雙十二大促時一淘數據部的效果平台是每分鍾將數據寫入HBase)然后將內存中的計數器和數據結構清零,但是B樹並不能高效的進行合並,這就使得內存數據落地成了非常大的難題。

另一個需要數據結構合並的場景是查看並集的基數,例如在上面的例子中,如果我想查看Item1和Item2的總UV,是沒有辦法通過這種B樹的結構快速得到的。當然可以為每一種可能的組合維護一棵B樹。不過通過簡單的分析就可以知道這個方案基本不可行。N個元素集合的非空冪集數量為2N1,因此要為10個鏈接維護1023棵B樹,而隨着鏈接的增加這個數量會以冪指級別增長。

基於bitmap的基數計數

為了克服B樹不能高效合並的問題,一種替代方案是使用bitmap表示集合。也就是使用一個很長的bit數組表示集合,將bit位順序編號,bit為1表示此編號在集合中,為0表示不在集合中。例如“00100110”表示集合 {2,5,6}。bitmap中1的數量就是這個集合的基數。

顯然,與B樹不同bitmap可以高效的進行合並,只需進行按位或(or)運算就可以,而位運算在計算機中的運算效率是很高的。但是bitmap方式也有自己的問題,就是內存使用問題。

很容易發現,bitmap的長度與集合中元素個數無關,而是與基數的上限有關。例如在上面的例子中,假如要計算上限為1億的基數,則需要12.5M字節的bitmap,十個鏈接就需要125M。關鍵在於,這個內存使用與集合元素數量無關,即使一個鏈接僅僅有一個1UV,也要為其分配12.5M字節。

由此可見,雖然bitmap方式易於合並,卻由於內存使用問題而無法廣泛用於大數據場景。

小結

本文重點在於通過電商數據分析中UV計算的例子,說明基數的應用、傳統的基數計數算法及這些算法在大數據面前遇到的問題。實際上目前還沒有發現更好的在大數據場景中准確計算基數的高效算法,因此在不追求絕對准確的情況下,使用概率算法算是一個不錯的解決方案。在后續文章中,我將逐一解讀常用的基數估計概率算法。

轉載請注明原處

 

 
 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM