HDFS 實踐 - 京東 HDFS EC 應用解密

導讀

爲了實現降本增效,京東 HDFS 團隊在 EC 功能的移植、測試與上線過程中,基於自身現狀採取的一些措施並最終實現平滑上線。同時自研了一套數據生命週期管理系統,對熱溫冷數據進行自動化管理。在研發落地過程中還構建了三維一體的數據校驗機制,爲 EC 數據的正確性提供了強有力的技術保障。

本文詳細介紹在研發一個複雜系統時,如何基於實際情況進行取捨,並確立行動準則。在功能上線過程中,要保持對線上系統的敬畏,確保上線與回滾不會導致元數據損壞。此外,要深刻認識系統的核心職責,對於存儲系統務必加強技術保障,確保數據的安全與可靠,不能丟不能錯。

數據作爲京東數智戰略的重要資產,隨着業務的持續增長,大數據平臺存儲的數據量已突破 EB 級別,並且還在以每天將近 PB 的速度持續增長。傳統的方法是分析數據的使用頻率,把數據分爲熱溫冷三類數據。對於溫冷數據,使用壓縮率更高的算法,來降低存儲成本。但是無法突破三副本存儲策略的固有屬性:需要存儲三份完整的數據塊。

EC 數據相比副本模式如何提升存儲效能

Hadoop 分佈式存儲引擎(HDFS)在 3.0 版本中發佈了 EC inside HDFS 重要特性。該 EC(Erasure Coding) 是一種可通過計算降存儲的技術。下面我以 HDFS 支持的一種 EC 存儲策略 RS-3-2-1024k 爲例簡單介紹其原理。對於一個 200MB 的文件,會把數據按 1MB(1024KB) 一個條帶(striped)進行切分,每切分出 3 個 1MB 的數據塊條帶(data striped,記爲 d1-1,d2-1,d3-1),就用這 3 個數據塊條帶計算出兩個 1MB 的校驗塊條帶 (parity striped,記爲 p1-1, p2-1),依次循環處理,最後的 2MB 數據只能構成 2 個數據塊條帶(d1-67,d2-67),EC 算法會構建一個全零的假數據塊(d3-67)計算出最後兩個校驗塊條帶(p1-67,p2-67)。實際存儲在 DN 上的 5(3 個數據塊加 2 個校驗塊) 個 EC 數據塊分別由(d1-1..d1-67, d2-1..d2-67, d3-1..d3-66, p1-1..p1-67, p2-1..p2-67)組合成的數據塊。如果丟失其中任意兩個數據塊,都可以使用剩下的 3 個塊算出丟失的兩個塊。

一個 200MB 的文件按三副本方式存儲,需要在不同 DN 上存放 3 份完整的數據塊,消耗 600MB 的存儲空間。而 RS-3-2-1024k 只需要 334MB 的存儲空間,就能保證數據的冗餘度,並且存儲空間降低了 45%。因此,我們可以藉助 EC inside HDFS 技術進一步降低數據存儲成本,提高存儲效率。

要把 EC 技術應用到生產環境,需要對我們的生產系統進行改造升級。首先,需要讓生產代碼支持 EC 功能,我將在第一部分分析我們採用升級還是移植的權衡,以及遵循的原則。然後,介紹我們在改造過程中,通過哪些方法和技術來保障項目質量的。改造過程中涉及元數據方面的改動,因此上線需要格外注意,否則會導致數據難以恢復,在第三部分我會介紹線上系統的平滑升級方法與回滾的可行性。

我們運用 EC 的目的是存儲溫冷數據。對於熱溫冷數據,我們需要一套方案讓數據在這三種場景中流轉。我會在第四部分解密我們的數據生命週期管理系統。

作爲數據存儲平臺,我們的首要職責是保證數據的安全可靠性,不能把用戶的數據弄丟了,也不能把數據存錯了。特別是 EC 這種基於計算的存儲技術,尤爲需要注意數據的完整性。在最後一部分會闡述我們在 EC 數據完整性方面所做的努力。

01

升級 or 移植

我們從 2015 年開始基於社區 2.7.1 版本,構建京東大數據生態系統,作爲大數據平臺的最底層基礎系統,服務着成百上千個業務。

HDFS EC 是 3.0 版本正式發佈的一個重要特性,爲了支持 EC 做了大量的修改,接口也發生了較大變化。此外 3.0+ 版本還在一直迭代,存在不少缺陷,正式上線前還需要解決一些額外的接口兼容問題,會影響項目進度。再加上這幾年我們基於 2.7.1 版本,做了大量的功能開發與性能優化,才能支持當前單集羣萬臺規模的存儲集羣。因此我們採用移植 EC 的方案。

社區 EC 相關的兩個父任務:HDFS-7285、HDFS-8031 以及相關的 bugfix,加起來差不多 300 多個 patch。同時,我們的移植目標版本是 2.7.1,而 patch 是相對 3.0 的。如果圍繞 patch 進行移植,我們要把 3.0 相對 2.7.1 相關的前驅 patch 先移植過來,而這帶來的工作量是巨大並且無法估量的。

面對如此複雜的存儲系統,在開始移植前,需要確立一套移植方案和移植原則:

參與移植工作的開發人員都是非常有經驗的,對 HDFS 的整體架構比較清楚,才能保證移植的效率。經過一個多月的努力,完成了移植工作並跑通所有 HDFS 相關測試用例。

02

項目質量保障

這麼龐大的工程,如果僅靠單元測試來驗證,顯然大家是不放心的,所以在啓動 EC 項目時,QA 團隊就參加進來,展開了自動化集成測試工作,主要目的是保證:

爲了適應千變萬化的測試需求,我們定製了一套自動化測試系統。運用 ansible 編寫了集羣搭建系統,實現組件(NN/DN/JN),操作(安裝、卸載、啓動、停止、配置、切換、初始化),安裝包,主機,配置修改等的參數化。測試人員能夠靈活組合各種參數操作測試集羣,實現測試目的。

在功能冒煙測試和迴歸測試中,使用 pytest 編寫了 HDFS 測試框架和接口用例,以及數據校驗用例等。方便構建測試數據集,按需執行測試用例,蒐集測試用例的輸出並對比歷史數據,獲取集羣 JMX 指標驗證正確性。

通過集成 Jenkins/Docker/makefile 等工具,貫通從開發人員提交代碼,到自動化編譯,到部署測試集羣,再到冒煙測試並返回測試結果,一套完整的自動化測試流程。

03

升級與回滾

完成功能開發,測試也做的差不多之後,接下來就該上線了。但是上線不是換個包重啓下就完事了,否則集羣很可能由於一些故障和兼容性問題,導致數據損壞。因此上線要支持回退,還要在升級 YARN、客戶端等生態系統後,能寫 EC 文件,同時集羣還能像以前一樣工作,儘可能不影響用戶的使用習慣。下面我詳細介紹一下 NameNode(NN) 和 DataNode(DN) 的升級過程。

爲了支撐不停服務升級,就需要依賴 HDFS 集羣的高可用(HA)特性。在兩臺 NN(Active/Standby) 滾動升級或回滾過程中,首先需要識別出 Active/Standby 間的元數據兼容問題,然後逐個進行兼容性改造。

如 Editlog 文件和 AddCloseOp 方法中爲表示當前版本支持 EC, LayoutVersion 被設置爲 -64。如果使用 -64 寫 Editlog 會導致 NN 回滾後不認識 -64 而起不來。我們的處理辦法是在升級完成前,把 LayoutVersion 設置爲老版本 - 63,但是內部代碼可以識別 EC 元數據。還有 FsImage 文件中新增了 ErasureCodingSection,需要先改造原有的老版本,保證老版本能處理新版本回傳的 FsImage 文件,否則會導致 NPE 異常。

INode 整體格式沒有太大變化,只是重新定義了表示 replication 的 12bit 字段。replication 字段的高位爲 1 時後邊的 11 位表示 EcPolicyId。而高位爲 0,後續的 11 位表示副本。這個變化其實沒有修改佈局,因此無需特殊處理。

平滑升級示意圖

通過一些兼容性改造,我們發佈了一個過渡性質的兼容性版本,可以識別 EC 相關元數據,但是不支持讀寫 EC 文件。這樣可以確保每次上線都是可以回滾到前一個版本,但不要求能回滾到之前的歷史版本。具體升級流程:

1. 第一次使用兼容版本升級一臺 standby 狀態的 NN(LayoutVersion=-63), 並切換爲 active,測試運行一段時間,驗證該節點服務正常。如果服務有預料之外的問題,或者性能有所下降,都可以隨時進行 Active/Standby 切換,並將 Standby 節點回滾爲老版本。

2. 第二次直接將 standby 節點的老版本升級到支持 EC 功能的新版 (LayoutVersion=-64,也就是支持寫 EC 文件),並切換爲 active。如果出現問題,都可以隨時進行 Active/Standby 切換,使用到第 1 步中已經穩定運行一段時間的 NN(LayoutVersion=-63) 作爲 Active 節點。

3.  第三次使用支持 EC 功能的新版替換第 1 步中升級的 NN。

至此,線上的服務就平滑由 2.7.1 升級爲支持 EC 的 HDFS 了。整個過程經歷 3 次升級,對外沒有中止服務,外界無感知,並且整個過程是支持逐步回退。因此這個升級過程可以應用到生產環境。

滾動升級 DN 示意圖

說完 NN 上線,接下來再討論一下 DN 滾動上線。首先,解釋下爲什麼要滾動升級 DN(支持讀寫 EC 塊, 記爲 DN(EC))。第一個很顯然的原因是爲了保證 HDFS 的可用性,不影響用戶讀寫副本文件;另外一個很重要的原因是想在線上驗證 EC 功能的同時限制 EC 的故障域。

客戶端調用 addBlock 選擇 DN 去寫塊時,現有的選塊策略無法給用戶返回 DN(EC),因此會出現寫失敗。通過在 NN 上新增 ECClusterMap,構建一棵由 DN(EC) 組成的拓撲樹。經過改造後,當客戶端想要寫 EC 文件,選塊策略會從 ECClusterMap 中選取目標節點,就可以解決 DN 滾動升級過程中不支持 EC 寫入的兼容性問題,同時也可以在線上環境中小範圍驗證 DN 中 EC 功能的穩定性。

04

數據生命週期管理

業界使用 DistCp 或用 Hive 創建新表的方式把數據轉換爲 EC 存儲。這個方案需要分別運維一套 YARN 集羣和任務調度系統,並存在一些不足:

我們實現了一套基於數據生命週期的溫冷數據轉存管理系統, 來解決現有方案的不足。在 NN 內部啓動一個數據轉換管理器,週期掃描待轉換文件, 把轉換任務封裝成 FileConvertCommand,然後藉助轉換任務均衡器(ConvertTaskBalancer)選擇一個 DN, 把 FileConvertCommand 加入到這個 DN 的待轉換隊列中。當 DN 心跳過來時,會從待轉換隊列中領取一定數量的任務回去處理。詳見下圖。

EC 數據轉換流程圖

無論轉換任務是否成功,DN 都會通過心跳告知 NN 處理結果。當收到文件轉換成功的響應,NN 讀取原始文件的屬性,包括用戶組、時間戳、擴展屬性等,設置轉換後的 EC 文件。然後藉助一個臨時目錄,對原始副本文件加讀鎖,並移動到臨時目錄,然後再把轉換後的 EC 文件移動到原副本文件目錄,實現副本文件和 EC 文件的原子性交換。如果這個過程沒有異常發生,會在 checkpoint 中加一條操作日誌。如果發生異常,把原始副本文件移回原目錄,然後把該任務加入到待轉換隊列進行重做。

在轉換執行過程中,允許人爲中斷。在轉換功能異常的情況下,要確保文件不能被損壞, 服務不能中斷。NN 側通過 checkpoint 記錄操作日誌,並存放在 HDFS 系統中,確保轉換過程的冪等性。通過轉換任務均衡器把轉換任務均勻的分佈到不同的 DN,避免熱節點導致集羣性能下降。DN 側通過超時機制快速終止異常轉換任務,釋放資源處理下一個轉換任務。

通過以上手段,我們可以讓 HDFS 集羣不依賴任何其它系統獨立完成數據轉換,並能對新增數據進行實時轉換,利用容錯機制確保數據不會被重複轉換或漏轉,提供了豐富的策略對待轉換數據進行過濾,使用原子性操作爲用戶提供不間斷服務,不修改轉後的文件屬性確保轉換對於用戶是透明的。

後來,我們對這套數據生命週期管理系統進行了抽象擴展,相當於在 HDFS 內部構建了一套調度系統。除了可以支持溫冷數據轉換,還可以對數據進行比對校驗,甚至連數據清理工作也可以由這套系統進行調度。

05

全方位的數據完整性保障

HDFS-14768, HDFS-15186 還有 HDFS-15240 是同行和我們在使用 EC 過程中發現導致數據損壞的情況,可見使用新興的 EC 存在極大的數據損壞風險。正因如此,我們設計了文件級別和數據塊級別的多重校驗機制。

對於文件,在轉換初期,我們使用 EC 與副本混合存儲的策略,週期性的比對並恢復數據塊,保障數據的完整性。通過在數據生命週期管理服務中,擴充 FileConvertCommand,支持數據驗證模式。在上文提到的數據轉換過程中,把副本文件轉換爲 EC 存儲後,會立即使用 md5sum 比對副本和 EC 文件內容,確保轉換的正確性。

EC 文件的數據塊組分爲數據塊與校驗塊兩部分。客戶端讀取 EC 文件時,一般情況下只需要讀取數據塊部分。因此,在比對副本文件與 EC 文件時,無法校驗 EC 文件的校驗塊部分。爲此,我們在文件內容比對過程中,還加入了數據塊級別的驗證。調用 BlockReaderFactory 獲取 EC(RS-6-3-1024k) 塊組中的數據塊(d1-d6)與校驗塊 (p1-p3) 內容,再使用 EC 工具庫中的 CodecUtil.createRawDecoder 隨機選取 EC 塊組中(d1,d2,d4,d5,p1,p3)計算其它塊(d3’,d6’,p2’),再與前面讀取到的(d3,d6,p2)塊內容比對。

三階段 EC 數據塊校驗

經過以上兩輪比對,數據轉換的結果可以保證完全無誤。但是這種數據校驗成本很高,我們不可能在很短的週期內,重新校驗所有的數據文件。另外,副本文件不可能一直存留在集羣中,否則使用 EC 降存的意義也就不存在了。

爲此,我們基於流式計算構建了一套實時的數據塊級別的檢測機制。具體流程是檢測到數據塊在節點間發生遷移(塊重建,複製或是 balancer 都會導致塊在節點間遷移),會計算新數據塊的 md5sum, 並與舊數據塊 md5sum 進行比對,當數據塊 md5sum 發生變化後,通知集羣維護人員進行處理。這套實時數據檢測機制減少了數據校驗成本,同時提高了時效性。

在這三維一體的數據校驗與檢測機制的保駕護航下,我們的 EC 功能成功上線到生產環境。經歷了機房遷移、數據節點升級、618 和雙十一的考驗。到目前爲止運用 EC 存儲了上百 PB 溫冷數據,爲公司節省上千臺服務器成本。

06

總結與展望

在如此龐大的生產系統改造工程中,我們踩了不少坑。例如,HDFS 命令行輸出發生變更,導致用戶程序無法識別新增內容報錯;修改 Hadoop 版本號後,一些 Hive 應用使用正則表達式解析 Hadoop 版本號報錯;由於接口變化導致 TeraSort 無法運行;要先改造老版本 NN,增加 BlockReportLeaseManager,否則新版本的 DN 無法向老版本的 NN 進行全量塊彙報。這是我們趟過的比較典型的坑,主要是代碼兼容,Hadoop 生態兼容的一些事項。

在實踐過程中,我們還有一些很好的經驗總結和大家分享下。移植代碼時,一定要移植單元測試用例,可以幫助我們避免在移植過程中的疏忽導致代碼少移漏移;另外,爲了與社區代碼的兼容,儘量使用一些設計模式,如裝飾器、工廠模式、組合模式,進行代碼的改造,方便日後引入社區新功能;還有一點非常重要,在改造 RPC 接口時,務必要保證 ProtoBuf 協議的兼容性,我們在新增自定義的字段時,會預留一部分坑位應對社區代碼的擴展;對於存儲系統,最重要的事情莫過於數據的完整性,大家可以參考上面第五部分內容,結合自己的場景進行優化。

Hadoop 社區爲我們創造了優秀的存儲系統。本着人人爲我,我爲人人的開源精神,在項目改造過程中,

我們向社區回饋了數十個 patch。比較典型的改進如下:

接下來,爲了讓 EC 突破溫冷數據的使用場景。我們準備在生產環境使用 native 方法加速 EC 數據的編解碼效率,驗證功能的穩定性,並向社區回饋我們的改造。目前的 EC inside HDFS 功能已經比較穩定了,但是問題還是存在的,我們將與社區一起努力建設更加穩定的 HDFS 存儲系統。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-F0FkqpRIAGUFOBX-FVhxg