Hadoop的那些事兒
文/張巡
在說Hadoop之前,作為一個鐵桿粉絲先粉一下Google。Google的偉大之處不僅在于它建立了一個強悍的搜索引擎,它還創造了幾項革命性的技術:GFS,MapReduce,BigTable,即所謂的Google三駕馬車。Google雖然沒有公布這幾項技術的實現代碼,但它發表了詳細的設計論文,這給業界帶來了新鮮氣息,很快就出現了類似于Google三駕馬車的開源實現,Hadoop就是其中的一個。
關于MapReduce
Hadoop說起來很簡單,一個存儲系統(HDFS),一個計算系統(MapReduce)。僅此而已。模型雖然簡單,但我覺得它的精妙之處也就在這里。目前,通過提高CPU主頻來提升計算性能的時代已經結束了,因此并行計算、分布式計算在業界發展了起來,但是這也往往意味著復雜的設計與實現,如果能找到一種方法,只需要寫簡單的程序就能實現分布式計算,那就太好了。
我們可以回想下當初做的課堂作業,它可能是一個處理數據的程序,沒有多線程,沒有進程間通信,數據輸入都是來自鍵盤(stdin),處理完數據之后打印到屏幕(stdout),這時的程序非常簡單。后來我們學習了多線程、內存管理、設計模式,寫出的程序越來越大,也越來越復雜。但是當學習使用Hadoop時,我們發現又回到了最初,盡管底層是一個巨大的集群,可是我們操作文件時就像在本地一樣簡單,寫MapReduce程序時也只需要在單線程里實現數據處理。
Hadoop就是這樣一個東西,簡單的文件系統(HDFS),簡單的計算模型(MapReduce)。這其中,我覺得HDFS是很自然的東西,如果我們自己實現一個,也很可能是這樣的。但是仔細琢磨下MapReduce,會發現它似乎是個新奇事物,不像HDFS的界面那樣通俗易懂老少皆宜。MapReduce雖然模型簡單,卻是一個新的、不廣為人所知的概念。比如說,如果說要簡化計算,為何要做成Map和Reduce兩個階段,而不是一個函數足矣呢?另外,它也不符合我們耳熟能詳的那些設計模式。一句話,MapReduce實在太另類了。
Hadoop的思想來源于Google的幾篇論文,Google的那篇MapReduce論文里說:Our abstraction is inspired by the map and reduce primitives present in Lisp and many other functional languages。這句話提到了MapReduce思想的淵源,大致意思是,MapReduce的靈感來源于函數式語言(比如Lisp)中的內置函數map和reduce。函數式語言也算是陽春白雪了,離我們普通開發者總是很遠。簡單來說,在函數式語言里,map表示對一個列表(List)中的每個元素做計算,reduce表示對一個列表中的每個元素做迭代計算。它們具體的計算是通過傳入的函數來實現的,map和reduce提供的是計算的框架。不過從這樣的解釋到現實中的MapReduce還太遠,仍然需要一個跳躍。再仔細看,reduce既然能做迭代計算,那就表示列表中的元素是相關的,比如我想對列表中的所有元素做相加求和,那么列表中至少都應該是數值吧。而map是對列表中每個元素做單獨處理的,這表示列表中可以是雜亂無章的數據。這樣看來,就有點聯系了。在MapReduce里,Map處理的是原始數據,自然是雜亂無章的,每條數據之間互相沒有關系;到了Reduce階段,數據是以key后面跟著若干個value來組織的,這些value有相關性,至少它們都在一個key下面,于是就符合函數式語言里map和reduce的基本思想了。
這樣我們就可以把MapReduce理解為,把一堆雜亂無章的數據按照某種特征歸納起來,然后處理并得到最后的結果。Map面對的是雜亂無章的互不相關的數據,它解析每個數據,從中提取出key和value,也就是提取了數據的特征。經過MapReduce的Shuffle階段之后,在Reduce階段看到的都是已經歸納好的數據了,在此基礎上我們可以做進一步的處理以便得到結果。這就回到了最初,終于知道MapReduce為何要這樣設計。
Hadoop, More and More
Hadoop/MapReduce的Job是一個曇花一現的東西,它僅僅是一個計算過程而已,所有數據都是從HDFS來,到HDFS去。當最后一個Reduce完成之后,整個Job就消失了,只在歷史日志中還保存著它曾經存在的證據。
Hadoop中的節點是對等的,因此每個節點都是可替代的。也因此,JobTracker可以把任務分配到任何一個節點執行,而不影響最后的產出結果。如果不是這樣,就破壞了Hadoop的對等性。對等性可以說是Hadoop擴展能力、容錯能力的基礎,它意味著計算不再局限于單個節點的能力。當然事情總有兩面性,對等性造成的結果是,如果我們想讓某些節點做特殊的事情,會發現很困難。
就像很多分布式系統中的文件、對象一樣,Hadoop對數據的操作是原子性的,一個Job跑完之后,最終的數據是在一瞬間呈現的,如果Job失敗了,則什么都沒有,連部分數據也得不到。由于Job中的task都是并行的,因此這里適用于木桶理論,最后一個完成的那個task決定了整個Job完成的時刻。所以如果Hadoop發現某些Task特別慢,會在其它節點運行這些Task的副本,當其中某個副本完成后,Hadoop將Kill掉其余的副本,這也是基于對等性的一個應用,使得那些慢的節點不會拖慢Job。如果某個task在重試若干次之后仍然失敗了,那么整個Job宣告失敗。
Hadoop包括HDFS、MapReduce,此外還有Hbase、Hive等,普通的應用大概是存儲海量的數據,并進行批量的處理、數據挖掘等,不過也有些比較巧妙的實際應用,比如用Hadoop搭建HTTP下載服務器,或FTP服務器等,還有人用Hadoop搭建視頻點播服務器。我覺得Hadoop對這些應用的最大價值是可以降低硬件成本,以前也許需要購買昂貴的硬件,現在購買廉價的服務器就可以實現。不過,做這些應用都需要對Hadoop做定制,修改代碼啥的,似乎還沒有整體的解決方案。
Hadoop是Java寫的。可能有不少人會想,如果Hadoop是用C或C++寫的,會不會更快些?關于這個問題網上也有不少討論,簡單地說就是,不會更快。而且我覺得需要強調的一點是,當面對Hadoop要面對的問題域時,編程語言不是首先要考慮的問題,或者說,依賴于具體的實現方案。Hadoop要處理的是大批量數據,它強調的是吞吐量,當整個集群跑起來之后,系統的瓶頸往往是在磁盤IO和網絡IO,這種情況下,用C/C++實現Hadoop不見得比Java實現性能更高。相反,由于Java語言的嚴謹、統一和一些高級特性,用Java實現Hadoop對開發者而言會更容易。一般來講,無論是用C++寫還是用Java寫,當系統發展較成熟時,都已經經過了相當的優化,這時系統的瓶頸往往不在于軟件了,而在于硬件的限制。當然,這并非意味著目前Hadoop已經實現得很好了,Hadoop還有很大的優化空間,它的開發進程依然火爆,很多人在優化Hadoop,還包括用C++來改寫Hadoop的部分代碼的。另外,對于超大的集群(比如幾千臺服務器),調度器的優化也很關鍵,否則它就可能成為整個集群的瓶頸。我想這就是Hadoop雖然已經廣泛應用,但是版本號依然還是零點幾的原因吧。
雖說Hadoop的模型很簡單,但是開發Hadoop的Job并不容易,在業務邏輯與HadoopAPI之間,我們還必須寫大量的膠水代碼,這無異于在WindowsSDK基礎上寫一個畫圖板程序,或者在Linux系統調用基礎上寫一個支持多用戶在線的聊天服務器,這些似乎都不難,但是比較繁瑣,需要堆砌大量代碼,寫完一個之后,你大概不會再寫第二個。我把這種情況稱為用匯編語言寫程序,也就是說,你面對的是業務問題,卻必須不遺巨細與機器打交道。
面對上述困境,人們想出了很多解決辦法,開發出高級語言來屏蔽底層的細節,讓開發過程更加簡單,我把這種情況稱為用腳本寫程序,可以很快的修改&調試。其中Facebook開發了Hive,這是類似于SQL的腳本語言,開發者基本上只要面對自己的業務數據,就能寫出Hive腳本,雖然有一定的入門門檻,但是比起用HadoopAPI寫程序,可以稱得上是巨簡單了。另外Yahoo開發了PIG-latin,也是類SQL的腳本語言,Hive和PIG都有很廣泛的應用