制解析:從HDFS到Y(jié)ARN的分布式存儲與計(jì)算實(shí)踐)
1. 從一個(gè)“數(shù)據(jù)裝不下”的真實(shí)困境說起我第一次真正意識到需要Hadoop是在處理一批日志數(shù)據(jù)的時(shí)候。當(dāng)時(shí)手頭有一臺配置還算不錯的服務(wù)器內(nèi)存64GB硬盤4TB自認(rèn)為“夠用了”。結(jié)果數(shù)據(jù)量一上來單是原始日志就超過了8TB而且每天還在以幾百GB的速度增長。更麻煩的是這些數(shù)據(jù)不是簡單地存起來就行還要做清洗、聚合、統(tǒng)計(jì)分析。單機(jī)跑一個(gè)腳本光讀取就花了幾個(gè)小時(shí)中間一旦內(nèi)存溢出整個(gè)任務(wù)就得從頭再來。那時(shí)候我試過垂直擴(kuò)展的思路——加內(nèi)存、換更快的硬盤、優(yōu)化代碼。短期確實(shí)有效但成本增長曲線太陡了。從64GB加到256GB內(nèi)存價(jià)格翻了好幾倍而數(shù)據(jù)量增長卻是線性的、持續(xù)的。更關(guān)鍵的是單機(jī)的可靠性問題沒有解決硬盤壞了怎么辦任務(wù)跑到一半機(jī)器宕機(jī)怎么辦這些問題不是靠“買更好的硬件”就能繞過去的。Hadoop就是在這個(gè)背景下進(jìn)入我的視野的。它的核心思路很樸素既然一臺機(jī)器搞不定那就用一群普通機(jī)器來分擔(dān)。這個(gè)思路聽起來簡單但真正落地需要解決一系列問題——數(shù)據(jù)怎么分布計(jì)算怎么調(diào)度某臺機(jī)器掛了怎么保證任務(wù)繼續(xù)這些問題的答案構(gòu)成了Hadoop整個(gè)技術(shù)體系的基礎(chǔ)。這篇文章適合那些正在面對數(shù)據(jù)量增長、單機(jī)處理越來越吃力、想了解分布式存儲與計(jì)算到底怎么回事的從業(yè)者。我不會堆砌概念而是從實(shí)際使用角度把Hadoop的核心機(jī)制、組件分工、部署要點(diǎn)和常見坑講清楚。即使你之前只聽過“大數(shù)據(jù)”這個(gè)詞也能看懂它到底在解決什么問題。2. Hadoop到底解決了哪幾類問題2.1 存儲問題單機(jī)硬盤裝不下也讀不快Hadoop的第一個(gè)核心能力是分布式存儲對應(yīng)組件叫HDFSHadoop Distributed File System。它的基本邏輯是把一個(gè)大文件切成固定大小的數(shù)據(jù)塊默認(rèn)128MB然后把這些塊分散存到集群中不同的機(jī)器上。每臺機(jī)器只存一部分合起來就是完整的數(shù)據(jù)。這樣做的好處很直接。假設(shè)你有10TB數(shù)據(jù)集群有10臺機(jī)器每臺只需要存1TB左右。如果以后數(shù)據(jù)漲到20TB加機(jī)器就行不需要換掉現(xiàn)有設(shè)備。讀取的時(shí)候多臺機(jī)器可以同時(shí)提供數(shù)據(jù)吞吐量是單機(jī)的數(shù)倍甚至數(shù)十倍。但這里有個(gè)關(guān)鍵設(shè)計(jì)HDFS默認(rèn)會把每個(gè)數(shù)據(jù)塊存三份分別放在不同機(jī)器上。這意味著實(shí)際存儲開銷是原始數(shù)據(jù)的三倍。很多人第一次聽到這個(gè)會覺得“太浪費(fèi)了”但這是為了保證可靠性。普通商用硬盤的故障率并不低三副本機(jī)制讓系統(tǒng)在任意一到兩臺機(jī)器同時(shí)故障時(shí)數(shù)據(jù)仍然可用。這是用存儲成本換可靠性和可用性在實(shí)際生產(chǎn)中是劃算的。2.2 計(jì)算問題單機(jī)算得慢還容易中途失敗有了分布式存儲接下來就是分布式計(jì)算對應(yīng)組件是MapReduce。它的核心思想是把一個(gè)大計(jì)算任務(wù)拆成很多小任務(wù)分發(fā)到多臺機(jī)器上并行執(zhí)行最后再把結(jié)果匯總。MapReduce的編程模型只有兩個(gè)主要階段Map和Reduce。Map階段負(fù)責(zé)“分解”把原始數(shù)據(jù)映射成鍵值對Reduce階段負(fù)責(zé)“匯總”把相同鍵的值合并處理。中間還有一個(gè)Shuffle階段負(fù)責(zé)把Map的輸出按照鍵重新分發(fā)到不同的Reduce任務(wù)上。這個(gè)模型看起來簡單但威力在于你只需要寫Map和Reduce的邏輯至于任務(wù)怎么切分、怎么調(diào)度、某臺機(jī)器失敗了怎么重試框架全部幫你處理。這是Hadoop對開發(fā)者最大的價(jià)值——把分布式編程的復(fù)雜性封裝起來讓普通人也能寫出能在集群上跑的程序。2.3 資源管理問題多任務(wù)怎么共享一個(gè)集群早期Hadoop中MapReduce既負(fù)責(zé)計(jì)算也負(fù)責(zé)資源調(diào)度導(dǎo)致集群只能跑MapReduce任務(wù)。后來引入了YARNYet Another Resource Negotiator把資源管理和計(jì)算框架分離開。YARN負(fù)責(zé)管理集群的CPU、內(nèi)存等資源任何計(jì)算框架MapReduce、Spark、Flink等都可以向YARN申請資源來運(yùn)行自己的任務(wù)。這個(gè)變化的意義很大。它讓一個(gè)Hadoop集群可以同時(shí)運(yùn)行多種類型的計(jì)算任務(wù)提高了集群利用率。比如白天跑批處理分析晚上跑機(jī)器學(xué)習(xí)訓(xùn)練資源統(tǒng)一由YARN調(diào)配不需要為每種任務(wù)單獨(dú)維護(hù)一套集群。2.4 三類問題的關(guān)系與邊界HDFS、MapReduce、YARN這三者構(gòu)成了Hadoop的核心。HDFS解決“數(shù)據(jù)放哪”MapReduce解決“怎么算”YARN解決“誰來分配資源”。它們之間的關(guān)系可以用一個(gè)簡單類比理解HDFS是倉庫MapReduce是流水線YARN是調(diào)度中心。但需要明確的是Hadoop并不是萬能的。它擅長的是大規(guī)模批處理場景——數(shù)據(jù)量大、對延遲不敏感、任務(wù)可以分解并行。如果你需要毫秒級響應(yīng)的實(shí)時(shí)查詢Hadoop并不合適那是內(nèi)存數(shù)據(jù)庫或流處理框架的領(lǐng)域。理解這個(gè)邊界比盲目上Hadoop更重要。3. HDFS的讀寫流程與副本機(jī)制拆解3.1 一次寫入請求經(jīng)歷了什么當(dāng)客戶端要往HDFS寫一個(gè)文件時(shí)背后發(fā)生的事情比想象中復(fù)雜??蛻舳耸紫嚷?lián)系NameNodeHDFS的主節(jié)點(diǎn)請求創(chuàng)建文件。NameNode會檢查權(quán)限、確認(rèn)文件不存在然后在元數(shù)據(jù)中記錄這個(gè)新文件但此時(shí)并不分配具體的數(shù)據(jù)塊??蛻舳碎_始寫數(shù)據(jù)時(shí)會把文件切分成一個(gè)個(gè)數(shù)據(jù)塊。每寫一個(gè)塊客戶端向NameNode申請?jiān)搲K應(yīng)該存到哪些DataNode上。NameNode根據(jù)副本放置策略返回一組DataNode列表。默認(rèn)策略是第一個(gè)副本放在客戶端所在機(jī)器如果客戶端在集群內(nèi)第二個(gè)副本放在不同機(jī)架的機(jī)器上第三個(gè)副本放在與第二個(gè)副本同機(jī)架但不同機(jī)器上??蛻舳四玫搅斜砗箝_始向第一個(gè)DataNode傳輸數(shù)據(jù)。第一個(gè)DataNode收到數(shù)據(jù)后會同時(shí)轉(zhuǎn)發(fā)給第二個(gè)DataNode第二個(gè)再轉(zhuǎn)發(fā)給第三個(gè)。這種流水線式的傳輸方式比客戶端分別向三個(gè)節(jié)點(diǎn)發(fā)送效率更高因?yàn)槔昧嗣颗_機(jī)器的網(wǎng)絡(luò)帶寬。注意如果寫入過程中某個(gè)DataNode失敗客戶端會通知NameNodeNameNode會安排新的DataNode繼續(xù)完成副本寫入。整個(gè)過程對客戶端是透明的不需要手動干預(yù)。3.2 讀取時(shí)如何找到數(shù)據(jù)讀取流程相對簡單一些??蛻舳讼騈ameNode請求讀取某個(gè)文件NameNode返回該文件所有數(shù)據(jù)塊的位置信息每個(gè)塊對應(yīng)的DataNode列表??蛻舳四玫搅斜砗髸?yōu)先選擇距離自己最近的DataNode讀取數(shù)據(jù)。如果某個(gè)DataNode讀取失敗客戶端會自動切換到下一個(gè)副本所在的DataNode。這里有個(gè)實(shí)際使用中的經(jīng)驗(yàn)HDFS的讀取性能很大程度上取決于客戶端與DataNode的網(wǎng)絡(luò)距離。如果客戶端在集群外所有讀取都要走網(wǎng)絡(luò)速度會明顯慢于集群內(nèi)讀取。所以在實(shí)際部署中計(jì)算任務(wù)盡量和存儲放在同一個(gè)集群內(nèi)利用數(shù)據(jù)本地性Data Locality來減少網(wǎng)絡(luò)傳輸。3.3 副本機(jī)制背后的權(quán)衡三副本是HDFS的默認(rèn)配置但不是唯一選擇。你可以根據(jù)數(shù)據(jù)的重要程度調(diào)整副本數(shù)。比如臨時(shí)中間結(jié)果可以設(shè)為1副本節(jié)省存儲空間核心業(yè)務(wù)數(shù)據(jù)可以設(shè)為3副本甚至更高。但副本數(shù)不是越多越好。每增加一個(gè)副本寫入時(shí)需要傳輸?shù)臄?shù)據(jù)量就增加一份存儲開銷也線性增長。在實(shí)際集群中三副本是一個(gè)平衡點(diǎn)既能容忍一定數(shù)量的機(jī)器故障又不會讓存儲成本失控。還有一個(gè)容易被忽略的點(diǎn)副本的放置策略直接影響可靠性。如果所有副本都放在同一個(gè)機(jī)架上一旦機(jī)架斷電或交換機(jī)故障數(shù)據(jù)就不可用了。HDFS的默認(rèn)策略通過跨機(jī)架放置來避免這個(gè)問題但在小規(guī)模集群中比如只有幾臺機(jī)器機(jī)架感知可能配置不完整需要額外注意。3.4 NameNode的單點(diǎn)問題與解決方案NameNode是HDFS的核心它存儲了整個(gè)文件系統(tǒng)的元數(shù)據(jù)文件名、目錄結(jié)構(gòu)、塊位置等。如果NameNode掛了整個(gè)HDFS就無法訪問。這是HDFS架構(gòu)中最大的單點(diǎn)風(fēng)險(xiǎn)。Hadoop提供了兩種應(yīng)對方案。第一種是SecondaryNameNode它定期合并NameNode的編輯日志和鏡像文件減輕NameNode的啟動壓力。但要注意SecondaryNameNode并不是熱備它不能自動接管NameNode的工作。第二種是NameNode HAHigh Availability通過配置兩個(gè)NameNode一個(gè)Active一個(gè)Standby來實(shí)現(xiàn)自動故障切換。Standby NameNode實(shí)時(shí)同步Active的元數(shù)據(jù)變化當(dāng)Active故障時(shí)Standby可以快速接管。這是生產(chǎn)環(huán)境中的標(biāo)準(zhǔn)做法。提示即使配置了HANameNode的元數(shù)據(jù)仍然需要定期備份。元數(shù)據(jù)丟失意味著整個(gè)文件系統(tǒng)的數(shù)據(jù)都無法訪問這是災(zāi)難性的。4. MapReduce編程模型的實(shí)際運(yùn)作細(xì)節(jié)4.1 一個(gè)WordCount程序的完整執(zhí)行鏈路WordCount是MapReduce的“Hello World”但它的執(zhí)行過程能說明很多問題。假設(shè)你要統(tǒng)計(jì)一個(gè)10GB文本文件中每個(gè)單詞出現(xiàn)的次數(shù)。首先Hadoop會把輸入文件切分成多個(gè)分片InputSplit每個(gè)分片默認(rèn)對應(yīng)一個(gè)HDFS數(shù)據(jù)塊。每個(gè)分片會分配給一個(gè)Map任務(wù)。Map任務(wù)逐行讀取分片內(nèi)容對每一行按空格切分單詞然后輸出鍵值對單詞, 1。這些鍵值對不會立即寫到磁盤而是先存在內(nèi)存緩沖區(qū)中。當(dāng)緩沖區(qū)達(dá)到一定閾值默認(rèn)80%會觸發(fā)溢寫Spill把數(shù)據(jù)寫到本地磁盤。在溢寫之前會按照鍵進(jìn)行排序和分區(qū)。分區(qū)決定了這個(gè)鍵值對最終會被哪個(gè)Reduce任務(wù)處理。所有Map任務(wù)完成后進(jìn)入Shuffle階段。每個(gè)Reduce任務(wù)會從所有Map任務(wù)的輸出中拉取屬于自己分區(qū)的數(shù)據(jù)。這個(gè)過程涉及大量的網(wǎng)絡(luò)傳輸和磁盤I/O是MapReduce中最耗時(shí)的階段之一。Reduce任務(wù)拿到數(shù)據(jù)后會按照鍵進(jìn)行歸并排序然后對每個(gè)鍵的值列表執(zhí)行Reduce邏輯。在WordCount中就是把同一個(gè)單詞的所有計(jì)數(shù)相加輸出單詞, 總次數(shù)。4.2 為什么Shuffle是性能瓶頸Shuffle之所以慢是因?yàn)樗瑫r(shí)涉及網(wǎng)絡(luò)傳輸、磁盤讀寫和排序。Map輸出的數(shù)據(jù)要先寫到本地磁盤然后Reduce再通過網(wǎng)絡(luò)拉取最后還要在Reduce端做歸并排序。每一步都有I/O開銷。在實(shí)際調(diào)優(yōu)中有幾個(gè)參數(shù)值得關(guān)注。mapreduce.task.io.sort.mb控制Map端排序緩沖區(qū)大小默認(rèn)100MB。如果Map輸出數(shù)據(jù)量大適當(dāng)調(diào)大這個(gè)值可以減少溢寫次數(shù)。mapreduce.reduce.shuffle.parallelcopies控制Reduce端并行拉取數(shù)據(jù)的線程數(shù)默認(rèn)5在網(wǎng)絡(luò)帶寬充足的情況下可以調(diào)大。還有一個(gè)經(jīng)驗(yàn)如果Map輸出數(shù)據(jù)量遠(yuǎn)大于輸入數(shù)據(jù)量比如做笛卡爾積之類的操作Shuffle會成為嚴(yán)重瓶頸。這時(shí)候需要考慮是否真的適合用MapReduce或者是否可以在Map端做更多預(yù)聚合來減少Shuffle數(shù)據(jù)量。4.3 Combiner一個(gè)容易被忽視的優(yōu)化點(diǎn)Combiner是MapReduce中的一個(gè)可選組件它在Map端對輸出進(jìn)行局部聚合。還是以WordCount為例如果某個(gè)Map任務(wù)處理的分片中“hello”出現(xiàn)了100次沒有Combiner的話會輸出100個(gè)hello, 1有Combiner的話會先在Map端合并成hello, 100只傳輸一個(gè)鍵值對。Combiner的使用有一個(gè)前提它的邏輯必須滿足交換律和結(jié)合律。也就是說先合并再計(jì)算和先計(jì)算再合并的結(jié)果必須一致。求和、求最大值都滿足這個(gè)條件但求平均值就不行。在實(shí)際使用中Combiner能顯著減少Shuffle數(shù)據(jù)量但很多人會忘記配置它。如果你的Map輸出數(shù)據(jù)量很大而Reduce邏輯又支持局部聚合一定要加上Combiner。4.4 任務(wù)失敗與推測執(zhí)行在分布式環(huán)境中任務(wù)失敗是常態(tài)而不是異常。MapReduce框架對任務(wù)失敗有完整的處理機(jī)制。如果某個(gè)Map或Reduce任務(wù)失敗框架會在另一臺機(jī)器上重新調(diào)度這個(gè)任務(wù)。默認(rèn)情況下一個(gè)任務(wù)最多可以重試4次。但有一種情況更棘手任務(wù)沒有失敗但執(zhí)行得特別慢。這可能是因?yàn)闄C(jī)器負(fù)載高、磁盤有問題、或者網(wǎng)絡(luò)擁塞。MapReduce引入了推測執(zhí)行Speculative Execution機(jī)制如果框架發(fā)現(xiàn)某個(gè)任務(wù)明顯慢于同批次的其他任務(wù)會在另一臺機(jī)器上啟動一個(gè)相同的任務(wù)誰先完成就用誰的結(jié)果。推測執(zhí)行在大多數(shù)情況下能縮短作業(yè)總時(shí)間但也不是沒有代價(jià)。它會占用額外的資源如果集群資源緊張反而可能拖慢其他任務(wù)。在實(shí)際調(diào)優(yōu)中可以根據(jù)集群負(fù)載情況決定是否開啟推測執(zhí)行。5. YARN如何調(diào)度集群資源5.1 ResourceManager與NodeManager的分工YARN采用主從架構(gòu)。ResourceManager是主節(jié)點(diǎn)負(fù)責(zé)整個(gè)集群的資源管理和分配。NodeManager是從節(jié)點(diǎn)運(yùn)行在每臺機(jī)器上負(fù)責(zé)管理本機(jī)的資源CPU、內(nèi)存等和容器Container的生命周期。當(dāng)客戶端提交一個(gè)應(yīng)用程序時(shí)ResourceManager會分配一個(gè)容器來運(yùn)行ApplicationMaster。ApplicationMaster負(fù)責(zé)這個(gè)應(yīng)用程序的具體執(zhí)行向ResourceManager申請資源、與NodeManager通信啟動任務(wù)、監(jiān)控任務(wù)狀態(tài)。這種架構(gòu)的好處是ResourceManager只負(fù)責(zé)宏觀調(diào)度具體的任務(wù)管理由ApplicationMaster負(fù)責(zé)。不同的計(jì)算框架可以實(shí)現(xiàn)自己的ApplicationMaster從而在YARN上運(yùn)行。5.2 調(diào)度器的選擇與影響YARN提供了三種主要調(diào)度器FIFO、Capacity和Fair。FIFO最簡單先提交的作業(yè)先獲得資源但大作業(yè)會阻塞后面的小作業(yè)。Capacity調(diào)度器把集群資源劃分成多個(gè)隊(duì)列每個(gè)隊(duì)列有容量保證適合多租戶場景。Fair調(diào)度器動態(tài)調(diào)整資源分配讓所有作業(yè)公平地獲得資源。在實(shí)際生產(chǎn)環(huán)境中Capacity和Fair更常用。選擇哪種取決于你的業(yè)務(wù)特點(diǎn)。如果多個(gè)團(tuán)隊(duì)共享集群且每個(gè)團(tuán)隊(duì)有明確的資源預(yù)算Capacity更合適。如果希望作業(yè)之間公平競爭Fair更合適。注意調(diào)度器的配置直接影響作業(yè)的等待時(shí)間和集群利用率。在集群資源緊張時(shí)不合理的隊(duì)列配置可能導(dǎo)致某些作業(yè)長期得不到資源。5.3 內(nèi)存與CPU的資源配置YARN管理的最主要資源是內(nèi)存和CPU。內(nèi)存資源以MB為單位CPU資源以虛擬核vCore為單位。每個(gè)NodeManager會報(bào)告本機(jī)可用的內(nèi)存和vCore數(shù)量。在配置時(shí)需要預(yù)留一部分內(nèi)存給操作系統(tǒng)和Hadoop自身進(jìn)程。比如一臺128GB內(nèi)存的機(jī)器可能只報(bào)告120GB給YARN使用。每個(gè)容器需要指定內(nèi)存大小如果容器實(shí)際使用內(nèi)存超過申請值NodeManager會殺掉這個(gè)容器。CPU資源的調(diào)度相對寬松一些。默認(rèn)情況下vCore只是邏輯上的限制并不做硬性隔離。也就是說一個(gè)容器可以使用超過其申請vCore數(shù)量的CPU時(shí)間。如果需要嚴(yán)格的CPU隔離需要配置Linux的cgroups。5.4 一個(gè)實(shí)際集群的資源規(guī)劃案例假設(shè)你有一個(gè)10臺機(jī)器的集群每臺機(jī)器配置為32核CPU、128GB內(nèi)存、4TB硬盤。那么整個(gè)集群的資源總量是320個(gè)vCore、1280GB內(nèi)存、40TB存儲。但實(shí)際可用資源要打折扣。每臺機(jī)器需要預(yù)留約8GB內(nèi)存給系統(tǒng)所以YARN可用內(nèi)存約120GB每臺總共1200GB。vCore方面如果每個(gè)vCore對應(yīng)一個(gè)物理核總共320個(gè)。但實(shí)際配置中通常會設(shè)置vCore數(shù)略多于物理核數(shù)比如1.5倍以提高CPU利用率。存儲方面HDFS可用空間大約是總硬盤容量的70%到80%因?yàn)橐A(yù)留空間給臨時(shí)文件、日志和副本開銷。40TB的原始容量實(shí)際可用可能在28TB到32TB之間。這個(gè)規(guī)劃不是一成不變的。隨著業(yè)務(wù)變化可能需要調(diào)整隊(duì)列容量、增加機(jī)器、或者優(yōu)化任務(wù)資源申請。關(guān)鍵是持續(xù)監(jiān)控集群利用率根據(jù)實(shí)際數(shù)據(jù)做調(diào)整。6. 從零搭建Hadoop集群的關(guān)鍵步驟與避坑點(diǎn)6.1 環(huán)境準(zhǔn)備中最容易忽略的細(xì)節(jié)搭建Hadoop集群的第一步是準(zhǔn)備機(jī)器和環(huán)境。很多人會直接開始安裝Hadoop但忽略了幾個(gè)基礎(chǔ)配置導(dǎo)致后面問題不斷。首先是主機(jī)名和hosts文件。集群中每臺機(jī)器必須有唯一的主機(jī)名并且所有機(jī)器都能通過主機(jī)名互相訪問。我見過因?yàn)閔osts文件配置不一致導(dǎo)致DataNode無法注冊到NameNode的情況。排查了半天才發(fā)現(xiàn)是某臺機(jī)器的hosts文件少了一行。其次是SSH免密登錄。Hadoop的啟動腳本需要通過SSH在集群各機(jī)器上執(zhí)行命令。如果沒配置免密每次啟動都要輸入密碼自動化腳本就無法運(yùn)行。配置方法是生成密鑰對然后把公鑰追加到所有機(jī)器的authorized_keys文件中。還有時(shí)間同步。集群中各機(jī)器的時(shí)間差不能太大否則會導(dǎo)致各種奇怪的問題比如任務(wù)調(diào)度異常、日志時(shí)間錯亂。建議配置NTP服務(wù)保持時(shí)間同步。6.2 核心配置文件的參數(shù)含義Hadoop的配置主要集中在幾個(gè)XML文件中。core-site.xml配置通用參數(shù)最重要的是fs.defaultFS指定HDFS的訪問地址。hdfs-site.xml配置HDFS相關(guān)參數(shù)包括副本數(shù)、NameNode和DataNode的數(shù)據(jù)目錄等。yarn-site.xml配置YARN相關(guān)參數(shù)。mapred-site.xml配置MapReduce相關(guān)參數(shù)。在配置時(shí)有幾個(gè)參數(shù)需要特別注意。dfs.replication控制副本數(shù)默認(rèn)3。在小規(guī)模集群中比如3臺機(jī)器如果還是3副本存儲利用率會很低可以考慮設(shè)為2。dfs.namenode.name.dir和dfs.datanode.data.dir指定數(shù)據(jù)存儲路徑建議配置多個(gè)路徑分別掛載不同磁盤提高I/O吞吐。yarn.nodemanager.resource.memory-mb指定NodeManager可用的總內(nèi)存這個(gè)值要根據(jù)機(jī)器實(shí)際內(nèi)存減去系統(tǒng)預(yù)留來設(shè)置。設(shè)得太大可能導(dǎo)致系統(tǒng)內(nèi)存不足設(shè)得太小則浪費(fèi)資源。6.3 格式化與啟動的順序問題第一次啟動Hadoop前需要格式化NameNode。命令是hdfs namenode -format。這個(gè)操作會創(chuàng)建NameNode的元數(shù)據(jù)目錄。注意格式化只能執(zhí)行一次重復(fù)格式化會導(dǎo)致元數(shù)據(jù)丟失集群中的數(shù)據(jù)就無法訪問了。啟動順序也有講究。先啟動HDFSstart-dfs.sh再啟動YARNstart-yarn.sh。停止時(shí)順序相反先停YARN再停HDFS。如果順序反了可能會導(dǎo)致某些進(jìn)程無法正常停止。啟動后可以通過Web UI檢查集群狀態(tài)。NameNode的Web UI默認(rèn)端口是9870ResourceManager的默認(rèn)端口是8088。通過這些界面可以查看集群節(jié)點(diǎn)數(shù)、存儲容量、運(yùn)行中的任務(wù)等信息。6.4 常見故障與排查思路集群搭建過程中最常見的問題是DataNode無法啟動或無法注冊到NameNode。排查思路是先看日志DataNode的日志通常在logs目錄下文件名包含datanode。常見原因包括防火墻阻止了通信、hosts文件配置錯誤、數(shù)據(jù)目錄權(quán)限不對、多次格式化導(dǎo)致clusterID不一致。另一個(gè)常見問題是YARN的NodeManager無法啟動。檢查yarn-site.xml中yarn.nodemanager.aux-services是否配置正確通常是mapreduce_shuffle。還要檢查NodeManager的內(nèi)存配置是否超過了機(jī)器實(shí)際可用內(nèi)存。如果任務(wù)提交后一直處于ACCEPTED狀態(tài)說明資源不足無法分配容器。這時(shí)候需要檢查集群資源使用情況或者調(diào)整隊(duì)列配置和任務(wù)資源申請。提示在排查問題時(shí)日志是最好的朋友。不要只看表面現(xiàn)象要深入日志找到根本原因。很多問題在日志中都有明確的錯誤信息只是需要耐心查找。7. 實(shí)際使用中的性能調(diào)優(yōu)經(jīng)驗(yàn)7.1 數(shù)據(jù)本地性對性能的影響數(shù)據(jù)本地性是指計(jì)算任務(wù)盡量在存儲有該任務(wù)所需數(shù)據(jù)的機(jī)器上執(zhí)行。在Hadoop中如果一個(gè)Map任務(wù)讀取的數(shù)據(jù)塊就在本機(jī)就不需要走網(wǎng)絡(luò)傳輸速度會快很多。Hadoop會盡量滿足數(shù)據(jù)本地性但不是總能做到。如果本機(jī)資源不足任務(wù)可能被調(diào)度到其他機(jī)器上變成機(jī)架內(nèi)傳輸甚至跨機(jī)架傳輸。跨機(jī)架傳輸?shù)难舆t和帶寬成本都更高。在實(shí)際使用中可以通過增加集群規(guī)模、合理配置資源來提高數(shù)據(jù)本地性命中率。另外如果數(shù)據(jù)是從外部導(dǎo)入的盡量在導(dǎo)入時(shí)就分布到集群各節(jié)點(diǎn)而不是集中在一臺機(jī)器上。7.2 小文件問題的處理策略HDFS設(shè)計(jì)時(shí)假設(shè)文件都很大因?yàn)榇笪募菀坠芾碓獢?shù)據(jù)開銷也小。但實(shí)際使用中經(jīng)常會有大量小文件比如每個(gè)日志文件只有幾KB。這些小文件會給NameNode帶來巨大壓力因?yàn)槊總€(gè)文件、每個(gè)塊都需要在NameNode內(nèi)存中存儲元數(shù)據(jù)。處理小文件有幾種常見策略。第一種是在數(shù)據(jù)進(jìn)入HDFS之前就合并比如用Flume或Logstash把多個(gè)小文件合并成大文件。第二種是用Hadoop的歸檔工具HAR把小文件打包。第三種是在MapReduce任務(wù)中使用CombineFileInputFormat把多個(gè)小文件合并成一個(gè)分片處理。我個(gè)人的經(jīng)驗(yàn)是最好在數(shù)據(jù)源頭就控制文件大小。如果源頭無法控制就在進(jìn)入HDFS的環(huán)節(jié)做合并。事后處理小文件往往更麻煩而且效果不一定好。7.3 內(nèi)存與GC調(diào)優(yōu)的實(shí)操建議Hadoop各組件的默認(rèn)內(nèi)存配置通常偏保守在生產(chǎn)環(huán)境中需要根據(jù)實(shí)際情況調(diào)整。NameNode的內(nèi)存主要消耗在元數(shù)據(jù)上每個(gè)文件、每個(gè)塊大約占用150字節(jié)。如果有1億個(gè)文件每個(gè)文件平均1個(gè)塊NameNode大約需要15GB內(nèi)存來存儲元數(shù)據(jù)。DataNode的內(nèi)存消耗相對較小主要受并發(fā)讀寫線程數(shù)影響。MapReduce任務(wù)的內(nèi)存配置更復(fù)雜需要分別設(shè)置Map任務(wù)和Reduce任務(wù)的堆大小。mapreduce.map.java.opts和mapreduce.reduce.java.opts控制JVM堆大小mapreduce.map.memory.mb和mapreduce.reduce.memory.mb控制容器內(nèi)存。GC調(diào)優(yōu)方面建議使用G1垃圾收集器它在大堆內(nèi)存下表現(xiàn)更好??梢酝ㄟ^-XX:UseG1GC開啟。另外設(shè)置合理的-XX:MaxGCPauseMillis可以控制GC停頓時(shí)間但設(shè)得太小會導(dǎo)致GC更頻繁需要根據(jù)實(shí)際負(fù)載做權(quán)衡。7.4 壓縮格式的選擇與權(quán)衡在Hadoop中使用壓縮可以顯著減少存儲空間和網(wǎng)絡(luò)傳輸量但會增加CPU開銷。選擇壓縮格式時(shí)需要在壓縮比、壓縮速度和解壓速度之間做權(quán)衡。常見的壓縮格式有Snappy、LZO、Gzip和Bzip2。Snappy壓縮和解壓速度都很快但壓縮比一般適合中間數(shù)據(jù)。Gzip壓縮比高但速度較慢適合冷數(shù)據(jù)存儲。Bzip2壓縮比最高但速度最慢且不支持分片在MapReduce中使用受限。在實(shí)際使用中我通常這樣選擇Map輸出用Snappy因?yàn)镾huffle階段對速度敏感最終存儲用Gzip因?yàn)榇鎯Τ杀靖匾绻麛?shù)據(jù)需要分片處理用LZO或Snappy它們支持分片。注意壓縮格式的選擇不是孤立的要考慮整個(gè)數(shù)據(jù)處理鏈路。如果上游用了一種格式下游可能也需要支持解壓否則就要增加解壓步驟。8. 一些個(gè)人體會與后續(xù)可擴(kuò)展的方向Hadoop的生態(tài)在不斷發(fā)展但它的核心思想——用普通機(jī)器集群解決大規(guī)模數(shù)據(jù)問題——一直沒有變。我在實(shí)際使用中最大的體會是Hadoop不是銀彈它有自己的適用邊界。對于批處理場景它依然是最成熟、最穩(wěn)定的選擇之一但對于實(shí)時(shí)性要求高的場景需要結(jié)合其他框架來使用。另外Hadoop的運(yùn)維成本不低。集群規(guī)模越大故障概率越高對運(yùn)維人員的要求也越高。如果團(tuán)隊(duì)沒有足夠的運(yùn)維能力可以考慮使用云上的托管Hadoop服務(wù)把底層運(yùn)維交給云廠商自己專注于業(yè)務(wù)邏輯。從學(xué)習(xí)路徑來看建議先理解HDFS和MapReduce的基本原理再動手搭建一個(gè)小集群跑通WordCount然后逐步深入YARN的資源管理和調(diào)優(yōu)。不要一開始就追求大而全先把核心鏈路走通再根據(jù)實(shí)際需求擴(kuò)展。后續(xù)可以擴(kuò)展的方向包括Hadoop與Spark的集成、Hive數(shù)據(jù)倉庫的使用、HBase列式存儲的應(yīng)用等。這些都是在Hadoop基礎(chǔ)上構(gòu)建的上層工具解決了不同場景下的問題。但無論上層工具怎么變底層的HDFS和YARN依然是基石理解它們的工作原理對用好整個(gè)生態(tài)至關(guān)重要。