所謂海量數據處理,無非就是基於海量數據上的存儲、處理、操做。何謂海量,就是數據量太大,因此致使要麼是沒法在較短期內迅速解決,要麼是數據太大,致使沒法一次性裝入內存。前端
那解決辦法呢?針對時間,咱們能夠採用巧妙的算法搭配合適的數據結構,如Bloom filter/Hash/bit-map/堆/數據庫或倒排索引/trie樹,針對空間,無非就一個辦法:大而化小:分而治之/hash映射,你不是說規模太大嘛,那簡單啊,就把規模大化爲規模小的,各個擊破不就完了嘛。node
至於所謂的單機及集羣問題,通俗點來說,單機就是處理裝載數據的機器有限(只要考慮cpu,內存,硬盤的數據交互),而集羣,機器有多輛,適合分佈式處理,並行計算(更多考慮節點和節點間的數據交互)。程序員
再者,經過本blog內的有關海量數據處理的文章:Big Data Processing,咱們已經大體知道,處理海量數據問題,無非就是:面試
1 分而治之/hash映射 + hash統計 + 堆/快速/歸併排序;算法
2 雙層桶劃分數據庫
3 Bloom filter/Bitmap;編程
4 Trie樹/數據庫/倒排索引;後端
5 外排序;數組
6 分佈式處理之Hadoop/Mapreduce。緩存
下面,本文第一部分、從set/map談到hashtable/hash_map/hash_set,簡要介紹下set/map/multiset/multimap,及hash_set/hash_map/hash_multiset/hash_multimap之區別(萬丈高樓平地起,基礎最重要),而本文第二部分,則針對上述那6種方法模式結合對應的海量數據處理面試題分別具體闡述。
第一部分、從set/map談到hashtable/hash_map/hash_set
稍後本文第二部分中將屢次提到hash_map/hash_set,下面稍稍介紹下這些容器,以做爲基礎準備。通常來講,STL容器分兩種,
· 序列式容器(vector/list/deque/stack/queue/heap),
· 關聯式容器。關聯式容器又分爲set(集合)和map(映射表)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和multimap(多鍵映射表),這些容器均以RB-tree完成。此外,還有第3類關聯式容器,如hashtable(散列表),以及以hashtable爲底層機制完成的hash_set(散列集合)/hash_map(散列映射表)/hash_multiset(散列多鍵集合)/hash_multimap(散列多鍵映射表)。也就是說,set/map/multiset/multimap都內含一個RB-tree,而hash_set/hash_map/hash_multiset/hash_multimap都內含一個hashtable。
所謂關聯式容器,相似關聯式數據庫,每筆數據或每一個元素都有一個鍵值(key)和一個實值(value),即所謂的Key-Value(鍵-值對)。當元素被插入到關聯式容器中時,容器內部結構(RB-tree/hashtable)便依照其鍵值大小,以某種特定規則將這個元素放置於適當位置。
包括在非關聯式數據庫中,好比,在MongoDB內,文檔(document)是最基本的數據組織形式,每一個文檔也是以Key-Value(鍵-值對)的方式組織起來。一個文檔能夠有多個Key-Value組合,每一個Value能夠是不一樣的類型,好比String、Integer、List等等。
{ "name" : "July",
"sex" : "male",
"age" : 23 }
set/map/multiset/multimap
set,同map同樣,全部元素都會根據元素的鍵值自動被排序,由於set/map二者的全部各類操做,都只是轉而調用RB-tree的操做行爲,不過,值得注意的是,二者都不容許兩個元素有相同的鍵值。
不一樣的是:set的元素不像map那樣能夠同時擁有實值(value)和鍵值(key),set元素的鍵值就是實值,實值就是鍵值,而map的全部元素都是pair,同時擁有實值(value)和鍵值(key),pair的第一個元素被視爲鍵值,第二個元素被視爲實值。
至於multiset/multimap,他們的特性及用法和set/map徹底相同,惟一的差異就在於它們容許鍵值重複,即全部的插入操做基於RB-tree的insert_equal()而非insert_unique()。
hash_set/hash_map/hash_multiset/hash_multimap
hash_set/hash_map,二者的一切操做都是基於hashtable之上。不一樣的是,hash_set同set同樣,同時擁有實值和鍵值,且實質就是鍵值,鍵值就是實值,而hash_map同map同樣,每個元素同時擁有一個實值(value)和一個鍵值(key),因此其使用方式,和上面的map基本相同。但因爲hash_set/hash_map都是基於hashtable之上,因此不具有自動排序功能。爲何?由於hashtable沒有自動排序功能。
至於hash_multiset/hash_multimap的特性與上面的multiset/multimap徹底相同,惟一的差異就是它們hash_multiset/hash_multimap的底層實現機制是hashtable(而multiset/multimap,上面說了,底層實現機制是RB-tree),因此它們的元素都不會被自動排序,不過也都容許鍵值重複。
因此,綜上,說白了,什麼樣的結構決定其什麼樣的性質,由於set/map/multiset/multimap都是基於RB-tree之上,因此有自動排序功能,而hash_set/hash_map/hash_multiset/hash_multimap都是基於hashtable之上,因此不含有自動排序功能,至於加個前綴multi_無非就是容許鍵值重複而已。
OK,接下來,請看本文第二部分、處理海量數據問題之六把密匙。
第二部分、處理海量數據問題之六把密匙
密匙1、分而治之/Hash映射 + Hash統計 + 堆/快速/歸併排序
一、海量日誌數據,提取出某日訪問百度次數最多的那個IP。
既然是海量數據處理,那麼可想而知,給咱們的數據那就必定是海量的。針對這個數據的海量,咱們如何着手呢?對的,無非就是分而治之/hash映射 + hash統計 + 堆/快速/歸併排序,說白了,就是先映射,然後統計,最後排序:
7 分而治之/hash映射:針對數據太大,內存受限,只能是:把大文件化成(取模映射)小文件,即16字方針:大而化小,各個擊破,縮小規模,逐個解決
8 hash統計:當大文件轉化了小文件,那麼咱們即可以採用常規的hash_map(ip,value)來進行頻率統計。所謂邊映射邊統計,在映射成小文件的同時完成hash_map頻率的統計。
9 堆/快速排序:統計完了以後,便進行排序(可採起堆排序),獲得次數最多的IP。
具體而論,則是: 「首先是這一天,而且是訪問百度的日誌中的IP取出來,逐個寫入到一個大文件中。注意到IP是32位的,最多有個2^32個IP。一樣能夠採用映射的方法,好比模1000,把整個大文件映射爲1000個小文件,再找出每一個小文中出現頻率最大的IP(能夠採用hash_map進行頻率統計,而後再找出頻率最大的幾個)及相應的頻率。而後再在這1000個最大的IP中,找出那個頻率最大的IP,即爲所求。」--十道海量數據處理面試題與十個方法大總結。
關於本題,還有幾個問題,以下:
一、有讀者反應:既然用hash_map統計完以後還要排序,爲什麼不直接用map搞定呢,同時完成統計和排序功能?可是凡有利必有弊,別忘了,hash_map統計時的時間複雜度在O(1)內搞定,而map至少logK(若爲K個元素)。
二、Hash取模是一種等價映射,不會存在同一個元素分散到不一樣小文件中去的狀況,即這裏採用的是mod1000算法,那麼相同的IP在hash後,只可能落在同一個文件中,不可能被分散的。
三、那到底什麼是hash映射呢?簡單來講,就是爲了便於計算機在有限的內存中處理big數據,從而經過一種映射散列的方式讓數據均勻分佈在對應的內存位置(如大數據經過取餘的方式映射成小樹存放在內存中,或大文件映射成多個小文件),而這個映射散列方式即是咱們一般所說的hash函數,設計的好的hash函數能讓數據均勻分佈而減小衝突。儘管數據映射到了另一些不一樣的位置,但數據仍是原來的數據,只是代替和表示這些原始數據的形式發生了變化而已。
二、尋找熱門查詢:搜索引擎會經過日誌文件把用戶每次檢索使用的全部檢索串都記錄下來,每一個查詢串的長度爲1-255字節。
假設目前有一千萬個記錄(這些查詢串的重複度比較高,雖然總數是1千萬,但若是除去重複後,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就是越熱門),請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。
由上面第1題,咱們知道,數據大則劃爲小的,但若是數據規模比較小,能一次性裝入內存呢?好比這第2題,雖然有一千萬個Query,可是因爲重複度比較高,所以事實上只有300萬的Query,每一個Query255Byte,所以咱們能夠考慮把他們都放進內存中去,而如今只是須要一個合適的數據結構,在這裏,Hash Table絕對是咱們優先的選擇。因此咱們摒棄分而治之/hash映射的方法,直接上hash統計,而後排序。So,
10 hash統計:先對這批海量數據預處理(維護一個Key爲Query字串,Value爲該Query出現次數的HashTable,即hash_map(Query,Value),每次讀取一個Query,若是該字串不在Table中,那麼加入該字串,而且將Value值設爲1;若是該字串在Table中,那麼將該字串的計數加一便可。最終咱們在O(N)的時間複雜度內用Hash表完成了統計;
11 堆排序:第二步、藉助堆這個數據結構,找出Top K,時間複雜度爲N‘logK。即藉助堆結構,咱們能夠在log量級的時間內查找和調整/移動。所以,維護一個K(該題目中是10)大小的小根堆,而後遍歷300萬的Query,分別和根元素進行對比因此,咱們最終的時間複雜度是:O(N) + N'*O(logK),(N爲1000萬,N’爲300萬)。
別忘了這篇文章中所述的堆排序思路:「維護k個元素的最小堆,即用容量爲k的最小堆存儲最早遍歷到的k個數,並假設它們便是最大的k個數,建堆費時O(k),並調整堆(費時O(logk))後,有k1>k2>...kmin(kmin設爲小頂堆中最小元素)。繼續遍歷數列,每次遍歷一個元素x,與堆頂元素比較,若x>kmin,則更新堆(用時logk),不然不更新堆。這樣下來,總費時O(k*logk+(n-k)*logk)=O(n*logk)。此方法得益於在堆中,查找等各項操做時間複雜度均爲logk。」--第三章續、Top K算法問題的實現。
固然,你也能夠採用trie樹,關鍵字域存該查詢串出現的次數,沒有出現爲0。最後用10個元素的最小推來對出現頻率進行排序。
三、有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。
由上面那兩個例題,分而治之 + hash統計 + 堆/快速排序這個套路,咱們已經開始有了屢試不爽的感受。下面,再拿幾道再多多驗證下。請看此第3題:又是文件很大,又是內存受限,咋辦?還能怎麼辦呢?無非仍是:
12 分而治之/hash映射:順序讀文件中,對於每一個詞x,取hash(x)%5000,而後按照該值存到5000個小文件(記爲x0,x1,...x4999)中。這樣每一個文件大概是200k左右。若是其中的有的文件超過了1M大小,還能夠按照相似的方法繼續往下分,直到分解獲得的小文件的大小都不超過1M。
13 hash統計:對每一個小文件,採用trie樹/hash_map等統計每一個文件中出現的詞以及相應的頻率。
14 堆/歸併排序:取出出現頻率最大的100個詞(能夠用含100個結點的最小堆),並把100個詞及相應的頻率存入文件,這樣又獲得了5000個文件。最後就是把這5000個文件進行歸併(相似於歸併排序)的過程了。
四、海量數據分佈在100臺電腦中,想個辦法高效統計出這批數據的TOP10。
此題與上面第3題相似,
15 堆排序:在每臺電腦上求出TOP10,能夠採用包含10個元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆)。好比求TOP10大,咱們首先取前10個元素調整成最小堆,若是發現,而後掃描後面的數據,並與堆頂元素比較,若是比堆頂元素大,那麼用該元素替換堆頂,而後再調整爲最小堆。最後堆中的元素就是TOP10大。
16 求出每臺電腦上的TOP10後,而後把這100臺電腦上的TOP10組合起來,共1000個數據,再利用上面相似的方法求出TOP10就能夠了。
上述第4題的此解法,經讀者反應有問題,如舉個例子如求2個文件中的top2,照上述算法,若是第一個文件裏有
a 49次
b 50次
c 2次
d 1次
第二個文件裏有
a 9次
b 1次
c 11次
d 10次
雖然第一個文件裏出來top2是b(50次),a(49次),第二個文件裏出來top2是c(11次),d(10次),而後2個top2:b(50次)a(49次)與c(11次)d(10次)歸併,則算出全部的文件的top2是b(50 次),a(49 次),但其實是a(58 次),b(51 次)。是否真是如此呢?若真如此,那做何解決呢?
正如老夢所述:
首先,先把全部的數據遍歷一遍作一次hash(保證相同的數據條目劃分到同一臺電腦上進行運算),而後根據hash結果從新分佈到100臺電腦中,接下來的算法按照以前的便可。
最後因爲a可能出如今不一樣的電腦,各有必定的次數,再對每一個相同條目進行求和(因爲上一步驟中hash以後,也方便每臺電腦只須要對本身分到的條目內進行求和,不涉及到別的電腦,規模縮小)。
五、有10個文件,每一個文件1G,每一個文件的每一行存放的都是用戶的query,每一個文件的query均可能重複。要求你按照query的頻度排序。
直接上:
17 hash映射:順序讀取10個文件,按照hash(query)%10的結果將query寫入到另外10個文件(記爲)中。這樣新生成的文件每一個的大小大約也1G(假設hash函數是隨機的)。
18 hash統計:找一臺內存在2G左右的機器,依次對用hash_map(query, query_count)來統計每一個query出現的次數。注:hash_map(query,query_count)是用來統計每一個query的出現次數,不是存儲他們的值,出現一次,則count+1。
19 堆/快速/歸併排序:利用快速/堆/歸併排序按照出現次數進行排序,將排序好的query和對應的query_cout輸出到文件中,這樣獲得了10個排好序的文件(記爲)。最後,對這10個文件進行歸併排序(內排序與外排序相結合)。
除此以外,此題還有如下兩個方法:
方案2:通常query的總量是有限的,只是重複的次數比較多而已,可能對於全部的query,一次性就能夠加入到內存了。這樣,咱們就能夠採用trie樹/hash_map等直接來統計每一個query出現的次數,而後按出現次數作快速/堆/歸併排序就能夠了。
方案3:與方案1相似,但在作完hash,分紅多個文件後,能夠交給多個文件來處理,採用分佈式的架構來處理(好比MapReduce),最後再進行合併。
六、 給定a、b兩個文件,各存放50億個url,每一個url各佔64字節,內存限制是4G,讓你找出a、b文件共同的url?
能夠估計每一個文件安的大小爲5G×64=320G,遠遠大於內存限制的4G。因此不可能將其徹底加載到內存中處理。考慮採起分而治之的方法。
20 分而治之/hash映射:遍歷文件a,對每一個url求取,而後根據所取得的值將url分別存儲到1000個小文件(記爲)中。這樣每一個小文件的大約爲300M。遍歷文件b,採起和a相同的方式將url分別存儲到1000小文件中(記爲)。這樣處理後,全部可能相同的url都在對應的小文件()中,不對應的小文件不可能有相同的url。而後咱們只要求出1000對小文件中相同的url便可。
21 hash統計:求每對小文件中相同的url時,能夠把其中一個小文件的url存儲到hash_set中。而後遍歷另外一個小文件的每一個url,看其是否在剛纔構建的hash_set中,若是是,那麼就是共同的url,存到文件裏面就能夠了。
OK,此第一種方法:分而治之/hash映射 + hash統計 + 堆/快速/歸併排序,再看最後4道題,以下:
七、怎麼在海量數據中找出重複次數最多的一個?
方案1:先作hash,而後求模映射爲小文件,求出每一個小文件中重複次數最多的一個,並記錄重複次數。而後找出上一步求出的數據中重複次數最多的一個就是所求(具體參考前面的題)。
八、上千萬或上億數據(有重複),統計其中出現次數最多的錢N個數據。
方案1:上千萬或上億的數據,如今的機器的內存應該能存下。因此考慮採用hash_map/搜索二叉樹/紅黑樹等來進行統計次數。而後就是取出前N個出現次數最多的數據了,能夠用第2題提到的堆機制完成。
九、一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
方案1:這題是考慮時間效率。用trie樹統計每一個詞出現的次數,時間複雜度是O(n*le)(le表示單詞的平準長度)。而後是找出出現最頻繁的前10個詞,能夠用堆來實現,前面的題中已經講到了,時間複雜度是O(n*lg10)。因此總的時間複雜度,是O(n*le)與O(n*lg10)中較大的哪個。
10. 1000萬字符串,其中有些是重複的,須要把重複的所有去掉,保留沒有重複的字符串。請怎麼設計和實現?
· 方案1:這題用trie樹比較合適,hash_map也行。
· 方案2:from xjbzju:,1000w的數據規模插入操做徹底不現實,之前試過在stl下100w元素插入set中已經慢得不能忍受,以爲基於hash的實現不會比紅黑樹好太多,使用vector+sort+unique都要可行許多,建議仍是先hash成小文件分開處理再綜合。
上述方案2中讀者xbzju的方法讓我想到了一些問題,便是set/map,與hash_set/hash_map的性能比較?共計3個問題,以下:
· 一、hash_set在千萬級數據下,insert操做優於set? 這位blog:http://t.cn/zOibP7t 給的實踐數據可靠不?
· 二、那map和hash_map的性能比較呢? 誰作過相關實驗?
· 三、那查詢操做呢,以下段文字所述?
或者小數據量時用map,構造快,大數據量時用hash_map?
rbtree PK hashtable
據朋友№邦卡貓№的作的紅黑樹和hash table的性能測試中發現:當數據量基本上int型key時,hash table是rbtree的3-4倍,但hash table通常會浪費大概一半內存。
由於hash table所作的運算就是個%,而rbtree要比較不少,好比rbtree要看value的數據 ,每一個節點要多出3個指針(或者偏移量) 若是須要其餘功能,好比,統計某個範圍內的key的數量,就須要加一個計數成員。
且1s rbtree能進行大概50w+次插入,hash table大概是差很少200w次。不過不少的時候,其速度能夠忍了,例如倒排索引差很少也是這個速度,並且單線程,且倒排表的拉鍊長度不會太大。正由於基於樹的實現其實不比hashtable慢到哪裏去,因此數據庫的索引通常都是用的B/B+樹,並且B+樹還對磁盤友好(B樹能有效下降它的高度,因此減小磁盤交互次數)。好比如今很是流行的NoSQL數據庫,像MongoDB也是採用的B樹索引。關於B樹系列,請參考本blog內此篇文章:從B樹、B+樹、B*樹談到R 樹。
OK,更多請待後續實驗論證。接下來,我們來看第二種方法,雙層捅劃分。
密匙2、雙層桶劃分
雙層桶劃分----其實本質上仍是分而治之的思想,重在「分」的技巧上!
適用範圍:第k大,中位數,不重複或重複的數字
基本原理及要點:由於元素範圍很大,不能利用直接尋址表,因此經過屢次劃分,逐步肯定範圍,而後最後在一個能夠接受的範圍內進行。能夠經過屢次縮小,雙層只是一個例子。
擴展:
問題實例:
十一、2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
有點像鴿巢原理,整數個數爲2^32,也就是,咱們能夠將這2^32個數,劃分爲2^8個區域(好比用單個文件表明一個區域),而後將數據分離到不一樣的區域,而後不一樣的區域在利用bitmap就能夠直接解決了。也就是說只要有足夠的磁盤空間,就能夠很方便的解決。
十二、5億個int找它們的中位數。
這個例子比上面那個更明顯。首先咱們將int劃分爲2^16個區域,而後讀取數據統計落到各個區域裏的數的個數,以後咱們根據統計結果就能夠判斷中位數落到那個區域,同時知道這個區域中的第幾大數恰好是中位數。而後第二次掃描咱們只統計落在這個區域中的那些數就能夠了。
實際上,若是不是int是int64,咱們能夠通過3次這樣的劃分便可下降到能夠接受的程度。便可以先將int64分紅2^24個區域,而後肯定區域的第幾大數,在將該區域分紅2^20個子區域,而後肯定是子區域的第幾大數,而後子區域裏的數的個數只有2^20,就能夠直接利用direct addr table進行統計了。
密匙三:Bloom filter/Bitmap
Bloom filter
關於什麼是Bloom filter,請參看blog內此文:
適用範圍:能夠用來實現數據字典,進行數據的判重,或者集合求交集
基本原理及要點:
對於原理來講很簡單,位數組+k個獨立hash函數。將hash函數對應的值的位數組置1,查找時若是發現全部hash函數對應位都是1說明存在,很明顯這個過程並不保證查找的結果是100%正確的。同時也不支持刪除一個已經插入的關鍵字,由於該關鍵字對應的位會牽動到其餘的關鍵字。因此一個簡單的改進就是 counting Bloom filter,用一個counter數組代替位數組,就能夠支持刪除了。
還有一個比較重要的問題,如何根據輸入元素個數n,肯定位數組m的大小及hash函數個數。當hash函數個數k=(ln2)*(m/n)時錯誤率最小。在錯誤率不大於E的狀況下,m至少要等於n*lg(1/E)才能表示任意n個元素的集合。但m還應該更大些,由於還要保證bit數組裏至少一半爲0,則m應該>=nlg(1/E)*lge 大概就是nlg(1/E)1.44倍(lg表示以2爲底的對數)。
舉個例子咱們假設錯誤率爲0.01,則此時m應大概是n的13倍。這樣k大概是8個。
注意這裏m與n的單位不一樣,m是bit爲單位,而n則是以元素個數爲單位(準確的說是不一樣元素的個數)。一般單個元素的長度都是有不少bit的。因此使用bloom filter內存上一般都是節省的。
擴展:
Bloom filter將集合中的元素映射到位數組中,用k(k爲哈希函數個數)個映射位是否全1表示元素在不在這個集合中。Counting bloom filter(CBF)將位數組中的每一位擴展爲一個counter,從而支持了元素的刪除操做。Spectral Bloom Filter(SBF)將其與集合元素的出現次數關聯。SBF採用counter中的最小值來近似表示元素的出現頻率。
1三、給你A,B兩個文件,各存放50億條URL,每條URL佔用64字節,內存限制是4G,讓你找出A,B文件共同的URL。若是是三個乃至n個文件呢?
根據這個問題咱們來計算下內存的佔用,4G=2^32大概是40億*8大概是340億,n=50億,若是按出錯率0.01算須要的大概是650億個bit。如今可用的是340億,相差並很少,這樣可能會使出錯率上升些。另外若是這些urlip是一一對應的,就能夠轉換成ip,則大大簡單了。
同時,上文的第5題:給定a、b兩個文件,各存放50億個url,每一個url各佔64字節,內存限制是4G,讓你找出a、b文件共同的url?若是容許有必定的錯誤率,能夠使用Bloom filter,4G內存大概能夠表示340億bit。將其中一個文件中的url使用Bloom filter映射爲這340億bit,而後挨個讀取另一個文件的url,檢查是否與Bloom filter,若是是,那麼該url應該是共同的url(注意會有必定的錯誤率)。
Bitmap
下面關於Bitmap的應用,直接上題,以下第九、10道:
1四、在2.5億個整數中找出不重複的整數,注,內存不足以容納這2.5億個整數。
方案1:採用2-Bitmap(每一個數分配2bit,00表示不存在,01表示出現一次,10表示屢次,11無心義)進行,共需內存2^32 * 2 bit=1 GB內存,還能夠接受。而後掃描這2.5億個整數,查看Bitmap中相對應位,若是是00變01,01變10,10保持不變。所描完過後,查看bitmap,把對應位是01的整數輸出便可。
方案2:也可採用與第1題相似的方法,進行劃分小文件的方法。而後在小文件中找出不重複的整數,並排序。而後再進行歸併,注意去除重複的元素。
1五、騰訊面試題:給40億個不重複的unsigned int的整數,沒排過序的,而後再給一個數,如何快速判斷這個數是否在那40億個數當中?
方案1:frome oo,用位圖/Bitmap的方法,申請512M的內存,一個bit位表明一個unsigned int值。讀入40億個數,設置相應的bit位,讀入要查詢的數,查看相應bit位是否爲1,爲1表示存在,爲0表示不存在。
密匙4、Trie樹/數據庫/倒排索引
Trie樹
適用範圍:數據量大,重複多,可是數據種類小能夠放入內存
基本原理及要點:實現方式,節點孩子的表示方式
擴展:壓縮實現。
問題實例:
22 上面的第2題:尋找熱門查詢:查詢串的重複度比較高,雖然總數是1千萬,但若是除去重複後,不超過3百萬個,每一個不超過255字節。
23 上面的第5題:有10個文件,每一個文件1G,每一個文件的每一行都存放的是用戶的query,每一個文件的query均可能重複。要你按照query的頻度排序。
24 1000萬字符串,其中有些是相同的(重複),須要把重複的所有去掉,保留沒有重複的字符串。請問怎麼設計和實現?
25 上面的第8題:一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞。其解決方法是:用trie樹統計每一個詞出現的次數,時間複雜度是O(n*le)(le表示單詞的平準長度),而後是找出出現最頻繁的前10個詞。
更多有關Trie樹的介紹,請參見此文:從Trie樹(字典樹)談到後綴樹。
數據庫索引
適用範圍:大數據量的增刪改查
基本原理及要點:利用數據的設計實現方法,對海量數據的增刪改查進行處理。
倒排索引(Inverted index)
適用範圍:搜索引擎,關鍵字查詢
基本原理及要點:爲什麼叫倒排索引?一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射。
以英文爲例,下面是要被索引的文本:
T0 = "it is what it is"
T1 = "what is it"
T2 = "it is a banana"
咱們就能獲得下面的反向文件索引:
"a": {2}
"banana": {2}
"is": {0, 1, 2}
"it": {0, 1, 2}
"what": {0, 1}
檢索的條件"what","is"和"it"將對應集合的交集。
正向索引開發出來用來存儲每一個文檔的單詞的列表。正向索引的查詢每每知足每一個文檔有序頻繁的全文查詢和每一個單詞在校驗文檔中的驗證這樣的查詢。在正向索引中,文檔佔據了中心的位置,每一個文檔指向了一個它所包含的索引項的序列。也就是說文檔指向了它包含的那些單詞,而反向索引則是單詞指向了包含它的文檔,很容易看到這個反向的關係。
擴展:
問題實例:文檔檢索系統,查詢那些文件包含了某單詞,好比常見的學術論文的關鍵字搜索。
關於倒排索引的應用,更多請參見:
· 第二十3、四章:楊氏矩陣查找,倒排索引關鍵詞Hash不重複編碼實踐,
密匙5、外排序
適用範圍:大數據的排序,去重
基本原理及要點:外排序的歸併方法,置換選擇敗者樹原理,最優歸併樹
擴展:
問題實例:
1).有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16個字節,內存限制大小是1M。返回頻數最高的100個詞。
這個數據具備很明顯的特色,詞的大小爲16個字節,可是內存只有1M作hash明顯不夠,因此能夠用來排序。內存能夠當輸入緩衝區使用。
關於多路歸併算法及外排序的具體應用場景,請參見blog內此文:
密匙6、分佈式處理之Mapreduce
MapReduce是一種計算模型,簡單的說就是將大批量的工做(數據)分解(MAP)執行,而後再將結果合併成最終結果(REDUCE)。這樣作的好處是能夠在任務被分解後,能夠經過大量機器進行並行計算,減小整個操做的時間。但若是你要我再通俗點介紹,那麼,說白了,Mapreduce的原理就是一個歸併排序。
適用範圍:數據量大,可是數據種類小能夠放入內存
基本原理及要點:將數據交給不一樣的機器去處理,數據劃分,結果歸約。
擴展:
問題實例:
26 The canonical example application of MapReduce is a process to count the appearances of each different word in a set of documents:
27 海量數據分佈在100臺電腦中,想個辦法高效統計出這批數據的TOP10。
28 一共有N個機器,每一個機器上有N個數。每一個機器最多存O(N)個數並對它們操做。如何找到N^2個數的中數(median)?
更多具體闡述請參見blog內:
· 從Hadhoop框架與MapReduce模式中談海量數據處理,
其它模式/方法論,結合操做系統知識
至此,六種處理海量數據問題的模式/方法已經闡述完畢。據觀察,這方面的面試題無外乎以上一種或其變形,然題目爲什麼取爲是:秒殺99%的海量數據處理面試題,而不是100%呢。OK,給讀者看最後一道題,以下:
很是大的文件,裝不進內存。每行一個int類型數據,如今要你隨機取100個數。
咱們發現上述這道題,不管是以上任何一種模式/方法都很差作,那有什麼好的別的方法呢?咱們能夠看看:操做系統內存分頁系統設計(說白了,就是映射+建索引)。
· Windows 2000使用基於分頁機制的虛擬內存。每一個進程有4GB的虛擬地址空間。基於分頁機制,這4GB地址空間的一些部分被映射了物理內存,一些部分映射硬盤上的交換文 件,一些部分什麼也沒有映射。程序中使用的都是4GB地址空間中的虛擬地址。而訪問物理內存,須要使用物理地址。 關於什麼是物理地址和虛擬地址,請看:
物理地址 (physical address): 放在尋址總線上的地址。放在尋址總線上,若是是讀,電路根據這個地址每位的值就將相應地址的物理內存中的數據放到數據總線中傳輸。若是是寫,電路根據這個 地址每位的值就將相應地址的物理內存中放入數據總線上的內容。物理內存是以字節(8位)爲單位編址的。
· 虛擬地址 (virtual address): 4G虛擬地址空間中的地址,程序中使用的都是虛擬地址。 使用了分頁機制以後,4G的地址空間被分紅了固定大小的頁,每一頁或者被映射到物理內存,或者被映射到硬盤上的交換文件中,或者沒有映射任何東西。對於一 般程序來講,4G的地址空間,只有一小部分映射了物理內存,大片大片的部分是沒有映射任何東西。物理內存也被分頁,來映射地址空間。對於32bit的 Win2k,頁的大小是4K字節。CPU用來把虛擬地址轉換成物理地址的信息存放在叫作頁目錄和頁表的結構裏。
物理內存分頁,一個物理頁的大小爲4K字節,第0個物理頁從物理地址 0x00000000 處開始。因爲頁的大小爲4KB,就是0x1000字節,因此第1頁從物理地址 0x00001000 處開始。第2頁從物理地址 0x00002000 處開始。能夠看到因爲頁的大小是4KB,因此只須要32bit的地址中高20bit來尋址物理頁。
返回上面咱們的題目:很是大的文件,裝不進內存。每行一個int類型數據,如今要你隨機取100個數。針對此題,咱們能夠借鑑上述操做系統中內存分頁的設計方法,作出以下解決方案:
操做系統中的方法,先生成4G的地址表,在把這個表劃分爲小的4M的小文件作個索引,二級索引。30位前十位表示第幾個4M文件,後20位表示在這個4M文件的第幾個,等等,基於key value來設計存儲,用key來建索引。
但若是如今只有10000個數,而後怎麼去隨機從這一萬個數裏面隨機取100個數?請讀者思考。
參考文獻
33 從Trie樹(字典樹)談到後綴樹;
36 從B樹、B+樹、B*樹談到R 樹;
37 第二十3、四章:楊氏矩陣查找,倒排索引關鍵詞Hash不重複編碼實踐;
39 從Hadhoop框架與MapReduce模式中談海量數據處理;
40 第十六~第二十章:全排列,跳臺階,奇偶排序,第一個只出現一次等問題;
41 STL源碼剖析第五章,侯捷著。
後記
通過上面這麼多海量數據處理面試題的轟炸,咱們依然能夠看出這類問題是有必定的解決方案/模式的,因此,沒必要將其神化。然這類面試題所包含的問題仍是比較簡單的,若您在這方面有更多實踐經驗,歡迎隨時來信與我不吝分享:zhoulei0907@yahoo.cn。固然,自會註明分享者及來源。
不過,相信你也早就意識到,若單純論海量數據處理面試題,本blog內的有關海量數據處理面試題的文章已涵蓋了你能在網上所找到的70~80%。但有點,必須負責任的敬告你們:不管是這些海量數據處理面試題也好,仍是算法也好,面試時,70~80%的人不是倒在這兩方面,而是倒在基礎之上,因此,不管任什麼時候候,基礎最重要,沒了基礎,便什麼都不是。若是你問我什麼叫作掌握了基礎,很簡單。
OK,本文如有任何問題,歡迎隨時不吝留言,評論,賜教,謝謝。完。
從hadoop框架與MapReduce模式中談海量數據處理
前言
幾周前,當我最初聽到,以至後來初次接觸Hadoop與MapReduce這兩個東西,我便稍顯興奮,以爲它們非常神祕,而神祕的東西常能勾起個人興趣,在看過介紹它們的文章或論文以後,以爲Hadoop是一項富有趣味和挑戰性的技術,且它還牽扯到了一個我更加感興趣的話題:海量數據處理。
由此,最近凡是空閒時,便在看「Hadoop」,「MapReduce」「海量數據處理」這方面的論文。但在看論文的過程當中,總以爲那些論文都是淺嘗輒止,經常看的很不過癮,老是一個東西剛要講到緊要處,它便結束了,讓我好生「憤懣」。
儘管我對這個Hadoop與MapReduce知之甚淺,但我仍是想記錄本身的學習過程,說不定,關於這個東西的學習能督促我最終寫成和「經典算法研究系列」通常的一系列文章。
Ok,閒話少說。本文從最基本的mapreduce模式,Hadoop框架開始談起,而後由各自的架構引伸開來,談到海量數據處理,最後談談淘寶的海量數據產品技術架構,覺得了兼備淺出與深刻之效,最終,但願獲得讀者的喜歡與支持。謝謝。
因爲本人是初次接觸這兩項技術,文章有任何問題,歡迎不吝指正。再謝一次。Ok,我們開始吧。
第一部分、mapreduce模式與hadoop框架深刻淺出
架構扼要
想讀懂此文,讀者必須先要明確如下幾點,以做爲閱讀後續內容的基礎知識儲備:
1 Mapreduce是一種模式。
2 Hadoop是一種框架。
3 Hadoop是一個實現了mapreduce模式的開源的分佈式並行編程框架。
因此,你如今,知道了什麼是mapreduce,什麼是hadoop,以及這二者之間最簡單的聯繫,而本文的主旨便是,一句話歸納:在hadoop的框架上採起mapreduce的模式處理海量數據。下面,我們能夠依次深刻學習和了解mapreduce和hadoop這兩個東西了。
Mapreduce模式
前面說了,mapreduce是一種模式,一種什麼模式呢?一種雲計算的核心計算模式,一種分佈式運算技術,也是簡化的分佈式編程模式,它主要用於解決問題的程序開發模型,也是開發人員拆解問題的方法。
Ok,光說不上圖,沒用。以下圖所示,mapreduce模式的主要思想是將自動分割要執行的問題(例如程序)拆解成map(映射)和reduce(化簡)的方式,流程圖以下圖1所示:
在數據被分割後經過Map 函數的程序將數據映射成不一樣的區塊,分配給計算機機羣處理達到分佈式運算的效果,在經過Reduce 函數的程序將結果彙整,從而輸出開發者須要的結果。
MapReduce 借鑑了函數式程序設計語言的設計思想,其軟件實現是指定一個Map 函數,把鍵值對(key/value)映射成新的鍵值對(key/value),造成一系列中間結果形式的key/value 對,而後把它們傳給Reduce(規約)函數,把具備相同中間形式key 的value 合併在一塊兒。Map 和Reduce 函數具備必定的關聯性。函數描述如表1 所示:
MapReduce致力於解決大規模數據處理的問題,所以在設計之初就考慮了數據的局部性原理,利用局部性原理將整個問題分而治之。MapReduce集羣由普通PC機構成,爲無共享式架構。在處理以前,將數據集分佈至各個節點。處理時,每一個節點就近讀取本地存儲的數據處理(map),將處理後的數據進行合併(combine)、排序(shuffle and sort)後再分發(至reduce節點),避免了大量數據的傳輸,提升了處理效率。無共享式架構的另外一個好處是配合複製(replication)策略,集羣能夠具備良好的容錯性,一部分節點的down機對集羣的正常工做不會形成影響。
ok,你能夠再簡單看看下副圖,整幅圖是有關hadoop的做業調優參數及原理,圖的左邊是MapTask運行示意圖,右邊是ReduceTask運行示意圖:
如上圖所示,其中map階段,當map task開始運算,併產生中間數據後並不是直接而簡單的寫入磁盤,它首先利用內存buffer來對已經產生的buffer進行緩存,並在內存buffer中進行一些預排序來優化整個map的性能。而上圖右邊的reduce階段則經歷了三個階段,分別Copy->Sort->reduce。咱們能明顯的看出,其中的Sort是採用的歸併排序,即merge sort。
瞭解了什麼是mapreduce,接下來,我們能夠來了解實現了mapreduce模式的開源框架—hadoop。
Hadoop框架
前面說了,hadoop是一個框架,一個什麼樣的框架呢?Hadoop 是一個實現了MapReduce 計算模型的開源分佈式並行編程框架,程序員能夠藉助Hadoop 編寫程序,將所編寫的程序運行於計算機機羣上,從而實現對海量數據的處理。
此外,Hadoop 還提供一個分佈式文件系統(HDFS)及分佈式數據庫(HBase)用來將數據存儲或部署到各個計算節點上。因此,你能夠大體認爲:Hadoop=HDFS(文件系統,數據存儲技術相關)+HBase(數據庫)+MapReduce(數據處理)。Hadoop 框架如圖2 所示:
藉助Hadoop 框架及雲計算核心技術MapReduce 來實現數據的計算和存儲,而且將HDFS 分佈式文件系統和HBase 分佈式數據庫很好的融入到雲計算框架中,從而實現雲計算的分佈式、並行計算和存儲,而且得以實現很好的處理大規模數據的能力。
Hadoop的組成部分
咱們已經知道,Hadoop是Google的MapReduce一個Java實現。MapReduce是一種簡化的分佈式編程模式,讓程序自動分佈到一個由普通機器組成的超大集羣上併發執行。Hadoop主要由HDFS、MapReduce和HBase等組成。具體的hadoop的組成以下圖:
由上圖,咱們能夠看到:
一、 Hadoop HDFS是Google GFS存儲系統的開源實現,主要應用場景是做爲並行計算環境(MapReduce)的基礎組件,同時也是BigTable(如HBase、HyperTable)的底層分佈式文件系統。HDFS採用master/slave架構。一個HDFS集羣是有由一個Namenode和必定數目的Datanode組成。Namenode是一箇中心服務器,負責管理文件系統的namespace和客戶端對文件的訪問。Datanode在集羣中通常是一個節點一個,負責管理節點上它們附帶的存儲。在內部,一個文件其實分紅一個或多個block,這些block存儲在Datanode集合裏。以下圖所示(HDFS體系結構圖):
二、 Hadoop MapReduce是一個使用簡易的軟件框架,基於它寫出來的應用程序可以運行在由上千個商用機器組成的大型集羣上,並以一種可靠容錯的方式並行處理上TB級別的數據集。
一個MapReduce做業(job)一般會把輸入的數據集切分爲若干獨立的數據塊,由 Map任務(task)以徹底並行的方式處理它們。框架會對Map的輸出先進行排序,而後把結果輸入給Reduce任務。一般做業的輸入和輸出都會被存儲在文件系統中。整個框架負責任務的調度和監控,以及從新執行已經失敗的任務。以下圖所示(Hadoop MapReduce處理流程圖):
三、 Hive是基於Hadoop的一個數據倉庫工具,處理能力強並且成本低廉。
主要特色:
存儲方式是將結構化的數據文件映射爲一張數據庫表。提供類SQL語言,實現完整的SQL查詢功能。能夠將SQL語句轉換爲MapReduce任務運行,十分適合數據倉庫的統計分析。
不足之處:
採用行存儲的方式(SequenceFile)來存儲和讀取數據。效率低:當要讀取數據表某一列數據時須要先取出全部數據而後再提取出某一列的數據,效率很低。同時,它還佔用較多的磁盤空間。
因爲以上的不足,有人(查禮博士)介紹了一種將分佈式數據處理系統中以記錄爲單位的存儲結構變爲以列爲單位的存儲結構,進而減小磁盤訪問數量,提升查詢處理性能。這樣,因爲相同屬性值具備相同數據類型和相近的數據特性,以屬性值爲單位進行壓縮存儲的壓縮比更高,能節省更多的存儲空間。以下圖所示(行列存儲的比較圖):
四、 HBase
HBase是一個分佈式的、面向列的開源數據庫,它不一樣於通常的關係數據庫,是一個適合於非結構化數據存儲的數據庫。另外一個不一樣的是HBase基於列的而不是基於行的模式。HBase使用和 BigTable很是相同的數據模型。用戶存儲數據行在一個表裏。一個數據行擁有一個可選擇的鍵和任意數量的列,一個或多個列組成一個ColumnFamily,一個Fmaily下的列位於一個HFile中,易於緩存數據。表是疏鬆的存儲的,所以用戶能夠給行定義各類不一樣的列。在HBase中數據按主鍵排序,同時表按主鍵劃分爲多個HRegion,以下圖所示(HBase數據表結構圖):
Ok,行文至此,看似洋洋灑灑近千里,但若給讀者形成閱讀上的負擔,則不是我本意。接下來的內容,我不會再引用諸多繁雜的專業術語,以給讀者內心上形成不良影響。
我再給出一副圖,算是對上文所說的hadoop框架及其組成部分作個總結,以下圖所示,即是hadoop的內部結構,咱們能夠看到,海量的數據交給hadoop處理後,在hadoop的內部中,正如上文所述:hadoop提供一個分佈式文件系統(HDFS)及分佈式數據庫(Hbase)用來存儲或部署到各個計算點上,最終在內部採起mapreduce的模式對其數據進行處理,而後輸出處理結果:
第二部分、淘寶海量數據產品技術架構解讀—學習海量數據處理經驗
在上面的本文的第一部分中,咱們已經對mapreduce模式及hadoop框架有了一個深刻而全面的瞭解。不過,若是一個東西,或者一個概念不放到實際應用中去,那麼你對這個理念永遠只是停留在理論以內,沒法向實踐邁進。
Ok,接下來,本文的第二部分,我們以淘寶的數據魔方技術架構爲依託,經過介紹淘寶的海量數據產品技術架構,來進一步學習和了解海量數據處理的經驗。
淘寶海量數據產品技術架構
以下圖2-1所示,便是淘寶的海量數據產品技術架構,我們下面要針對這個架構來一一剖析與解讀。
相信,看過本博客內其它文章的細心讀者,定會發現,圖2-1最初見於本博客內的此篇文章:從幾幅架構圖中偷得半點海量數據處理經驗之上,同時,此圖2-1最初發表於《程序員》8月刊,做者:朋春。
在此以前,有一點必須說明的是:本文下面的內容大都是參考自朋春先生的這篇文章:淘寶數據魔方技術架構解析所寫,我我的所做的工做是對這篇文章的一種解讀與關鍵技術和內容的抽取,覺得讀者更好的理解淘寶的海量數據產品技術架構。與此同時,還能展現我本身讀此篇的思路與感悟,順帶學習,何樂而不爲呢?。
Ok,不過,與本博客內以前的那篇文章(幾幅架構圖中偷得半點海量數據處理經驗)不一樣,本文接下來,要詳細闡述這個架構。我也作了很多準備工做(如把這圖2-1打印了下來,常常琢磨):
圖2-1 淘寶海量數據產品技術架構
好的,如上圖所示,咱們能夠看到,淘寶的海量數據產品技術架構,分爲如下五個層次,從上至下來看,它們分別是:數據源,計算層,存儲層,查詢層和產品層。咱們來一一瞭解這五層:
4 數據來源層。存放着淘寶各店的交易數據。在數據源層產生的數據,經過DataX,DbSync和Timetunel準實時的傳輸到下面第2點所述的「雲梯」。
5 計算層。在這個計算層內,淘寶採用的是hadoop集羣,這個集羣,咱們暫且稱之爲雲梯,是計算層的主要組成部分。在雲梯上,系統天天會對數據產品進行不一樣的mapreduce計算。
6 存儲層。在這一層,淘寶採用了兩個東西,一個使MyFox,一個是Prom。MyFox是基於MySQL的分佈式關係型數據庫的集羣,Prom是基於hadoop Hbase技術 的(讀者可別忘了,在上文第一部分中,我們介紹到了這個hadoop的組成部分之一,Hbase—在hadoop以內的一個分佈式的開源數據庫)的一個NoSQL的存儲集羣。
7 查詢層。在這一層中,有一個叫作glider的東西,這個glider是以HTTP協議對外提供restful方式的接口。數據產品經過一個惟一的URL來獲取到它想要的數據。同時,數據查詢便是經過MyFox來查詢的。下文將具體介紹MyFox的數據查詢過程。
8 產品層。簡單理解,不做過多介紹。
接下來,我們重點來了解第三層-存儲層中的MyFox與Prom,而後會稍帶分析下glide的技術架構,最後,再瞭解下緩存。文章即宣告結束。
咱們知道,關係型數據庫在咱們如今的工業生產中有着普遍的引用,它包括Oracle,MySQL、DB二、Sybase和SQL Server等等。
MyFOX
淘寶選擇了MySQL的MyISAM引擎做爲底層的數據存儲引擎。且爲了應對海量數據,他們設計了分佈式MySQL集羣的查詢代理層-MyFOX。
以下圖所示,是MySQL的數據查詢過程:
圖2-2 MyFOX的數據查詢過程
在MyFOX的每個節點中,存放着熱節點和冷節點兩種節點數據。顧名思義,熱節點存放着最新的,被訪問頻率較高的數據;冷節點,存放着相對而來比較舊的,訪問頻率比較低的數據。而爲了存儲這兩種節點數據,出於硬件條件和存儲成本的考慮,你固然會考慮選擇兩種不一樣的硬盤,來存儲這兩種訪問頻率不一樣的節點數據。以下圖所示:
圖2-3 MyFOX節點結構
「熱節點」,選擇每分鐘15000轉的SAS硬盤,按照一個節點兩臺機器來計算,單位數據的存儲成本約爲4.5W/TB。相對應地,「冷數據」咱們選擇了每分鐘7500轉的SATA硬盤,單碟上可以存放更多的數據,存儲成本約爲1.6W/TB。
Prom
出於文章篇幅的考慮,本文接下來再也不過多闡述這個Prom了。以下面兩幅圖所示,他們分別表示的是Prom的存儲結構以及Prom查詢過程:
圖2-4 Prom的存儲結構
圖2-5 Prom查詢過程
glide的技術架構
圖2-6 glider的技術架構
在這一層-查詢層中,淘寶主要是基於用中間層隔離先後端的理念而考慮。Glider這個中間層負責各個異構表之間的數據JOIN和UNION等計算,而且負責隔離前端產品和後端存儲,提供統一的數據查詢服務。
緩存
除了起到隔離先後端以及異構「表」之間的數據整合的做用以外,glider的另一個不容忽視的做用即是緩存管理。咱們有一點須瞭解,在特定的時間段內,咱們認爲數據產品中的數據是隻讀的,這是利用緩存來提升性能的理論基礎。
在上文圖2-6中咱們看到,glider中存在兩層緩存,分別是基於各個異構「表」(datasource)的二級緩存和整合以後基於獨立請求的一級緩存。除此以外,各個異構「表」內部可能還存在本身的緩存機制。
圖2-7 緩存控制體系
圖2-7向咱們展現了數據魔方在緩存控制方面的設計思路。用戶的請求中必定是帶了緩存控制的「命令」的,這包括URL中的query string,和HTTP頭中的「If-None-Match」信息。而且,這個緩存控制「命令」必定會通過層層傳遞,最終傳遞到底層存儲的異構「表」模塊。
緩存系統每每有兩個問題須要面對和考慮:緩存穿透與失效時的雪崩效應。
緩存穿透是指查詢一個必定不存在的數據,因爲緩存是不命中時被動寫的,而且出於容錯考慮,若是從存儲層查不到數據則不寫入緩存,這將致使這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。至於如何有效地解決緩存穿透問題,最多見的則是採用布隆過濾器(這個東西,在個人此篇文章中有介紹:),將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
而在數據魔方里,淘寶採用了一個更爲簡單粗暴的方法,若是一個查詢返回的數據爲空(無論是數據不存在,仍是系統故障),咱們仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過五分鐘。
二、緩存失效時的雪崩效應儘管對底層系統的衝擊很是可怕。但遺憾的是,這個問題目前並無很完美的解決方案。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線程(進程)寫,從而避免失效時大量的併發請求落到底層存儲系統上。
在數據魔方中,淘寶設計的緩存過時機制理論上可以將各個客戶端的數據失效時間均勻地分佈在時間軸上,必定程度上可以避免緩存同時失效帶來的雪崩效應。
本文參考:
基於雲計算的海量數據存儲模型,侯建等。
基於hadoop的海量日誌數據處理,王小森
基於hadoop的大規模數據處理系統,王麗兵。
淘寶數據魔方技術架構解析,朋春。
Hadoop做業調優參數整理及原理,guili。
十七道十七道海量數據處理面試題與Bit-map詳解
海量數據處理面試題與Bit-map詳解
做者:小橋流水,redfox66,July。
前言
本博客內曾經整理過有關海量數據處理的10道面試題(十道海量數據處理面試題與十個方法大總結),這次除了重複了以前的10道面試題以後,從新多整理了7道。僅做各位參考,不做它用。
同時,程序員編程藝術系列將從新開始創做,第十一章之後的部分題目來源將取自下文中的17道海量數據處理的面試題。由於,咱們以爲,下文的每一道面試題都值得從新思考,從新深究與學習。再者,編程藝術系列的前十章也是這麼來的。若您有任何問題或建議,歡迎不吝指正。謝謝。
第一部分、十五道海量數據處理面試題
1. 給定a、b兩個文件,各存放50億個url,每一個url各佔64字節,內存限制是4G,讓你找出a、b文件共同的url?
方案1:能夠估計每一個文件安的大小爲50G×64=320G,遠遠大於內存限制的4G。因此不可能將其徹底加載到內存中處理。考慮採起分而治之的方法。
1 遍歷文件a,對每一個url求取,而後根據所取得的值將url分別存儲到1000個小文件(記爲)中。這樣每一個小文件的大約爲300M。
2 遍歷文件b,採起和a相同的方式將url分別存儲到1000小文件中(記爲)。這樣處理後,全部可能相同的url都在對應的小文件()中,不對應的小文件不可能有相同的url。而後咱們只要求出1000對小文件中相同的url便可。
3 求每對小文件中相同的url時,能夠把其中一個小文件的url存儲到hash_set中。而後遍歷另外一個小文件的每一個url,看其是否在剛纔構建的hash_set中,若是是,那麼就是共同的url,存到文件裏面就能夠了。
方案2:若是容許有必定的錯誤率,能夠使用Bloom filter,4G內存大概能夠表示340億bit。將其中一個文件中的url使用Bloom filter映射爲這340億bit,而後挨個讀取另一個文件的url,檢查是否與Bloom filter,若是是,那麼該url應該是共同的url(注意會有必定的錯誤率)。
讀者反饋@crowgns:
4 hash後要判斷每一個文件大小,若是hash分的不均衡有文件較大,還應繼續hash分文件,換個hash算法第二次再分較大的文件,一直分到沒有較大的文件爲止。這樣文件標號能夠用A1-2表示(第一次hash編號爲1,文件較大因此參加第二次hash,編號爲2)
5 因爲1存在,第一次hash若是有大文件,不能用直接set的方法。建議對每一個文件都先用字符串天然順序排序,而後具備相同hash編號的(如都是1-3,而不能a編號是1,b編號是1-1和1-2),能夠直接從頭至尾比較一遍。對於層級不一致的,如a1,b有1-1,1-2-1,1-2-2,層級淺的要和層級深的每一個文件都比較一次,才能確認每一個相同的uri。
2. 有10個文件,每一個文件1G,每一個文件的每一行存放的都是用戶的query,每一個文件的query均可能重複。要求你按照query的頻度排序。
方案1:
6 順序讀取10個文件,按照hash(query)%10的結果將query寫入到另外10個文件(記爲)中。這樣新生成的文件每一個的大小大約也1G(假設hash函數是隨機的)。
7 找一臺內存在2G左右的機器,依次對用hash_map(query, query_count)來統計每一個query出現的次數。利用快速/堆/歸併排序按照出現次數進行排序。將排序好的query和對應的query_cout輸出到文件中。這樣獲得了10個排好序的文件(記爲)。
8 對這10個文件進行歸併排序(內排序與外排序相結合)。
方案2:
通常query的總量是有限的,只是重複的次數比較多而已,可能對於全部的query,一次性就能夠加入到內存了。這樣,咱們就能夠採用trie樹/hash_map等直接來統計每一個query出現的次數,而後按出現次數作快速/堆/歸併排序就能夠了
(讀者反饋@店小二:原文第二個例子中:「找一臺內存在2G左右的機器,依次對用hash_map(query, query_count)來統計每一個query出現的次數。」因爲query會重複,做爲key的話,應該使用hash_multimap 。hash_map 不容許key重複。@hywangw:店小二所述的確定是錯的,hash_map(query,query_count)是用來統計每一個query的出現次數 又不是存儲他們的值 出現一次 把count+1 就好了 用multimap幹什麼?多謝hywangw)。
方案3:
與方案1相似,但在作完hash,分紅多個文件後,能夠交給多個文件來處理,採用分佈式的架構來處理(好比MapReduce),最後再進行合併。
3. 有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。
方案1:順序讀文件中,對於每一個詞x,取,而後按照該值存到5000個小文件(記爲)中。這樣每一個文件大概是200k左右。若是其中的有的文件超過了1M大小,還能夠按照相似的方法繼續往下分,直到分解獲得的小文件的大小都不超過1M。對每一個小文件,統計每一個文件中出現的詞以及相應的頻率(能夠採用trie樹/hash_map等),並取出出現頻率最大的100個詞(能夠用含100個結點的最小堆),並把100詞及相應的頻率存入文件,這樣又獲得了5000個文件。下一步就是把這5000個文件進行歸併(相似與歸併排序)的過程了。
4. 海量日誌數據,提取出某日訪問百度次數最多的那個IP。
方案1:首先是這一天,而且是訪問百度的日誌中的IP取出來,逐個寫入到一個大文件中。注意到IP是32位的,最多有2^32個IP。一樣能夠採用映射的方法,好比模1000,把整個大文件映射爲1000個小文件,再找出每一個小文中出現頻率最大的IP(能夠採用hash_map進行頻率統計,而後再找出頻率最大的幾個)及相應的頻率。而後再在這1000個最大的IP中,找出那個頻率最大的IP,即爲所求。
5. 在2.5億個整數中找出不重複的整數,內存不足以容納這2.5億個整數。
方案1:採用2-Bitmap(每一個數分配2bit,00表示不存在,01表示出現一次,10表示屢次,11無心義)進行,共需內存2^32*2bit=1GB內存,還能夠接受。而後掃描這2.5億個整數,查看Bitmap中相對應位,若是是00變01,01變10,10保持不變。所描完過後,查看bitmap,把對應位是01的整數輸出便可。
方案2:也可採用上題相似的方法,進行劃分小文件的方法。而後在小文件中找出不重複的整數,並排序。而後再進行歸併,注意去除重複的元素。
6. 海量數據分佈在100臺電腦中,想個辦法高效統計出這批數據的TOP10。
方案1:
9 在每臺電腦上求出TOP10,能夠採用包含10個元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆)。好比求TOP10大,咱們首先取前10個元素調整成最小堆,若是發現,而後掃描後面的數據,並與堆頂元素比較,若是比堆頂元素大,那麼用該元素替換堆頂,而後再調整爲最小堆。最後堆中的元素就是TOP10大。
10 求出每臺電腦上的TOP10後,而後把這100臺電腦上的TOP10組合起來,共1000個數據,再利用上面相似的方法求出TOP10就能夠了。
(更多能夠參考:第三章、尋找最小的k個數,以及第三章續、Top K算法問題的實現)
讀者反饋@QinLeopard:
第6題的方法中,是否是不能保證每一個電腦上的前十條,確定包含最後頻率最高的前十條呢?
好比說第一個文件中:A(4), B(5), C(6), D(3)
第二個文件中:A(4),B(5),C(3),D(6)
第三個文件中: A(6), B(5), C(4), D(3)
若是要選Top(1), 選出來的結果是A,但結果應該是B。
@July:我想,這位讀者可能沒有明確提議。本題目中的TOP10是指最大的10個數,而不是指出現頻率最多的10個數。但若是說,如今有另一提,要你求頻率最多的 10個,至關於求訪問次數最多的10個IP地址那道題,便是本文中上面的第4題。特此說明。
7. 怎麼在海量數據中找出重複次數最多的一個?
方案1:先作hash,而後求模映射爲小文件,求出每一個小文件中重複次數最多的一個,並記錄重複次數。而後找出上一步求出的數據中重複次數最多的一個就是所求(具體參考前面的題)。
8. 上千萬或上億數據(有重複),統計其中出現次數最多的錢N個數據。
方案1:上千萬或上億的數據,如今的機器的內存應該能存下。因此考慮採用hash_map/搜索二叉樹/紅黑樹等來進行統計次數。而後就是取出前N個出現次數最多的數據了,能夠用第6題提到的堆機制完成。
9. 1000萬字符串,其中有些是重複的,須要把重複的所有去掉,保留沒有重複的字符串。請怎麼設計和實現?
方案1:這題用trie樹比較合適,hash_map也應該能行。
10. 一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
方案1:這題是考慮時間效率。用trie樹統計每一個詞出現的次數,時間複雜度是O(n*le)(le表示單詞的平準長度)。而後是找出出現最頻繁的前10個詞,能夠用堆來實現,前面的題中已經講到了,時間複雜度是O(n*lg10)。因此總的時間複雜度,是O(n*le)與O(n*lg10)中較大的哪個。
11. 一個文本文件,找出前10個常常出現的詞,但此次文件比較長,說是上億行或十億行,總之沒法一次讀入內存,問最優解。
方案1:首先根據用hash並求模,將文件分解爲多個小文件,對於單個文件利用上題的方法求出每一個文件件中10個最常出現的詞。而後再進行歸併處理,找出最終的10個最常出現的詞。
12. 100w個數中找出最大的100個數。
· 方案1:採用局部淘汰法。選取前100個元素,並排序,記爲序列L。而後一次掃描剩餘的元素x,與排好序的100個元素中最小的元素比,若是比這個最小的要大,那麼把這個最小的元素刪除,並把x利用插入排序的思想,插入到序列L中。依次循環,知道掃描了全部的元素。複雜度爲O(100w*100)。
· 方案2:採用快速排序的思想,每次分割以後只考慮比軸大的一部分,知道比軸大的一部分在比100多的時候,採用傳統排序算法排序,取前100個。複雜度爲O(100w*100)。
· 方案3:在前面的題中,咱們已經提到了,用一個含100個元素的最小堆完成。複雜度爲O(100w*lg100)。
13. 尋找熱門查詢:
搜索引擎會經過日誌文件把用戶每次檢索使用的全部檢索串都記錄下來,每一個查詢串的長度爲1-255字節。假設目前有一千萬個記錄,這些查詢串的重複讀比較高,雖然總數是1千萬,可是若是去除重複和,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。
(1) 請描述你解決這個問題的思路;
(2) 請給出主要的處理流程,算法,以及算法的複雜度。
方案1:採用trie樹,關鍵字域存該查詢串出現的次數,沒有出現爲0。最後用10個元素的最小推來對出現頻率進行排序。
關於此問題的詳細解答,請參考此文的第3.1節:第三章續、Top K算法問題的實現。
14. 一共有N個機器,每一個機器上有N個數。每一個機器最多存O(N)個數並對它們操做。如何找到N^2個數中的中數?
方案1:先大致估計一下這些數的範圍,好比這裏假設這些數都是32位無符號整數(共有2^32個)。咱們把0到2^32-1的整數劃分爲N個範圍段,每一個段包含(2^32)/N個整數。好比,第一個段位0到2^32/N-1,第二段爲(2^32)/N到(2^32)/N-1,…,第N個段爲(2^32)(N-1)/N到2^32-1。而後,掃描每一個機器上的N個數,把屬於第一個區段的數放到第一個機器上,屬於第二個區段的數放到第二個機器上,…,屬於第N個區段的數放到第N個機器上。注意這個過程每一個機器上存儲的數應該是O(N)的。下面咱們依次統計每一個機器上數的個數,一次累加,直到找到第k個機器,在該機器上累加的數大於或等於(N^2)/2,而在第k-1個機器上的累加數小於(N^2)/2,並把這個數記爲x。那麼咱們要找的中位數在第k個機器中,排在第(N^2)/2-x位。而後咱們對第k個機器的數排序,並找出第(N^2)/2-x個數,即爲所求的中位數的複雜度是O(N^2)的。
方案2:先對每臺機器上的數進行排序。排好序後,咱們採用歸併排序的思想,將這N個機器上的數歸併起來獲得最終的排序。找到第(N^2)/2個即是所求。複雜度是O(N^2*lgN^2)的。
15. 最大間隙問題
給定n個實數,求着n個實數在實軸上向量2個數之間的最大差值,要求線性的時間算法。
方案1:最早想到的方法就是先對這n個數據進行排序,而後一遍掃描便可肯定相鄰的最大間隙。但該方法不能知足線性時間的要求。故採起以下方法:
11 找到n個數據中最大和最小數據max和min。
12 用n-2個點等分區間[min, max],即將[min, max]等分爲n-1個區間(前閉後開區間),將這些區間看做桶,編號爲,且桶i 的上界和桶i+1的下屆相同,即每一個桶的大小相同。每一個桶的大小爲:。實際上,這些桶的邊界構成了一個等差數列(首項爲min,公差爲),且認爲將min放入第一個桶,將max放入第n-1個桶。
13 將n個數放入n-1個桶中:將每一個元素x[i] 分配到某個桶(編號爲index),其中,並求出分到每一個桶的最大最小數據。
14 最大間隙:除最大最小數據max和min之外的n-2個數據放入n-1個桶中,由抽屜原理可知至少有一個桶是空的,又由於每一個桶的大小相同,因此最大間隙不會在同一桶中出現,必定是某個桶的上界和睦候某個桶的下界之間隙,且該量筒之間的桶(即使好在該連個便好之間的桶)必定是空桶。也就是說,最大間隙在桶i的上界和桶j的下界之間產生j>=i+1。一遍掃描便可完成。
16. 將多個集合合併成沒有交集的集合
給定一個字符串的集合,格式如:。要求將其中交集不爲空的集合合併,要求合併完成的集合之間無交集,例如上例應輸出。
(1) 請描述你解決這個問題的思路;
(2) 給出主要的處理流程,算法,以及算法的複雜度;
(3) 請描述可能的改進。
方案1:採用並查集。首先全部的字符串都在單獨的並查集中。而後依掃描每一個集合,順序合併將兩個相鄰元素合併。例如,對於,首先查看aaa和bbb是否在同一個並查集中,若是不在,那麼把它們所在的並查集合並,而後再看bbb和ccc是否在同一個並查集中,若是不在,那麼也把它們所在的並查集合並。接下來再掃描其餘的集合,當全部的集合都掃描完了,並查集表明的集合即是所求。複雜度應該是O(NlgN)的。改進的話,首先能夠記錄每一個節點的根結點,改進查詢。合併的時候,能夠把大的和小的進行合,這樣也減小複雜度。
17. 最大子序列與最大子矩陣問題
數組的最大子序列問題:給定一個數組,其中元素有正,也有負,找出其中一個連續子序列,使和最大。
方案1:這個問題能夠動態規劃的思想解決。設b[i]表示以第i個元素a[i]結尾的最大子序列,那麼顯然。基於這一點能夠很快用代碼實現。
最大子矩陣問題:給定一個矩陣(二維數組),其中數據有大有小,請找一個子矩陣,使得子矩陣的和最大,並輸出這個和。
方案2:能夠採用與最大子序列相似的思想來解決。若是咱們肯定了選擇第i列和第j列之間的元素,那麼在這個範圍內,其實就是一個最大子序列問題。如何肯定第i列和第j列能夠詞用暴搜的方法進行。
第二部分、海量數據處理之Bti-map詳解
Bloom Filter已在上一篇文章海量數據處理之Bloom Filter詳解中予以詳細闡述,本文接下來着重闡述Bit-map。有任何問題,歡迎不吝指正。
什麼是Bit-map
所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key便是該元素。因爲採用了Bit爲單位來存儲數據,所以在存儲空間方面,能夠大大節省。
若是說了這麼多還沒明白什麼是Bit-map,那麼咱們來看一個具體的例子,假設咱們要對0-7內的5個元素(4,7,2,5,3)排序(這裏假設這些元素沒有重複)。那麼咱們就能夠採用Bit-map的方法來達到排序的目的。要表示8個數,咱們就只須要8個Bit(1Bytes),首先咱們開闢1Byte的空間,將這些空間的全部Bit位都置爲0(以下圖:)
而後遍歷這5個元素,首先第一個元素是4,那麼就把4對應的位置爲1(能夠這樣操做 p+(i/8)|(0×01<<(i%8)) 固然了這裏的操做涉及到Big-ending和Little-ending的狀況,這裏默認爲Big-ending),由於是從零開始的,因此要把第五位置爲一(以下圖):
而後再處理第二個元素7,將第八位置爲1,,接着再處理第三個元素,一直到最後處理完全部的元素,將相應的位置爲1,這時候的內存的Bit位的狀態以下:
而後咱們如今遍歷一遍Bit區域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的。下面的代碼給出了一個BitMap的用法:排序。
15 //定義每一個Byte中有8個Bit位
16 #include <memory.h>
17 #define BYTESIZE 8
18 void SetBit(char *p, int posi)
19 {
20 for(int i=0; i < (posi/BYTESIZE); i++)
21 {
22 p++;
23 }
24
25 *p = *p|(0x01<<(posi%BYTESIZE));//將該Bit位賦值1
26 return;
27 }
28
29 void BitMapSortDemo()
30 {
31 //爲了簡單起見,咱們不考慮負數
32 int num[] = {3,5,2,10,6,12,8,14,9};
33
34 //BufferLen這個值是根據待排序的數據中最大值肯定的
35 //待排序中的最大值是14,所以只須要2個Bytes(16個Bit)
36 //就能夠了。
37 const int BufferLen = 2;
38 char *pBuffer = new char[BufferLen];
39
40 //要將全部的Bit位置爲0,不然結果不可預知。
41 memset(pBuffer,0,BufferLen);
42 for(int i=0;i<9;i++)
43 {
44 //首先將相應Bit位上置爲1
45 SetBit(pBuffer,num[i]);
46 }
47
48 //輸出排序結果
49 for(int i=0;i<BufferLen;i++)//每次處理一個字節(Byte)
50 {
51 for(int j=0;j<BYTESIZE;j++)//處理該字節中的每一個Bit位
52 {
53 //判斷該位上是不是1,進行輸出,這裏的判斷比較笨。
54 //首先獲得該第j位的掩碼(0x01<<j),將內存區中的
55 //位和此掩碼做與操做。最後判斷掩碼是否和處理後的
56 //結果相同
57 if((*pBuffer&(0x01<<j)) == (0x01<<j))
58 {
59 printf("%d ",i*BYTESIZE + j);
60 }
61 }
62 pBuffer++;
63 }
64 }
65
66 int _tmain(int argc, _TCHAR* argv[])
67 {
68 BitMapSortDemo();
69 return 0;
70 }
可進行數據的快速查找,判重,刪除,通常來講數據範圍是int的10倍如下
基本原理及要點
使用bit數組來表示某些元素是否存在,好比8位電話號碼
擴展
Bloom filter能夠看作是對bit-map的擴展(關於Bloom filter,請參見:海量數據處理之Bloom filter詳解)。
問題實例
1)已知某個文件內包含一些電話號碼,每一個號碼爲8位數字,統計不一樣號碼的個數。
8位最多99 999 999,大概須要99m個bit,大概10幾m字節的內存便可。 (能夠理解爲從0-99 999 999的數字,每一個數字對應一個Bit位,因此只須要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了全部的8位數的電話)
2)2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
將bit-map擴展一下,用2bit表示一個數便可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍歷這些數的時候,若是對應位置的值是0,則將其置爲1;若是是1,將其置爲2;若是是2,則保持不變。或者咱們不用2bit來進行表示,咱們用兩個bit-map便可模擬實現這個2bit-map,都是同樣的道理。