大資料計算:如何僅用1.5KB記憶體為十億對象計數 - Hyper LogLog 演算法
Big Data Counting: How To Count A Billion Distinct Objects Using Only 1.5K
This is a guest post by Matt Abrams (@abramsm), from Clearspring, discussing how they are able to accurately estimate the cardinality of sets with billions of distinct elements using surprisingly small data structures. Their servers receive well over 100 billion events per month.
在Clearspring,我們從事統計資料。統計一組不同元素且數量很大的資料集時,是一個挑戰。
為了更好地理解已經明確基數的大資料集的挑戰,我們假設你的記錄檔包含16個字元的ID,並且你想統計不同ID的數量.例如:
4f67bfc603106cb2
這16個字元需要用128位來表示。6萬5千個ID將需要1MB的空間。我們每天收到30多億條事件記錄,每條記錄都有一個ID。這些ID需要3840億位或45GB的儲存。而這僅僅是ID欄位需要的空間。我們採取一種簡單的方法擷取日常事件記錄中以ID為基數的資料。最簡單的辦法就是使用雜湊集合且存放到記憶體中,其中雜湊集包含唯一ID的列表(即輸入檔案中可能會有多條記錄的id是相同,但在雜湊集中只存放一條)。即使我們假設只有1/3的條記錄ID是唯一的(即2/3的記錄ID是重複的),雜湊集仍需要119GB的RAM,這其中不包括Java需要在記憶體中儲存物件的開銷。你需要一台配備幾百GB記憶體的機器來計算不同的元素,並且這隻是計算一天內日誌事件記錄的唯一ID的記憶體消耗。如果我們想要統計數周或數月的資料,這問題只會變得更加困難。我們身邊當然不會有一台配備幾百GB記憶體的空閑機器,所以我們需要一個更好的解決方案。
解決這一問題的常見辦法是使用位元影像(部落格:海量資料處理演算法—Bit-Map)。位元影像可以快速、準確地擷取一個給定輸入的基數。位元影像的基本思想是使用雜湊函數把資料集映射到一個bit位,每個輸入元素與bit位是一一對應。這樣Hash將沒有產生碰撞衝突,並減少需要計算每個元素映射到1個bit的空間。雖然Bit-map大大節省了儲存空間,但當統計很高的基數或非常大的不同的資料集,它們仍然有問題。例如,如果我們想要使用Bit-map計數十億,你將需要Bit-map位,或需要每個約120 MB的計數器。稀疏的位元影像可以被壓縮,以獲得更多的空間效率,但也並不總是有協助的。
幸運的是,基數估計是一個熱門的研究領域。我們已經利用這項研究提供了一個開源實現的基數估計、集合元素檢測和top-k演算法。
基數估計演算法就是使用準確性換取空間。為了說明這一點,我們用三種不同的計算方法統計所有莎士比亞作品中不同單詞的數量。請注意,我們的輸入資料集增加了額外的資料以致比問題的參考基數更高。這三種技術是:Java HashSet、Linear Probabilistic Counter以及一個Hyper LogLog Counter。結果如下:
該表顯示,我們統計這些單詞只用了512 bytes,而誤差在3%以內。相比之下,HashMap的計數準確度最高,但需要近10MB的空間,你可以很容易地看到為什麼基數估計是有用的。在實際應用中準確性並不是很重要的,這是事實,在大多數網路規模和網路計算的情況下,用機率計數器會節省巨大的空間。
線性機率計數器
線性機率計數器是高效的使用空間,並且允許實現者指定所需的精度水平。該演算法在注重空間效率時是很有用的,但你需要能夠控制結果的誤差。該演算法分兩步運行:第一步,首先在記憶體中分配一個初始化為都為0的Bit-map,然後使用雜湊函數對輸入資料中的每個條目進行hash計算,雜湊函數運算的結果是將每條記錄(或者是元素)映射到Bit-map的一個Bit位上,該Bit位被置為1;第二步,演算法計算空的bit位元量,並使用這個數輸入到下面的公式來進行估算:
n=-m ln Vn
注意:ln Vn=Loge(Vn) 自然對數
在公式中,m是 Bit-map的大小,Vn是空bit位和map的大小的比率。需要重點注意的是原始Bit-map的大小,可以遠小於預期的最大基數。到底小多少取決於你可以承受誤差的大小。因為Bit-map的大小m小於不同元素的總數將會產生碰撞。雖然碰撞可以節省空間的,但同時也造成了估算結果出現誤差。所以通過控制原始map的大小,我們可以估算碰撞的次數,以致我們將在最終結果中看到誤差有多大。
Hyper LogLog
顧名思義,Hyper LogLog計數器就是估算Nmax為基數的資料集僅需使用loglog(Nmax)+O(1) bits就可以。如線性計數器的Hyper LogLog計數器允許設計人員指定所需的精度值,在Hyper LogLog的情況下,這是通過定義所需的相對標準差和預期要計數的最大基數。大部分計數器通過一個輸入資料流M,並應用一個雜湊函數設定h(M)來工作。這將產生一個S = h(M) of {0,1}^∞字串的可觀測結果。通過分割雜湊輸入資料流成m個子字串,並對每個子輸入資料流保持m的值可觀測 ,這就是相當一個新Hyper LogLog(一個子m就是一個新的Hyper LogLog)。利用額外的觀測值的平均值,產生一個計數器,其精度隨著m的增長而提高,這隻需要對輸入集合中的每個元素執行幾步操作就可以完成。其結果是,這個計數器可以僅使用1.5 kb的空間計算精度為2%的十億個不同的資料元素。與執行 HashSet所需的120 MB進行比較,這種演算法的效率很明顯。
合并分布式計數器
我們已經證明了使用上面描述的計數器我們可以估算大集合的基數。但是,如果你的原始輸入資料集不適合於單台機器,將怎麼做呢。這正是我們在Clearspring所面臨的問題。我們的資料分散在數百台伺服器上,並且每個伺服器只包含整個資料集子集的一部分。這事實上我們能合并一組分布式計數器的內容是至關重要的。這個想法有點令人費解,但如果你花費一些時間去思考這個問題,就會發現其與基本的基數估計值相比並沒有太大的不同。因為這個計數器表示映射中的位作為基數,我們可以採取兩個相容計數器並將他們bit位合并到單一的map上。這個演算法已經處理碰撞,所以我們可以得到一個基數估計所需的精密,即使我們從來沒有把所有的輸入資料到一台機器。這是非常有用的,節省了我們在網路中移動資料的大量時間和精力。
Next Steps
希望這篇文章能協助你更好地理解這個概念和機率計數器的應用。如果估算大集合的基數是一個問題,而你又碰巧使用一個基於JVM的語言,那麼你應該使用stream-lib項目——它提供了其他幾個流處理工具以及上文所述的演算法的實現。
本文來自:High Scalability
若深入瞭解Hyper LogLog:
http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf
http://www.ic.unicamp.br/~celio/peer2peer/math/bitmap-algorithms/durand03loglog.pdf
文章來源:http://blog.csdn.net/hguisu/article/details/8433731
=================================================================================================
Hyperloglog演算法淺說
這個演算法的目的:
比如給你一個數組,int a[]={1,1,2,6,9,8,5,4,1,2}
這個數組裡一共有十個元素,其中distinct的數一共有7個,它們是1,2,4,5,6,8,9
這個演算法就是判斷輸入資料流中互不相同的元素一共有多少個。
這個演算法是機率演算法,但是它的精確度很高,以下是它的描述和實現細節
我們首先需要以下幾個輔助函數或者資料
1,int hash(type input);//將輸入的元素hash成一個32bit的整數,輸入可能是整數,也可能是字串,甚至是結構體,etc
2,unsigned int position(int input);//返回input的二進位表示中,從左往右數,第一個1出現的位置
比如
position(1000000100000111110)=1
position(0001111000011100000)=4
position(000000)=7
3,m=2^b,其中b在[4,16]之間
4,幾個常數
const double a16=0.673
const double a32=0.697
const double a64=0.709
const double am=0.7213/(1+1.079/m) (m>=128)
有了這四個準備之後,我們就可以開始用hyperloglog來實現計數了
m=2^b個計數器,M[1]到M[m]都初始化為0
for(v=input)
{
x=hash(v);
j=1+<x1x2...xb>(binary)
w=x(b+1)x(b+2)....x32
M[j]=max(M[j],position(w));
}
res=am*m^2*S(1,m,2^(-M[j]))
來源:http://www.java123.net/v/356202.html
=================================================================================================
redis資料結構HyperLogLog
如果我們要實現記錄網站每天訪問的獨立IP數量這樣的一個功能
集合實現:
使用集合來儲存每個訪客的 IP ,通過集合性質(集合中的每個元素都各不相同)來得到多個獨立 IP ,
然後通過調用 SCARD 命令來得出獨立 IP 的數量。
舉個例子,程式可以使用以下代碼來記錄 2014 年 8 月 15 日,每個網站訪客的 IP :
ip = get_vistor_ip()
SADD '2014.8.15::unique::ip' ip
然後使用以下代碼來獲得當天的唯一 IP 數量:
SCARD '2014.8.15::unique::ip'
集合實現的問題
使用字串來儲存每個 IPv4 地址最多需要耗費 15 位元組(格式為 'XXX.XXX.XXX.XXX' ,比如
'202.189.128.186')。
下表給出了使用集合記錄不同數量的獨立 IP 時,需要耗費的記憶體數量:
獨立 IP 數量一天一個月一年
一百萬15 MB 450 MB 5.4 GB
一千萬150 MB 4.5 GB 54 GB
一億1.5 GB 45 GB 540 GB
隨著集合記錄的 IP 越來越多,消耗的記憶體也會越來越多。
另外如果要儲存 IPv6 地址的話,需要的記憶體還會更多一些
為了更好地解決像獨立 IP 位址計算這種問題,
Redis 在 2.8.9 版本添加了 HyperLogLog 結構。
HyperLogLog介紹
HyperLogLog 可以接受多個元素作為輸入,並給出輸入元素的基數估算值:
• 基數:集合中不同元素的數量。比如 {'apple', 'banana', 'cherry', 'banana', 'apple'} 的基數就是 3 。
• 估算值:演算法給出的基數並不是精確的,可能會比實際稍微多一些或者稍微少一些,但會控制在合
理的範圍之內。
HyperLogLog 的優點是,即使輸入元素的數量或者體積非常非常大,計算基數所需的空間總是固定
的、並且是很小的。
在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基
數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以
HyperLogLog 不能像集合那樣,返回輸入的各個元素。
將元素添加至 HyperLogLog
PFADD key element [element ...]
將任意數量的元素添加到指定的 HyperLogLog 裡面。
這個命令可能會對 HyperLogLog 進行修改,以便反映新的基數估算值,如果 HyperLogLog 的基數估算
值在命令執行之後出現了變化, 那麼命令返回 1 , 否則返回 0 。
命令的複雜度為 O(N) ,N 為被添加元素的數量。
返回給定 HyperLogLog 的基數估算值
PFCOUNT key [key ...]
當只給定一個 HyperLogLog 時,命令返回給定 HyperLogLog 的基數估算值。
當給定多個 HyperLogLog 時,命令會先對給定的 HyperLogLog 進行並集計算,得出一個合并後的
HyperLogLog ,然後返回這個合并 HyperLogLog 的基數估算值作為命令的結果(合并得出的
HyperLogLog 不會被儲存,使用之後就會被刪掉)。
當命令作用於單個 HyperLogLog 時, 複雜度為 O(1) , 並且具有非常低的平均常數時間。
當命令作用於多個 HyperLogLog 時, 複雜度為 O(N) ,並且常數時間也比處理單個 HyperLogLog 時要
大得多。
PFADD 和 PFCOUNT 的使用樣本
redis> PFADD unique::ip::counter '192.168.0.1'
(integer) 1
redis> PFADD unique::ip::counter '127.0.0.1'
(integer) 1
redis> PFADD unique::ip::counter '255.255.255.255'
(integer) 1
redis> PFCOUNT unique::ip::counter
(integer) 3
合并多個 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...]
將多個 HyperLogLog 合并為一個 HyperLogLog ,合并後的 HyperLogLog 的基數估算值是通過對所有
給定 HyperLogLog 進行並集計算得出的。
命令的複雜度為 O(N) , 其中 N 為被合并的 HyperLogLog 數量, 不過這個命令的常數複雜度比較高。
PFMERGE 的使用樣本
redis> PFADD str1 "apple" "banana" "cherry"
(integer) 1
redis> PFCOUNT str1
(integer) 3
redis> PFADD str2 "apple" "cherry" "durian" "mongo"
(integer) 1
redis> PFCOUNT str2
(integer) 4
redis> PFMERGE str1&2 str1 str2
OK
redis> PFCOUNT str1&2
(integer) 5
HyperLogLog 實現獨立 IP 計算功能
獨立 IP 數量一天一個月一年一年(使用集合)
一百萬12 KB 360 KB 4.32 MB 5.4 GB
一千萬12 KB 360 KB 4.32 MB 54 GB
一億12 KB 360 KB 4.32 MB 540 GB
下表列出了使用 HyperLogLog 記錄不同數量的獨立 IP 時,需要耗費的記憶體數量:
可以看到,要統計相同數量的獨立 IP ,HyperLogLog 所需的記憶體要比集合少得多。
來源:http://www.cnblogs.com/ysuzhaixuefei/p/4052110.html