在討論我們是否真的需要map-reduce這一分布式計算技術之前,我們先面對乙個問題,這可以為我們討論這個問題提供乙個直觀的背景。
我們先從最直接和直觀的方式出發,來嘗試解決這個問題:
先偽一下這個問題:
select
count(distinct surname)
from big_name_file
我們用乙個指標來關聯這個檔案.
接著考察每一行的資料,解析出裡面的姓氏,這裡我們可能需要乙個姓氏字典或者對照表,然後我們可以利用最長字首匹配來解析出姓氏。這很像命名實體識別所幹的事。
拿到了姓氏,我們還需要乙個鍊錶l,這個鍊錶的每個元素儲存兩個資訊,乙個是姓氏或者姓氏的編號,另乙個是這個姓氏出現的次數。
在考察每一行的資料時,我們解析出姓氏,然後在鍊錶l中查詢這個姓氏對應的元素是否存在,如果存在就將這個元素的姓氏出現次數加一,否則就新增乙個元素,然後置這個元素的姓氏出現次數為1。
當所有的行都遍歷完畢,鍊錶l的長度就是不同的姓氏的個數出現的次數。
/**
* 直接法偽**
*/int distinctcount(file)
//鍊錶的長度就是檔案中不同姓氏的個數
return l.size();
}
ok,這個方法在不關心效率和記憶體空間的情況下是個解決辦法。
但是卻有一些值得注意的問題:
在進行addorupdate操作時,我們需要進行乙個find的操作來找到元素是否已在鍊錶中了。對於無序鍊錶來說,我們必須採取逐一比較的方式來實現這個find的語義。對於上面的考慮,顯然我們知道如果能按下標直接找出元素就最好不過了,我們可以在常量時間找出元素並更新姓氏出現的次數。
對於這一點,我們可以採取雜湊表來做,採取這個結構,我們可以用常量時間來找到元素並更新。
int distinctcountwithhashtable(file)
//雜湊表中實際儲存的元素個數就是檔案中不同姓氏的個數
return t.size();
}
雜湊表法看起來很美,但還是有潛在的問題,如果記憶體不夠大怎麼辦,雜湊表在記憶體中放不下。這個問題同樣存在於直接法中。
想想看,如果這個檔案是個排好序的檔案,那該多好。
所有重複的姓氏都會連著出現,這樣我們只需要標記乙個計數器,每次讀取一行文字,如果解析出的姓氏和上一行的不同,計數器就增1.
那麼**就像下面這樣:
int distinctcountwithsortedfile(file)
return c;
}
遺憾的是,我們並不能保證給定的檔案是有序的。但上面方法的優點是可以破除記憶體空間的限制,對記憶體的需求很小很小。
那麼能不能先排個序呢?
肯定是可以的,那麼多排序演算法在。但是有了記憶體空間的限制,能用到的排序演算法大概只有位圖法和外排了吧。
假設13億/32 + 1個int(這裡設32位)的記憶體空間還是有的,那麼我們用位圖法來做。
位圖法很簡單,基本上需要兩個操作:
/**
* 將i編碼
*/void encode(m,i)
/***將i解碼
*/int decode(m,i)
假設我們採取和姓氏字典一樣的編號,我們做乙個自然公升序,那麼這個方法就像下面這樣:
int distinctcountwithbitmap(file)
//找出點陣圖中二進位制1的個數
c return c;
}
ok,一切看起來很完美,但如何有效地找出點陣圖中的二進位制1的個數呢?上面使用了乙個findcountofonebits方法,找出二進位制1的個數,好吧,這是另外乙個問題,但我們為了完整,可以給出它的一些演算法:
int findcountofonebits_1(int array)
int findcountofonebits_2(int array)
}return c;
}int findcountofonebits_3(int array)
return c;
}
上面的演算法哪種效率最高呢?老三。
ok,位圖法看起來破除了記憶體的限制,的確如此嗎?如果記憶體小到連位圖都放不下怎麼辦?
不解決這個問題了!開玩笑~
既然記憶體嚴重不足,那麼我們只能每次處理一小部分資料,然後對這部分資料進行不同姓氏的個數的統計,用乙個
的結構去維護這個統計,其中key就代表了我們的姓氏,co
unt 代表了它出現的次數。
處理完畢一小批資料後,我們需要將統計結果持久化到硬碟,以備最後累計,這牽扯到乙個合併的問題。
如何進行有效地合併也值得思索,因為一開始檔案內的姓名是無序的,所以不能在最後時刻進行簡單合併,因為同一種姓氏可能出現在不同的統計結果分組中,這會使得統計結果出現重複。
所以我們必須對每批統計結果維護乙個gr
oup 結構或者如下的結構:
統計結果這樣,我們在最後可以按key進行合併,得出如下的結構:1 :..
.}統計結果
2 :..
.}… 統計結果
n : ..
.}
彙總結果btw,資料是瞎編的,我個人並不知道到底哪個姓氏最多。 這樣m1 :..
.}彙總結果
2 :
… 彙總結果
m :..
.}
就是我們不同姓氏的個數。
由於不斷地將部分的統計結果合併到硬碟中,這種方式非常類似lsm演算法,不同的是,我們對硬碟上中間檔案的合併是on-line的,不是off-line的。
合併法中,顯然需要多次的訪問硬碟,這有點問題:
如果是機械硬碟,那麼磁碟的尋道時間令人頭痛。面對記憶體容量有限的假設,我們可以推廣到單機的計算資源有限的場景中來,設想一下,上面所列舉的演算法中,如果文件是有序的,那麼我們僅僅使用極小的記憶體就可以解決問題,那麼我們不需要分布式,也不需要map-reduce。並且,合併的演算法是序列的,我們無法降低攤還尋道代價。
當然,如果我們不僅需要統計不同姓氏的個數,還想知道不同姓氏出現的頻率,以研究到底姓王的多還是姓張的多,那麼我們需要一些新思路。
如果我們能將姓名資料仔細分組,使得同樣的姓氏會出現在同一組中.而對應這種分布式計算模型的,就是map-reduce.然後將這些組分派到不同的計算節點上,由這些節點平行計算出若干個數c1
、c2、
...、
cn,最終我們的答案就是:
n .
而每個姓氏的頻率可以表示為: fr
eque
ncyi
=ci∑
ni=1
ci,其
中i是姓
氏的編號
,ci表
示第i個
姓氏的出現的個數 。
乙個典型的map-reduce模型,大概像下圖這樣:
注:上圖來自search engines:information retrieval in practice.
對應我們這個問題,偽**如下:
function
map(file)
}function
reduce
(key,values)
emit(surname,c);
}
使用map-reduce技術,不僅可以並行處理姓氏頻率,同時也可以應對big、big、big-data(比如全銀河系的「人」的姓名)。前提是你有足夠的計算節點或者機器。
這裡還有乙個問題需要注意,就是上面的reduce演算法預設了資料已經按姓氏分組了,這個目標我們依靠shuffle來完成。
在shuffle階段,依靠雜湊表來完成gr在這裡,將所有資料按姓氏分組並將每一組分派到乙個計算節點上顯得有些奢侈,所以如果在機器不足的情況下,可以將分組的粒度變大,比如100個姓氏為一組,然後通過多次的map-reduce來獲得最終結果。oupb
ysur
name
.
最後,希望我說明白了為什麼我們需要map-reduce技術。
同時,不得不承認這個問題的設定是比較尷尬的= _ =,因為在對姓氏的parse階段,我們用到了乙個全姓氏字典,顯然這個字典本身(trie or hash)可以告訴我們不同姓氏的個數。但如果問題的設定不是全部的姓氏都出現在檔案中,或許這篇文章就能起到拋磚引玉的效果,那麼其中的過程也值得書寫下來。
我們為什麼需要睡眠
隨著時光的消逝,你是否發覺眼角的皺紋逐漸加深?變得越來越粗糙黯淡?記憶力也開始衰退?這個時候很多人都會感慨 時光易逝,容顏易老 並且開始習慣接受自己已慢慢變老,提前加入老人的行列。其實,這一切也許只是因為你長時間睡眠不足造成的。如果能夠早些了解這些常識,並引起足夠重視,你的青春也許還能保留十年。睡眠...
我們為什麼需要睡眠
隨著時光的消逝,你是否發覺眼角的皺紋逐漸加深?變得越來越粗糙黯淡?記憶力也開始衰退?這個時候很多人都會感慨 時光易逝,容顏易老 並且開始習慣接受自己已慢慢變老,提前加入老人的行列。其實,這一切也許只是因為你長時間睡眠不足造成的。如果能夠早些了解這些常識,並引起足夠重視,你的青春也許還能保留十年。睡眠...
我們為什麼需要SDN?
引言 sdn為什麼會出現?是什麼原因使得學術界提出sdn?我們為什麼需要sdn?如果你剛接觸sdn方案時,你一定有這樣的疑問。而問題的答案是 我們需要擁有更多可程式設計能力的網路,來支援快速增長的網路業務需求。本文選自 重構網路 sdn架構與實現 眾所周知,相比發展迅速的計算機產業,網路產業的創新十...