Categories
程式開發

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考


1. 上雲之路

上文(詳情戳此處)中,我們探討了雲原生的、存儲和計算分離的數據湖架構為何將會成為數倉分析技術的演進的趨勢。 從企業方的應用角度來看,對雲上的分析類產品有以下這些共同的訴求

  1. 彈性伸縮,滿足業務高峰需求的同時控制整體TCO;
  2. 企業級安全,通過產品本身的權限管控和基礎設施本身的安全來保證用戶數據安全;
  3. 和on-premise 版本一樣,在高可用、 多租戶隔離、高吞吐、負載均衡等方面令客戶滿意,且監控運維負擔較低;
  4. 自助式的產品體驗。

我們的企業級數據分析平台最早依託於Hadoop 生態建設,雖然在上述的安全性、可用性、高性能等方面已經做了不少投入,但是在雲環境中特有的安全、資源治理、存儲計算天然分離等方面的挑戰面前,我們的Hadoop 原生的數據分析平台遇到了一系列亟需解決的問題。 粗略總結下來,這些問題主要涵蓋了存儲、計算、網絡、安全、可用性和費用等方面。 我們將對這些問題做了簡單的梳理,如下圖所示。 在接下來的篇幅中,我們將主要介紹這些方面遇到的具體問題,並給出我們的思考和經驗。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 1

2. 計算:面向伸縮的計算引擎

雲上的成本包括:計算的成本、存儲的成本、網絡的成本、應用和管理成本。 其中,計算的比重相對較大。 因此,為了降低TCO,通過使用動態伸縮的虛擬機、或者Serverless 的服務提供算力,是上雲之路中的必然選擇。

Kyligence 企業級數據分析平台的主體源自Apache Kylin,也最大程度集成了它的可插拔的模塊設計。 在Apache Kylin 中,離線引擎使用的是MapReduce,在線計算引擎和存儲引擎主要使用的是HBase。 但是無論是MapReduce 還是HBase 在雲上要伸縮都不是很容易。 以HBase 為例,不僅在增加節點的時候配置複雜,難以擴容,更重要的是,在縮容的情況下,則部分Region 需要被轉移到其它繼續存活的Region Server 上,轉移期間這部分Region 處於不可用的狀態。 這對於一個需要連續保持服務高可用的生產系統來說是難以接受的。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 2

由於整體的架構是可插拔的,在雲上,我們使用Standalone Spark 替換了MapReduce,用Parquet(on object storage) 取代HBase 來存放數據。 通過監測Spark 集群的負載,系統預判需要對現有的Spark 集群進行擴容還是縮容。 對於Standalone Spark 來說,增加新的Worker,或者減少現有的Worker,只需要進行簡單的註冊,不會造成服務的中斷,因此非常適合計算資源的動態伸縮。

由於使用了Parquet on Object Storage,因此存儲也不再像HBase 那樣需要專門的節點,對象存儲作為完全託管的服務,讓我們不需要去操心存儲資源的伸縮。

關於容器化

以K8S 為代表的容器管理平台能夠集中所有的計算資源調度,能夠讓在夜間需求量較大的離線計算任務和在日間需求量較大的應用計算任務共存於同一套平台中,通過錯峰的資源使用,最大化計算資源的利用率。 同時,容器管理平台帶來的負載均衡、故障檢測和恢復、運維管理等方面的優勢也比較具有吸引力。 目前我們已經在開源社區進行相關的原型驗證,但是還並未在企業級產品中默認使用。

3. 存儲:對象存儲帶來的挑戰

在數倉都構建在on-premise 環境中的時代,HDFS 被HBASE、HIVE、PRESTO、KYLIN 等諸多數倉或分析類系統視作主要的文件存儲系統。 作為一個不完全兼容POSIX 的文件系統,HDFS 已經放棄支持了很多POSIX特性,以保持簡單高效,其中最典型的一個例子就是HDFS 的append only 的特性。 不過,HBASE、HIVE、PRESTO、KYLIN 等系統在設計的時候就很好的接納了HDFS 的這些妥協設計,在HDFS 上大家相安無事。

當這些系統要被遷移到雲上時,由於這些軟件對文件系統的訪問都是走HDFS 接口,那麼一種很自然的做法是在雲上搭建一套HDFS 文件系統來支撐。 在Databricks 的一篇文章中(https://databricks.com/blog/2017/05/31/top-5-reasons-for-choosing-s3-over-hdfs.html),作者詳細分析雲上使用對象存儲,對比在雲上搭建HDFS 的方案,在彈性、費用、可用性、持久性等方面的的全面優勢。 為了讓我們的用戶享受到低TCO 和存儲的高可用性,我們也選擇了使用對象存儲。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 3

Azure 的Blob Storage 和AWS 的S3 都是對象存儲,他們都用「對象」來代表文件和目錄。 在存儲方式上,也不是像POSIX 或者HDFS 那樣用目錄樹組織文件,而是用一種類似KV store 的形式來組織(有點像HBASE,用一個KV 代表一個文件)。 社區也早有人為對象存儲適配了HDFS 協議:目前,Hadoop 社區官方文檔上認為兼容HDFS 協議的文件系統(基上都是對象存儲)的有Aliyun OSS、Amazon S3、Azure Blob Storage、Azure Data Lake Storage(ADLS)、OpenStack Swift和Tencent COS。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 4

雖然如此,這些畢竟只是「HDFS Compatible」,在一些比較關鍵的特性上,這些偽裝成HDFS的文件系統和真實的HDFS還是存在較大差別,這些差別包括:

  1. HDFS 保證當文件被刪除、創建、重命名後,操作的影響可被所有後續調用立刻感知,即「一致性」,但是對象存儲並不一定保證;
  2. 在HDFS 中,Rename 是一個非常高效的操作,但是在對象存儲中往往是通過List + Copy + Delete來實現的,比較低效;
  3. 在HDFS 中,數據的訪問具備一定的locality,計算調度框架可以優先把計算安排在本地性更強的節點進行。 但是使用了對象存儲後,天然不具備本地性。
  4. 在HDFS 中,目錄的list 甚至遞歸的list 是一個正常的操作,但是在對象存儲中效率並不高,而且如果涉及大量的對象存儲API 調用(對對象存儲的所有訪問都是通過API調用來實現的)調用還要額外收費;
  5. HDFS 會在目錄和文件維護Timestamp 信息,且在文件操作時有比較明確的時間戳變更定義,但是對象存儲並不一定保證;
  6. 在對象存儲中,對同一個目錄下的文件的訪問有並發限制,以S3 為例,官方的說法是「You can send 3,500 PUT/COPY/POST/DELETE and 5,500 GET/HEAD requests per second per prefix in an S3 bucket 」,而HDFS 並無這種限制;
  7. HDFS 保證刪除、創建、重命名文件必須都是原子的,即「原子性」,但是對象存儲並不一定保證;
  8. 文件和目錄上需要存儲ACL 等權限信息,但是對象存儲並不一定保證。

我們將對其中的幾個主要問題進行展開:

1)一致性問題

從實際經驗來看,最容易遇到的還是一致性相關的問題。 一個最普通的例子是,前序步驟在文件系統中創建了一個文件,後續步驟去訪問它的時候找不到該文件,系統報錯。

為了解決這種一致性問題,開源社區中有S3Guard 的方案,它的大體思路是在一個獨立的DB 中維持一個Consistent View 表,來記錄文件和目錄的變化。 文件寫入成功後,會插入一條信息到表中。 當用List 操作去列舉對象存儲上面的文件時,會將結果與該表中的記錄進行對比,如果發現列舉結果不完整,就會等待一段時間再去列舉,直到二者信息一致才會繼續其他操作。 這種方式的缺點也比較明顯,即可能帶來性能的嚴重下降,同時也可能會增加一筆可觀的DB 成本。 針對這個問題,我們梳理了我們產品中所有可能受到一致性影響的點,發現所有的問題都能通過在HDFS 客戶端植入簡單的檢查-等待-重試策略來規避,這種做法也與AWS EMRFS 中HDFS 客戶端的行為接近。 採用這種簡單的策略,我們既擺脫了對象存儲一致性的問題,又避免了單獨維護一個Consistent View 的成本。

2)Rename 問題

如上文所述,Kyligence 使用Spark 加工數據,並把加工好的數據存儲到對象存中供分析使用。 無論是Hadoop 還是Spark,他們在運行每個作業的時候都有多個Task 並行獨立地輸出結果文件。 在每個作業的結果輸出上,Spark 需要考慮2 個問題:

  1. Task 可能運行失敗,即每個Task 對應一次或者多次Task Attempt,更糟糕的是,同一個Task 的不同Attempt 可能會同時執行。 因此,不同Task Attempt的輸出不能混在一起,Spark 最後只認成功的那次Task Attempt 所輸出的結果;
  2. Job 可能運行失敗並且重跑,不同Job Attempt 的輸出不能混在一起。

因此,一直以來Spark 都是使用下圖(圖片來自CSDN )所示的FileOutputCommitter,這種Committer 會為每個Task Attempt 創建一個臨時的目錄(1),在Task Attempt 成功後對它的輸出目錄進行重命名(2)。 同時,整個Job 的輸出目錄一開始也是一個臨時目錄中,在Job 完成時,對Job 的輸出目錄進行重命名(3)。

在這種模式下,每個Task 的輸出都要經歷兩輪Rename 才行。 當有大量Task 寫入時,即使所有Task 都完成了,還需要等待很長一段時間Job 才能結束,這個時間主要花在Driver 端做第二次Rename上。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 5

於是需要想辦法優化這兩次Rename。 這裡有一個細節,不同於Task,同一個Job 不會在同時有並行的Attempt。 因此Spark 可以選擇下圖所示的新的FileOutputCommitterV2,在這種模式下,不再為Job 的數據目錄設定一個臨時目錄,每個Task Attempt 在成功的時候直接把目錄Rename 到最終的目錄中。 這樣就把每個Task 輸出目錄的2次Rename 簡化成了一次Rename。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 6

這種做法也有一些弊端:在v2 中,如果部分Task 已執行成功,而此時Job 失敗了,就會導致有部分數據已經對外可見了,需要數據的消費者自己根據是否有_SUCCESS 標記來判斷其完整性。 不過這個問題對我們的產品來說不是主要問題。

3)本地性問題

對象存儲的本地性的缺失,主要影響了延遲敏感的分析查詢類請求的體驗。 為了改進訪問的性能,我們使用了Alluxio 來作為數據緩存層。 Alluxio 在每個Worker 節點上預留一部分內存作為緩存的存儲空間,其內置的換入換出算法可以幫助最大化有限的內存資源的利用。 如果分析請求所需的數據文件恰好已經被Alluxio緩存,則直接使用緩存中的數據處理,而不用再訪問對象存儲。 對於一次性讀寫的場景,增加緩存層並不會有明顯的效果,就不再試圖通過Alluxio 加速。

上面的結論對應到我們雲上的部署架構圖,可以看到在以分析查詢請求為主的Read Cluster 中,每台Spark Worker 所在的節點都安裝了Alluxio 實例,而以一次性讀寫作業為主的Write Cluster 中則沒有安裝Alluxio 實例。

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考 7

4. 小結

在本文中,我們先初步歸納了六個考慮要素中的計算和存儲兩大要素。 在下一篇文章中,我們會進一步展開剩下的網絡、安全、可用性和費用相關的見解和思考。

作者介紹

馬洪賓

Kyligence 創始合夥人& 研發副總裁

Apache Kylin PMC成員

專注於大數據相關的基礎架構和平台設計。 曾經是微軟亞洲研究院的圖數據庫Trinity 的核心貢獻者。 作為Kyligence 企業級產品的研發負責人,幫助客戶從傳統數據倉庫升級到雲原生的、低TCO 的現代數據倉庫。

吳毅華

Kyligence 雲產品研發總監

專注於雲原生與數據倉庫的結合和實踐,8 年雲計算與DevOps 領域的資深技術及管理經驗。 前嘉銀金科運維總監,前eBay PaaS 中國區核心成員,前攜程私有云創始成員。

本文轉載自公眾號Kyligence(ID:Kyligence)。

原文鏈接

從Hadoop 到雲原生(2):Kyligence 在雲原生巨浪中的思考