大規模 Hadoop 升級在 Pinterest 的實踐

ITPUB社群發表於2022-11-30

大規模 Hadoop 升級在 Pinterest 的實踐

Monarch 是 Pinterest 的批處理平臺,由30多個 Hadoop YARN 叢集組成,其中17k+節點完全建立在 AWS EC2 之上。2021年初,Monarch 還在使用五年前的 Hadoop 2.7.1。由於同步社群分支(特性和bug修復)的複雜性不斷增加,我們決定是時候進行版本升級了。我們最終選擇了Hadoop 2.10.0,這是當時 Hadoop 2 的最新版本。

本文分享 Pinterest 將 Monarch 升級到 Hadoop 2.10.0 的經驗。為了簡單起見,我們將 Hadoop 2.10.0 簡稱為 Hadoop 2.10;將 Hadoop 2.7.1 簡稱為 Hadoop 2.7。

挑戰

自 Pinterest 的批處理平臺開始(大約 2016 年)以來,我們一直在使用 Hadoop 2.7。隨著時間的推移,我們的平臺處理的工作負載不斷增長和發展。為了滿足這些需求,我們進行了數百次內部更改。這些內部補丁中的大多數都是 Pinterest 特有的,需要大量時間投入才能將它們移植到 Hadoop 2.10。

大多數最關鍵的批處理工作負載是在 Monarch 上執行,因此我們的首要任務是以不會對這些工作負載造成叢集停機或效能/SLA 影響的方式執行升級。

升級策略

由於許多使用者定義的應用程式與 Hadoop 2.7 緊密耦合,我們決定將升級過程分為兩個獨立的階段。第一階段是平臺本身從 Hadoop 2.7 升級到 Hadoop 2.10,第二階段是使用者自定義應用升級到 2.10。

在升級的第一階段,我們允許使用者的作業繼續使用 Hadoop 2.7 的依賴項,同時我們專注於平臺升級。這增加了額外的開銷,因為我們需要使 Hadoop 2.7 作業與 Hadoop 2.10 平臺相容,但它會讓我們有更多的時間來處理第二階段。

由於平臺和使用者應用程式的規模,上述兩個階段都需要逐步完成:

我們需要一個一個地升級 Monarch 裡面的叢集;我們需要將使用者應用程式批次升級到與 2.10 繫結,而不是 2.7。

當時,我們沒有一個靈活的構建管道來允許我們構建兩個不同版本的作業,這些作業具有單獨的 hadoop 依賴項。同樣,我們要求所有使用者(公司中的其他工程師)單獨驗證從 2.7 到 2.10 的 10,000 多個單獨的工作遷移是不合理的。為了支援上述增量升級,我們需要在遷移之前執行許多驗證,以確保使用 Hadoop 2.7 構建的絕大多數應用程式將繼續在 2.10 叢集中工作。

我們為升級過程制定的步驟如下:

Hadoop 2.10 釋出準備:將 Hadoop 2.7 內部分支上的所有補丁移植到 Apache Hadoop 2.10 上;一個一個的升級 Monarch 叢集到 Hadoop 2.10 ;批次升級使用者應用程式以使用 Hadoop 2.10。

Hadoop 2.10 釋出準備

Pinterest 使用的 Hadoop 2.7 版本包括在開源 Hadoop 2.7 之上進行的許多內部更改,這些更改需要移植到 Hadoop 2.10。但是,Hadoop 2.7 和 Hadoop 2.10 之間發生了重大變化。因此,將 Pinterest Hadoop 2.7 的更改應用到社群的 Hadoop 2.10 上並非易事。

下面是一些我們在 Hadoop 2.7 上做的內部補丁,然後移植到 Hadoop 2.10 的例子:

Monarch 建立在 EC2 之上,並使用 S3 作為持久儲存,Task 的輸入和輸出通常在 S3 上。Pinterest 實現了 DirectOutputFileCommitter 以使 Task 能夠直接將結果寫入目標位置,以避免在 S3 中複製結果檔案的開銷;新增 application master 和 history server 的 endpoint 以獲取給定作業的所有任務的特定計數器的值;在為 container log 提供服務時實現了範圍查詢,它允許獲取指定 container log 的一部分;為日誌聚合新增 Node-Id 分割槽,使得叢集不同節點的日誌可以寫入不同的 S3 分割槽,避免達到 S3 訪問速率限制。新建立的 namenode 可能有不同的 IP 地址,我們實現了一個功能,以解決NN RPC套接字地址故障轉移。如果分配的 mappers 數量與總 mappers 的比率超過配置的閾值,則禁用 preempting reducers。將磁碟使用監控執行緒新增到 AM,這樣如果磁碟使用超過配置的限制,應用程式將被終止。

升級 Monarch 中的叢集到 Hadoop 2.10

叢集升級方法探索

我們評估了將 Monarch 叢集升級到 Hadoop 2.10 的多種方法。每種方法都有其優缺點,我們將在下面概述。

方案一:使用 CCR(cross-cluster routing,跨叢集路由)

正如在《Efficient Resource Management》 一文提到的,我們開發了跨叢集路由(CCR)來平衡不同叢集之間的工作負載。為了儘量減少對現有 2.7 叢集的影響,一種選擇是構建新的 Hadoop 2.10 叢集,並逐步將工作負載轉移到新的叢集。如果出現任何問題,我們可以將工作負載路由回原來的叢集,修復問題,然後再次路由回2.10叢集。

我們開始使用這種方法,並在一些小的生產和開發叢集上進行評估。沒有什麼大問題,但我們發現了一些缺點:

我們必須為每個叢集遷移構建一個新的並行叢集,對於大型 YARN 叢集(多達數千個節點),這會變得很昂貴需要批次遷移工作負載,這非常耗時。因為 Monarch 是一個非常大的平臺,這個升級過程可能需要很長時間才能完成。

方案二:滾動升級(Rolling Upgrade)

理論上,我們可以嘗試滾動升級 Worker 節點,但滾動升級可能會影響叢集上的所有工作負載,如果我們遇到任何問題,回滾將是昂貴的。


方案三:原地升級(In-place Upgrade)

我們利用類似的方法將叢集從一個例項型別升級到另一個例項型別,我們:

將幾個新例項型別的 canary 主機作為新的自動縮放組(canary ASG)節點插入叢集評估相對於 base ASG(現有例項型別)的 canary ASG擴大 canary ASG縮小 base ASG

一般來說,這對於沒有服務級別更改(service level change)的小型基礎設施非常有效。作為探索,我們想看看 Hadoop 2.10 的升級是否也能做到這一點。我們必須做出的一個關鍵假設是 Hadoop 2.7 和 2.10 元件之間的通訊是相容的。這種方法的步驟是:

在 Hadoop 2.7 叢集中新增 Hadoop 2.10 worker 節點(執行 HDFS datanode 和 YARN NodeManager);發現並解決出現的問題;增加 Hadoop 2.10 worker 節點的數量,減少 Hadoop 2.7 worker 節點的數量,直到2.7節點完全替換為2.10節點升級所有管理節點(namenode, JournalNodes, resourcemanager, History Servers等)。這個過程的工作原理類似於用 Hadoop 2.10 節點替換 worker 節點。

在將這種有風險的方法應用到生產叢集之前,我們對生產環境的 Monarch 叢集進行了廣泛的評估。這是一個無縫的升級體驗,除了一些小問題,我們將在後面描述。

最終升級方案

如前所述,業務作業最初是用 Hadoop 2.7 依賴項構建的。這意味著它們可以將 Hadoop 2.7 jar 檔案攜帶到分散式快取中。然後在執行時,我們將使用者類路徑放在叢集中存在的庫路徑之前。這可能會導致 Hadoop 2.10 節點的依賴問題,因為 Hadoop 2.7 和 2.10 可能依賴不同版本的第三方 jar。

在使用方法一對一些小叢集進行升級後,我們認為這種方法將花費太長時間來完成所有 Monarch 叢集的升級。此外,考慮到我們最大的 Monarch 叢集的規模(多達3k個節點),我們無法在這麼短的時間內獲得足夠的 EC2 例項來替換這些叢集。我們評估了優點和缺點,並決定採用方法三,因為我們可以大大加快升級過程,並且可以快速解決大多數依賴問題。如果我們不能快速解決某些作業的問題,我們可以使用 CCR 將作業路由到另一個 Hadoop 2.7 叢集,然後花時間修復問題。

遇到的問題及解決方案

在我們最終確定了方法三之後,我們的主要關注點就變成了識別任何問題並儘快解決它們。從廣義上講,我們遇到了三類問題:由於 Hadoop 2.7 和 Hadoop 2.10 之間的不相容導致的服務級別問題、使用者定義的應用程式中的依賴性問題以及其他各種問題。

不相容的行為問題

重啟 Hadoop 2.10 NM 會導致容器被殺死。我們發現 Hadoop 2.10 引入了一個預設值為 FALSE 的新配置 yarn.nodemanager.recovery.supervised。為了防止容器在重新啟動 NMs 時被殺死,我們需要將其設定為TRUE。當啟用此配置時,執行中的 NodeManager 不會嘗試清理容器,因為它會假設立即重啟並恢復容器。當 AM 在 2.10 節點上排程時,作業可能會卡死:MAPREDUCE-6515 中新增的 Application Priority 假設該欄位總是在 PB (protobuf)響應中設定。在多版本的叢集(2.7.1 ResourceManager + 2.10 worker)中則不是這樣,因為 RM 返回的 PB 響應將不包含 appPriority 欄位。我們檢查這個欄位是否在 protobuf 中,如果不在,則忽略更新 applicationPriority。HADOOP-13680 使 fs.s3a.readahead.range 從 Hadoop 2.8 開始使用 getLongBytes,並支援“32M”格式的值(記憶體字尾 K、M、G、T、P)。但是,Hadoop 2.7 程式碼無法處理這種格式。這會破壞混合 Hadoop 版本叢集中的作業。我們為 Hadoop 2.7 新增了一個修復程式,以使其與 Hadoop 2.10 行為相容。Hadoop 2.10 不小心在 io.serialization 配置的多個值之間引入了空格,這導致了 ClassNotFound 錯誤。我們進行了修復以刪除配置值中的空格。

依賴問題

當我們執行 Hadoop 2.7 到 2.10 的就地升級時,我們面臨的大多數依賴問題是由於 Hadoop 服務和使用者應用程式之間共享的不同版本的依賴關係造成的。解決方案是修改使用者的作業以與 Hadoop 平臺依賴項相容,或者在作業或 Hadoop 平臺分發版中設定版本號。以下是一些例子:

Hadoop 2.7 jars 被放入分散式快取並導致 Hadoop 2.10 節點上的依賴問題。我們在 Hadoop 2.7 版本中實現了一個解決方案,以防止將這些 jars 新增到分散式快取中,以便所有主機都使用已部署到主機的 Hadoop jars。woodstox-core 包:Hadoop-2.10.0 依賴於 woodstox-core-5.0.3.jar,而一些應用程式依賴於 wstx-asl-3.2.7.jar 的模組。woodstox-core-5.0.3.jar 和 wstx-asl-3.2.7.jar 之間的不相容導致了作業失敗。我們的解決方案是在 Hadoop 2.10 中遮蔽 woodstox-core-5.0.3.jar。我們有一些基於 Hadoop 2.7 實現的內部庫或類。它們不能在 Hadoop 2.10 上執行。例如,我們有一個名為 S3DoubleWrite 的類,它同時將輸出寫到 s3 的兩個位置。它的開發是為了幫助我們在3個桶之間遷移日誌。因為我們不再需要那個類了,所以直接刪除它即可。一些 Hadoop 2.7 庫被打包到使用者的 bazel jar 中,在執行時導致一些依賴問題。我們採取的解決方案是將使用者應用程式與 Hadoop jar 解耦,更多的細節可以在後面的相關章節中找到。

各種各樣的其他問題

我們在開發叢集上執行的驗證之一是確保在升級過程的中可以回滾。當我們試圖回滾 NameNode 到 Hadoop 2.7 時,出現了一個問題。我們發現 NameNode 沒有收到來自升級的 datanode 的塊報告。我們確定的解決方法是手動觸發塊報告。我們後來發現了潛在的問題 HDFS-12749 (DN 可能不會在 NN 重啟後向 NN 傳送阻塞報告),並將其移植到我們內部分支。當 Hadoop streaming 作業與 Hadoop 2.7 jar 捆綁部署到 Hadoop 2.10 節點時,預期的 2.7 jar 不可用。這是因為我們使用叢集提供的 jar 來滿足大多數使用者作業的依賴關係,從而減少作業的大小。然而,所有的 Hadoop 依賴都在 jar 名稱中編碼了版本。解決方案是讓 Hadoop streaming 作業包的 Hadoop jar 不帶版本字串,這樣提供的 Hadoop 依賴項在執行時總是在類路徑中,而不管它執行在 Hadoop 2.7 或 2.10 的節點上。

將使用者程式升級到 Hadoop 2.10

為了將使用者應用程式升級到 Hadoop 2.10,我們需要確保在編譯時和執行時都使用 Hadoop 2.10。第一步是確保 Hadoop 2.7 jar 不是隨使用者 jar 一起提供的,以便在執行時使用部署到叢集中的 Hadoop jar (2.7 版本的節點有 2.7 版本的 jar, 2.10 版本的節點有 2.10 版本的jar)。然後我們將使用者應用程式構建環境改為使用 Hadoop 2.10 而不是2.7。

將使用者應用程式與 Hadoop jar 解耦

在 Pinterest,大多數資料管道都使用 Bazel 構建的 fat jars。這些 jar 包含了升級前的所有依賴關係,包括 Hadoop 2.7 客戶端庫。我們總是優先使用那些 fat jar 中的類而不是本地環境中的類,這意味著在使用 Hadoop 2.10 的叢集上執行這些 fat jar 時,我們仍將使用 Hadoop 2.7 類。

為了解決這個問題(在2.10叢集中使用 2.7 jar),我們決定將使用者的 Bazel jar 從 Hadoop 庫中解耦;也就是說,我們不再將 Hadoop jar 放在 fat user Bazel jar 中,已經部署到叢集節點的 Hadoop jar 將在執行時使用。

Bazel java_binary 規則有一個名為 deploy_env 的引數,它的值是表示此二進位制的部署環境的其他 java_binary 目標的列表。我們設定這個屬性是為了從使用者 jar 中排除所有 Hadoop 依賴及其子依賴。這裡的挑戰在於,許多使用者應用程式對 Hadoop 所依賴的庫有共同的依賴關係。這些常見的庫很難識別,因為它們沒有明確指定,因為它們已經作為 NodeManager 部署的一部分在 Hadoop worker 本身上提供了。在測試期間,我們花了很多精力來識別這些型別的情況,並修改使用者的 bazel 規則,以顯式地新增那些隱藏的依賴項。

將 Hadoop bazel targets 從 2.7 升級到 2.10

在將使用者應用程式與 Hadoop Jars 解耦後,我們需要將 Hadoop bazel targets 從 2.7 升級到 2.10,以便我們可以確保構建和執行時環境中使用的 Hadoop 版本是一致的。在這個過程中,Hadoop 2.7 和 Hadoop 2.10 之間又出現了一些依賴衝突。我們透過構建測試確定了這些依賴項,並相應地將它們升級到正確的版本。

總結

將 17k 多個節點從一個 Hadoop 版本升級到另一個 Hadoop 版本,同時又不會對應用程式造成重大破壞,這是一個挑戰。我們以高質量、合理的速度和合理的成本效益做到了這一點。我們希望以上分享的經驗對社群有益。

本文翻譯自:Large Scale Hadoop Upgrade At Pinterest

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2925872/,如需轉載,請註明出處,否則將追究法律責任。

相關文章