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

1. 上雲之路

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

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

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

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

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

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

由於整體的架構是可插拔的,在雲上,我們使用 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 和存儲的高可用性,我們也選擇了使用對象存儲。

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。

雖然如此,這些畢竟只是「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上。

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

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

3)本地性問題

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

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

4. 小結

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

作者介紹

馬洪賓

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

Apache Kylin PMC Member

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

吳毅華

Kyligence 雲產品研發總監

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

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

原文鏈接

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章