1. 程式人生 > 其它 >HDFS Federation在美團點評的應用與改進

HDFS Federation在美團點評的應用與改進

背景

2015年10月,經過一段時間的優化與改進,美團點評HDFS叢集穩定性和效能有顯著提升,保證了業務資料儲存量和計算量爆發式增長下的儲存服務質量;然而,隨著叢集規模的發展,單組NameNode組成的叢集也產生了新的瓶頸:

  • 擴充套件性:根據HDFS NameNode記憶體全景HDFS NameNode記憶體詳解這兩篇文章的說明可知,NameNode記憶體使用和元資料量正相關。180GB堆記憶體配置下,元資料量紅線約為7億,而隨著叢集規模和業務的發展,即使經過小檔案合併與資料壓縮,仍然無法阻止元資料量逐漸接近紅線。
  • 可用性:隨著元資料量越來越接近7億,CMS GC頻率也越來越高,期間也曾發生過一次在CMS GC過程中由於大檔案getBlocklocation併發過高導致的promotion fail。
  • 效能:隨著業務的發展,叢集規模接近2000臺,NameNode響應的RPC QPS也在逐漸提高。越來越高併發的讀寫,與NameNode的粗粒度元資料鎖,使NameNode RPC響應延遲和平均RPC佇列長度都在慢慢提高。
  • 隔離性:由於NameNode沒有隔離性設計,單一對NameNode負載過高的應用,會影響到整個叢集的服務能力。

HDFS Federation是Hadoop-0.23.0中為解決HDFS單點故障而提出的NameNode水平擴充套件方案。該方案可以為HDFS服務建立多個namespace,從而提高叢集的擴充套件性和隔離性。基於以上背景,我們在2015年10月發起了HDFS Federation改造專案。

HDFS Federation是以客戶端為核心的解決方案,對Hadoop客戶端影響較大,在落地應用時也有較多的限制,對上層應用模式有較強的依賴。本文分享了在此次改造的過程中,基於美團點評的業務背景,我們對HDFS Federation本身做出的改進和對拆分過程的流程化處理,希望能為需要落地HDFS Federation的同學提供一個參考。

上層應用與業務

基礎架構方面,美團點評Hadoop版本為2.4.1,使用了Kerberos作為認證支援。相關技術棧中,Spark應用版本包含1.1、1.3、1.4、1.5,同時使用了Zeppelin作為Spark Notebook的開發工具。在查詢引擎方面Hive有0.13和1.2兩個版本,同時重度依賴Presto和Kylin,除此之外,也對DMLC提供了平臺性支援。

工具鏈建設方面,基於Hadoop生態,資料平臺組自研了各類平臺工具,其中受Federation影響的部分工具有:

  • 數倉管理:滿足各類Hive表的DDL需求,同時支援UDF和檔案上傳建表。
  • 原始資料接入:支援日誌抓取和MySQL資料接入資料倉庫。
  • 非結構資料開發:支援作業託管,提供MR/Spark作業編譯、管理、測試、部署一站式服務。
  • 數倉開發:支援ETL的一站式開發和管理,同時在任務狀態、診斷、SLA保證方面也有強力的支援;針對流程測試以及資料回收進行了隔離,使用統一的test.db和backup.db。
  • 排程系統:自研的排程系統支撐了每天數萬個排程作業,準確的處理作業間的強弱依賴關係,有效的保證了按天資料生產。
  • 查詢平臺:統一了Hive和Presto的查詢入口。

自研的資料平臺基本覆蓋了90%的資料開發需求,一方面有效的控制了Hadoop客戶端的數量,收緊了使用者入口,對於發放的客戶端,配合Kerberos,也具有很高的掌控力,另一方面實現了對使用者行為的原始碼級掌控力。

資料開發方面,美團點評業務一直持續著爆發式增長,整體叢集規模和資料生產流程增量每年都接近double。業務發展也推動了組織結構的發展,進而也影響到了相應的大資料資產:

  • 一個Hadoop賬號可能經歷過多個業務線,使用者應用中,對其他Hadoop賬號的資料進行讀寫、move較為常見,對這類行為也沒有進行過梳理和限制。
  • 完成平臺接入後,對生產流程管理的規範較多,但對使用者程式碼的規範較少,使用者程式碼風格多樣。

應用與改進

Federation的侷限性

在解決NameNode擴充套件能力方面,社群雖然提供了Federation,但這個方案有很強的侷限性:

  1. HDFS路徑Scheme需要變為ViewFs,ViewFs路徑和其他Scheme路徑互不相容,比如DistributedFileSystem無法處理ViewFs為Scheme的路徑,也就是說如果啟用,則需要將Hive meta、ETL指令碼、MR/Spark作業中的所有HDFS路徑均的scheme改為viewfs。
  2. 如果將fs.defaultFS的配置從hdfs://ns1/變為viewfs://ns/,將導致舊程式碼異常,通過指令碼對使用者上萬個原始碼檔案的分析,常用的HDFS路徑風格多樣,包括hdfs:///user、hdfs://ns1/user、/user等,如果fs.defaultFS有所更改,hdfs:///user將會由於缺失nameservice變為非法HDFS路徑。
  3. ViewFs路徑的掛載方式與Linux有所區別:
    • 如果一個路徑聲明瞭掛載,那麼其同級目錄都需要進行掛載,比如/user/path_one掛載到了hdfs://ns1/user/path_one上,那麼/user/path_two也需要在配置中宣告其掛載到哪個具體的路徑上。
    • 如果一個路徑聲明瞭掛載,那麼其子路徑不能再宣告掛載,比如/user/path_one掛載到了hdfs://ns1/user/path_one上,那麼其子路徑也自動並且必須掛載到hdfs://ns1/user/path_one上。
  4. 一次路徑請求不能跨多個掛載點:
    • 由於HDFS客戶端原有的機制,一個DFSClient只對應一個nameservice,所以一次路徑處理不能轉為多個nameservice的多次RPC。
    • 對於跨掛載點的讀操作,只根據掛載配置返回假結果。
    • 對於跨掛載點的rename(move路徑)操作,會丟擲異常。
  5. Federation架構中,NameNode相互獨立,NameNode元資料、DataNode中塊檔案都沒有進行共享,如果要進行拆分,需要使用DistCp,將資料完整的拷貝一份,儲存成本較高;資料先被讀出再寫入三備份的過程,也導致了拷貝效率的低效。
  6. Federation是改造了客戶端的解決方案,重度依賴客戶端行為。方案中NameNode相互獨立,對Federation沒有感知。另外HDFS為Scheme的路徑,不受Federation掛載點影響,也就是說如果對路徑進行了namespace拆分後,如果因為程式碼中的路徑或客戶端配置沒有及時更新,導致流程資料寫入老資料路徑,那麼請求依然是合法但不符合預期的。

對其中一些名詞的解釋:

  • 在HDFS中namespace是指NameNode中負責管理檔案系統中的樹狀目錄結構以及檔案與資料塊的對映關係的一層邏輯結構,在Federation方案中,NameNode之間相互隔離,因此社群也用一個namespace來指代Federation中一組獨立的NameNode及其元資料。
  • Scheme是URI命名結構([scheme:][//authority][path][?query][#fragment])中的一部分,用於標識URI所使用的協議,HDFS路徑也是一個URI,常見的Scheme為HDFS,在Federation的方案中,HDFS路徑Scheme為ViewFs。
  • 掛載點(mount point),它在HDFS Federation中和Linux中的概念近似,指在HDFS客戶端上下文中,將ViewFs為Scheme的一個路徑,比如viewfs://ns/user,對映到一個具體的HDFS路徑上,比如hdfs://ns2/user,這個路徑可以是任意Scheme的HDFS路徑,這樣對於viewfs://ns/user實際上會被轉換為對hdfs://ns2/user的操作。

侷限性帶來的問題和解決

Scheme相容性問題

Scheme的相容問題要求在上線時全量替換業務方程式碼中的路徑,雖然對業務方大多數原始碼具有掌控力,但是由於不可灰度帶來的全量修改帶來的測試、上線、修復工作的成本,全量操作帶來的運維時間,以及對資料生產穩定性的影響都是不能接受的。為此,以能灰度啟用Federation特性為目標,對HDFS客戶端進行了修改:

  • 增加了ViewFs和HDFS兩種Scheme路徑的相容性:
    • 修改了org.apache.hadoop.fs.FileSystem.fixRelativePart(Path),該函式在DistributedFileSystem各類請求處理中均有呼叫,原本用於處理相對路徑,而ViewFileSystem不會呼叫。在這裡,如果遇到了ViewFs為Scheme的路徑,則利用ViewFileSystem中的掛載資訊返回真正的HDFS路徑。
    • 修改了org.apache.hadoop.fs.viewfs.ViewFileSystem.getUriPath(Path),該函式在ViewFileSystem各類請求處理中均有呼叫,原本用作判斷路徑Scheme為ViewFs,同時處理相對路徑。一方面,由於Federation的掛載配置中,只有通過掛載點查詢真實路徑的資料結構,逆向查詢比較複雜,改動也比較大,另一方面,從運營角度看我們也不希望維持非常複雜的掛載配置。所以在這裡,做了一個限定,對於HSFS為Scheme的路徑與其在Federation的掛載點路徑相同,所以在此函式中如果遇到了HDFS為Scheme的路徑,直接使用org.apache.hadoop.fs.Path.getPathWithoutSchemeAndAuthority(Path)去掉Scheme即可。
  • fs.defaultFS變更會對原有程式碼帶來影響,但是將其配置為ViewFs為Scheme的路徑才能使HDFS Scheme的應用逐漸收斂,因此,我們增加了用於指定預設namespace的配置fs.defaultNS,使hdfs:///user這樣即使沒有提供Authority的路徑也能路由到正確的NameNode。

針對Scheme侷限性的改造,雖然提高了相容性,使方案能夠進行灰度,但卻使DistributedFileSystem和ViewFileSystem耦合,又增加了一條ViewFileSystem掛載限制,因此只適合在過度期間應用。

掛載配置限制

ViewFs的掛載方式與Linux有所區別,如果完全繼承現有HDFS不變,則需要非常多的掛在配置項,並且後續每次增加Hive庫、使用者目錄,初期我們使用了運營手段解決了這個問題:

  1. 將遷移路徑放到獨立的目錄下,比如/user/hivedata/xx.db,遷移到/ns2/hivedata/xx.db,這樣掛載宣告則不會太過複雜。
  2. 由於使用者組路徑大都應用於MR、Spark作業中,修改路徑需要重新編譯,因此初期應用時,只對Hive庫路徑。
  3. 由於跨namespace不能進行rename,所以分析NameNode審計日誌,得到Hive庫路徑和使用者組路徑沒有rename關係的庫,只對這些庫進行遷移。
  4. 通過以上三個種手段,對於ETL流程這種不需要編譯的程式碼,可以直接替換,對於MR、Spark作業來說推動修改的成本也有所降低。
  5. 為了進一步降低後續拆分成本,我們在ETL和作業開發兩個方面提供並推廣了根據庫表資訊從Hive meta中取得庫表HDFS路徑的工具,減少了程式碼中對庫表路徑的硬編碼。

以上三種手段,能滿足美團側常規的拆分需求,但是隨著點評側資料融合,點評側資料也作為整體叢集的一個namespace加入進來。然而,我們對點評側平臺的掌控力沒有深入到原始碼級別,因此無法統一推動更改HDFS路徑。如果不對掛載邏輯進行修改,在合併重複路徑時,需要將美團側/user路徑合併到點評側/user路徑中,但是由於跨namespace無法進行rename,勢必會造成使用者作業的失敗。因此,我們對掛載邏輯進行了修改,使其同Linux的掛載方式相同。

同namespace,不同掛載點不能rename

業務方很多Hive庫表資料會先生成在測試庫表或使用者目錄中,驗證完成後將資料載入到對應時間分割槽中。在掛載配置中,業務方Hive庫、Hive測試庫、使用者組目錄一般不會掛載到同一目錄下,即使三者在同一namespace下,由於不同掛載點間不能rename的限制,也無法進行載入。在原始碼分析的過程中,發現以下注釋:

// Note we compare the URIs. the URIs include the link targets.
// hence we allow renames across mount links as long as the mount links
// point to the same target.
if (!resSrc.targetFileSystem.getUri().equals(
          resDst.targetFileSystem.getUri())) {
  throw new IOException("Renames across Mount points not supported");
}
*/
//
// Alternate 3 : renames ONLY within the the same mount links.
//
if (resSrc.targetFileSystem !=resDst.targetFileSystem) {
  throw new IOException("Renames across Mount points not supported");
}

可以發現社群是有考慮相同namespace路徑可以進行rename操作的(註釋掉的原因沒有找到),因此,我們將這段邏輯開啟,替換掉了“renames ONLY within the the same mount links”。

儲存成本與拷貝效率問題

使用Federation方案時,叢集節點規模為2000多臺,元資料已達6億,儲存使用已近80%。按照規劃,儲存容量將不足以支撐全部待遷移資料,但是拆成多次操作,週期和運維成本都比較高,因此我們開始調研FastCopy。

FastCopy是Facebook開源的資料拷貝方案,它通過以下方式在不增加儲存成本的情況下對資料進行拷貝:

  1. 通過getBlockLocation獲取原始檔塊分佈。
  2. 通過ClientProtocol(HDFS包中的介面,下同)建立目標檔案。
  3. 通過ClientProtocol addBlock,在引數中,指定源塊分佈作為favoredNodes,常規情況下NameNode會優先選擇favoredNodes中的DataNode作為塊的儲存位置,特殊情況下(比如儲存空間不足,DataNode負載過高等)也有可能返回不同位置。
  4. 整理源和目標塊位置,使相同DataNode的位置能一一對應。
  5. 通過ClientDatanodeProtocol向源DataNode傳送copyBlock請求。
  6. 在DataNode中,如果copyBlock請求中的源和目標相同,則通過在Linux檔案系統中建立硬鏈的方式完成拷貝,否則通過原有邏輯完成拷貝。

但是,在計劃合入時,該方案也有自身的問題:

  • 社群path為HDFS-2139,一直處於未合入狀態,且當時Patch內容相對Facebook的方案來說,部分細節沒有考慮,例如檔案lease,無法構造硬鏈時的降級,DFS Used的統計問題等。
  • Facebook的原始碼相對成熟,但其原始碼基於0.20(facebookarchive/hadoop-20),已有四年沒有更新,很多原始碼發生變化,DFS Used的統計問題也沒有解決。
  • 雖然Facebook將FastCopy合入DistCp,但也有部分缺陷:
    • 每個路徑生成一個mapper,每個mapper只處理一個路徑,如果目錄層次過高,容易導致資料傾斜,如果目錄層次太低,容易產生過多mapper。
    • 只對遷移路徑進行屬主同步,其父目錄沒有處理。
    • 與DistCp耦合定製比較複雜。

所以,綜合以上內容,我們完善了HDFS-2139,並更新了issue,在合入Facebook實現的基礎上解決了DFS Used的統計問題;除了這個Patch,我們也實現了獨立的FastCopy MR作業,解決了上述問題。最終,在拆分時15小時完成14+PB資料拷貝,保證了方案的可行性。

另外需要注意的是,對於HDFS來說,無法感知哪個塊是通過硬鏈構造的,因此,一旦源和目標檔案同時存在時,開啟balancer,會因為塊的遷移導致儲存使用的增加。因此,遷移期間,一般建議暫停相關namespace的balancer。

重度依賴客戶端

基於以上幾點改進,雖然降低了拆分成本和相容性,使Federation的應用成為可迭代方案,但是如果沒有對客戶端強大的掌控力,客戶端例項不能完全更新,HDFS路徑硬編碼不能得到徹底梳理,反而會造成資料生產方面的混亂,成為此方案的掣肘。

經過美團側資料平臺的多年運營,對客戶端以及業務程式碼有非常強的掌控力,有效避免了上述問題的發生。

計算和查詢引擎的問題和解決

一方面,雖然Federation已出現了多年,但Hive、Spark等上層應用對Federation的支援仍然存在問題,另一方面,隨著應用的逐漸加深,雖然有些問題並不是程式碼bug,但在美團點評的應用場景下,仍然產生了一定問題。我們針對這些問題,進行了探索和改進。

安全問題

安全方面,計算引擎(包括MapReduce和Spark)在提交作業時,會向NameNode傳送RPC,獲取HDFS Token。在ViewFileSystem中,會向所有namespace序列的申請Token,如果某個namespace的NameNode負載很高,或者發生故障,則任務無法提交,YARN的ResourceManager在renew Token時,也會受此影響。隨著美團點評的發展YARN作業併發量也在逐漸提高,儲存在HDFS上的YARN log由於QPS過高,被拆分為獨立的namespace。但由於其併發和YARN container併發相同,NameNode讀寫壓力還是非常大,經常導致其RPC佇列打滿,請求超時,進而影響了作業的提交。針對此問題,我們做出了一下改進:

  • container日誌由NodeManager通過impersonate寫入HDFS,這樣客戶端在提交Job時,就不需要YARN log所在namespace的Token。
  • ViewFileSystem在獲取Token時,增加了引數,用於指定不獲取哪些namespace的Token。
  • 由於作業並不總是需要所有namespace中的資料,因此當單個namespace故障時,不應當影響其他namespace資料的讀寫,否則會降低整個叢集的分割槽容忍性和可用性,ViewFileSystem在獲取Token時,即使失敗,也不影響作業提交,而是在真正訪問資料時作業失敗,這樣在不需要的Token獲取失敗時,不影響作業的執行。

另外,客戶端獲取到的Token會以namespace為key,儲存在一個自定義資料結構中(Credentials)。ResourceManager renew時,遍歷這個資料結構。而NodeManager在拉取JAR包時,根據本地配置中的namespace名去該資料結構中獲取對應Token。因此需要注意的是,雖然namespace配置和服務端不同不影響普通HDFS讀寫,但提交作業所使用的namespace配置需要與NodeManager相同,至少會用到的namespace配置需要是一致的。

已存在Patch問題

其他問題

  • Hive create table .. as .. 會導致臨時檔案所在目錄和表目錄不在同一namespace,導致move結果失敗,目前已修復,思路同HIVE-6152,將臨時檔案生成在表目錄中。
  • Hive表的元資料中,SERDEPROPERTIES中,可能會存在對HDFS路徑的依賴,在梳理路徑硬編碼時,容易忽略掉。
  • Spark 1.1在啟用viewfs時,會產生不相容問題。
  • 開源分散式機器學習專案DMLC目前也尚不相容ViewFs。

拆分流程與自動化

隨著namespace拆分經驗的積累,其流程也逐漸清晰和明確:

  1. 當namespace的NameNode逐漸接近瓶頸(包括RPC和元資料量)時,對Hadoop使用者對應的使用者組目錄和Hive庫目錄進行分析,得出元資料量(通過分析fsimage)和一天內RPC量(通過分析審計日誌),進而得出需要拆分的使用者資料。
  2. 對於需要拆分的資料,分析其和不需要拆分資料的rename關係,如果存在rename關係,則需要重新選擇拆分資料。
  3. 如果需要,則搭建新namespace環境。
  4. 關閉相關namespace balancer。
  5. 根據fsimage,分析出待拆分路徑元資料分佈,得出一個路徑列表,使列表中每個路徑下的檔案塊數基本接近。
  6. 基於第四步的結果進行首輪拷貝,首輪拷貝中針對不需要比較驗證的情況作出了優化:FastCopy MR工具會遞迴的拷貝路徑,如果目標路徑已存在說明之前已拷貝成功過,則不進行拷貝。
  7. 之後進行多輪補充拷貝:通過ls -r得到檔案和目錄列表;拷貝過程中開啟-delete -update,非遞迴的進行檢測與拷貝,這樣對於源目錄有更新的檔案和目錄會進行覆蓋(包括許可權和屬主的更新),源目錄新增的目錄和檔案會進行拷貝,源目錄刪除的檔案和目錄會進行刪除;這樣,可以會每一層的目錄進行檢測,可以同步目錄許可權和屬主發生的變化,同時也不會產生較大的資料傾斜。
  8. 準備好新掛載配置,找一個非工作時間,進行最終一輪的操作: a. 禁止源目錄的許可權(FastCopy使用hdfs身份執行不受影響)。 b. 進行最後一輪補充拷貝。 c. 由於資料大多數情況下基於硬鏈進行拷貝,所以存在檔案長度相同,但內容有問題的可能性極低,拷貝完成後,可以通過du路徑,校驗並逐漸找到資料長度不一致的檔案,進行重考。 d. 對客戶端分發新掛載配置。 e. 對NodeManager分發 新掛載配置,並進行decommission,重啟(YARN已支援recovery)。 f. 更新Hive meta。 g. 開放目標目錄許可權。
  9. 觀察一週,如果沒有問題則刪除源目錄。
  10. 重啟balancer。

以上是已經固定下來的步驟,其中第1、2、5、6、7步,第8步中的a~c是可以進行自動化的,這也是後續工作過程中,有待完善的部分。

總結

HDFS Federation作為以客戶端配置為核心的NameNode橫向擴容解決方案,對業務背景有較強的依賴,另一方面方案本身也有較多的侷限性。本文以美團點評實際應用場景出發,介紹了方案侷限性在業務背景下的影響,分享了對侷限性的解決和實施經驗。對HDFS Federation應用到已運營較長時間的大規模HDFS叢集有一定的借鑑意義。

參考資料