資料中臺:資料服務的架構設計實踐

陶然陶然發表於2022-11-09

  導讀:資料服務是資料中臺體系中的關鍵組成部分。作為數倉對接上層應用的統一出入口,資料服務將數倉當作一個統一的 DB 來訪問,提供統一的 API 介面控制資料的流入及流出,能夠滿足使用者對不同型別資料的訪問需求。

  電商平臺唯品會的資料服務自 2019 年開始建設,在公司內經歷了從無到有落地,再到為超過 30+業務方提供To B、To C 的資料服務的過程。本文主要介紹唯品會自研資料服務Hera相關背景、架構設計和核心功能。

   01 背景介紹

  在統一數倉資料服務之前,數倉提供的訪問接入方式往往存在效率問題低、資料指標難統一等問題,具體而言有以下幾個比較突出的情況:

  廣告人群 USP、DMP 系統每天需要透過 HiveServer 以流的方式從數倉匯出資料到本地,每個人群的資料量從幾十萬到幾個億,人群數量 2w+,每個人群執行時間在 30min +,部分大人群的執行直接超過 1h,在資源緊張的情況下,人群延遲情況嚴重。

  數倉的資料在被資料產品使用時,需要為每個表新生成一個單獨的介面,應用端需要為每一種訪問方式(如 Presto、ClickHouse)區分使用不同的介面,導致資料產品介面暴漲,不方便維護,影響開發及維護效率。資料在不同的儲存時,需要包含 clickhouse-client,presto-client 等等第三方 jar 包。

  不同資料產品中都需要使用一些常用的資料指標,如銷售額、訂單數、PV、UV 等,而這些資料在不同資料產品的實現口徑、實現方式都不一樣,無法形成資料共享,每個資料產品都重複進行相同的指標建設。因此,在不同資料產品檢視相同指標卻發現數值不同的情況下,難以判斷哪個資料產品提供的資料是準確的。  

圖 1.資料流入流出方式

  為解決以上問題,資料服務應運而生。目前資料服務的主要優勢有:遮蔽底層的儲存引擎、計算引擎,使用同一個 API(one service),數倉資料分層儲存,不同Engine 的 SQL 生成能力,自適應 SQL 執行以及統一快取架構保障業務 SLA,支援資料註冊並授權給任何呼叫方進行使用,提高資料交付效率。

  透過唯一的 ID 標識,資料產品可透過 ID 查閱資料,而非直接訪問對應的數倉表。一方面,指標服務統一了指標的口徑,同時也支援快速構建新的資料產品。

   02 架構設計

  資料服務能給業務帶來運營和商業價值,核心在於給使用者提供自助分析資料能力。Hera整體架構基於典型的 Master/slave 模型,資料流與控制流單獨鏈路,從而保障系統的高可用性。資料服務系統主要分為三層:

  應用接入層:業務申請接入時,可以根據業務要求選擇資料服務 API(TCP Client),HTTP 以及 OSP 服務介面(公司內部 RPC 框架)。

  資料服務層:主要執行業務提交的任務,並返回結果。主要功能點包括:路由策略,多引擎支援,引擎資源配置,引擎引數動態組裝,SQL Lispengine 生成,SQL 自適應執行,統一資料查詢快取,FreeMaker SQL 動態生成等功能。

  資料層:業務查詢的資料無論在數倉、Clickhouse、MySQL 還是 Redis 中,都可以很好地得到支援,使用者都使用同一套 API。  

圖 2. 資料服務整體架構圖

  排程系統的整體流程大致包含以下模組:

  Master:負責管理所有的 Worker、TransferServer、AdhocWorker 節點,同時負責排程分發作業;

  Worker:負責執行 ETL 和資料檔案匯出型別的作業,拉起 AdhocWorker 程式(Adhoc 任務在 AdhocWorker 程式中的執行緒池中執行),ETL 型別的作業透過子程式的方式完成;

  Client:客戶端,用於程式設計式地提交 SQL 作業;

  ConfigCenter:負責向叢集推送統一配置資訊及其它執行時相關的配置和 SQLParser (根據給定的規則解析、替換、生成改寫 SQL 語句,以支援不同計算引擎的執行);

  TransferServer:檔案傳輸服務。  

圖 3. 資料服務排程流程圖

   03 主要功能

  Hera 資料服務的主要功能有:多佇列排程策略、多引擎查詢、多工型別、檔案匯出、資源隔離、引擎引數動態組裝、自適應 Engine 執行和 SQL 構建。

  多佇列排程策略

  資料服務支援按照不同使用者、不同任務型別並根據權重劃分不同排程佇列,以滿足不同任務型別的 SLA。

  多引擎查詢

  資料服務支援目前公司內部所有 OLAP 和資料庫型別,包括 Spark、Presto、Clickhouse、Hive 、MySQL、Redis。會根據業務具體場景和要求,選擇當前合適的查詢引擎。

  多工型別

  資料服務支援的任務型別有:ETL、Adhoc、檔案匯出、資料匯入。加上多引擎功能,實現多種功能組合,如 Spark adhoc 和 Presto adhoc。

  檔案匯出

  主要是支援大量的資料從資料倉儲中匯出,便於業務分析和處理,比如供應商發券和資訊推送等。

  具體執行過程如下:使用者提交需要匯出資料的 SQL,透過分散式 engine 執行完成後,落地檔案到 hdfs/alluxio. 客戶端透過 TCP 拉取檔案到本地。千萬億級的資料匯出耗時最多 10min。資料匯出在人群資料匯出上效能由原來的 30min+ ,提升到最多不超過 3min,效能提升 10~30 倍。具體流程如下:  

圖 4. 資料服務檔案下載流程圖

  資源隔離(Worker 資源和計算資源)

  業務一般分為核心和非核心,在資源分配和排程上也不同。主要是從執行任務 Worker 和引擎資源,都可以實現物理級別的隔離,最大化減少不同業務之間相互影響。

  引擎引數動態組裝

  線上業務執行需要根據業務情況進行調優,動態限制使用者資源使用,叢集整體切換等操作,這個時候就需要對使用者作業引數動態修改,如 OLAP 引擎執行任務時,經常都要根據任務調優,設定不同引數。針對這類問題,資料服務提供了根據引擎型別自動組裝引擎引數,並且引擎引數支援動態調整,也可以針對特定任務、執行賬號、業務型別來設定 OLAP 引擎執行引數。

  自適應 Engine 執行

  業務方在查詢時,有可能因為引擎資源不足或者查詢條件資料型別不匹配從而導致執行失敗。為了提高查詢成功率和服務 SLA 保障,設計了 Ad Hoc 自適應引擎執行,當一個引擎執行報錯後,會切換到另外一個引擎繼續執行。具體自適應執行邏輯如下圖所示:  

圖 5. 自適應 Engine 執行

  SQL構建

  資料服務 SQL 構建基於維度事實建模,支援單表模型、星型模型和雪花模型。

  單表模型:一張事實表,一般為 DWS 或者 ADS 的彙總事實表。

  星型模型:1 張事實表(如 DWD 明細事實表)+ N 張維表,例如訂單明細表 (事實表 FK=商品 ID) + 商品維表 (維度表 PK=商品 ID) 。

  雪花模型:1 張事實表(如 DWD 明細事實表)+ N 張維表+M 張沒有直接連線到事實表的維表,例如訂單明細表 (事實表 FK=商品 ID) + 商品維表 (維度表 PK=商品 ID,FK=品類 ID) + 品類維表(維度表 PK=品類 ID)。  

圖 6.SQL 維度模型

  任務排程

  基於 Netty 庫收發叢集訊息,系統僅僅使用同一個執行緒池物件 EventLoopGroup 來收發訊息,而使用者的業務邏輯,則交由一個單獨的執行緒池。

  選用 Netty 的另外一個原因是“零複製”的能力,在大量資料返回時,透過檔案的形式直接將結果送給呼叫者。

  多佇列+多使用者排程

  業務需求通常包含時間敏感與不敏感作業,為了提高作業的穩定性和系統的可配置性,Hera 提供了多佇列作業排程的功能。

  使用者在提交作業時可以顯式地指定一個作業佇列名,當這個作業在提交到叢集時,如果相應的佇列有空閒,則就會被新增進相應的佇列中,否則返回具體的錯誤給客戶端,如任務佇列滿、佇列名不存在、佇列已經關閉等,客戶端可以選擇“是否重試提交”。

  當一個作業被新增進佇列之後,Master 就會立即嘗試排程這個佇列中的作業,基於以下條件選擇合適的作業執行:

  每個佇列都有自己的權重,同時會設定佔用整個叢集的資源總量,如最多使用多少記憶體、最多執行的任務數量等。

  佇列中的任務也有自己的權重,同時會記錄這個作業入隊的時間,在排序當前佇列的作業時,利用入隊的時間偏移量和總的超時時間,計算得到一個最終的評分。

  除了排程系統本身的排程策略外,還需要考慮外部計算叢集的負載,在從某個佇列中拿出一個作業後,再進行一次過濾,或者是先過濾,再進行作業的評分計算。

  一個可用的計算作業評分模型如下:

  佇列動態因子 = 佇列大小 / 佇列容量 * (1 - 作業執行數 / 佇列並行度)

  這個等式表示的意義是:如果某個佇列正在等待的作業的佔比比較大,同時並行執行的作業數佔比也比較大時,這個佇列的作業就擁有一個更大的因子,也就意味著在佇列權重相同時,這個佇列中的作業應該被優先排程。

  作業權重 = 1 - (當前時間-入隊時間) / 超時時間

  這個等式表示的意義是:在同一個佇列中,如果一個作業的剩餘超時時間越少,則意味著此作業將更快達到超時,因此它應該獲得更大的選擇機會。

  Score = 作業權重 + 佇列動態因子 + 佇列權重

  這個等式表示的意義是:對於所有的佇列中的所有任務,首先決定一個作業是否優先被排程的因子是設定的佇列權重,例如權重為 10 的佇列的作業,應該比權重為 1 的佇列中的作業被優先排程,而不管作業本身的權重(是否會有很大的機率超時);其次影響作業排程優先順序的因子是佇列動態因子,例如有兩個相同權重的佇列時,如果一個佇列的動態因子為 0.5,另外一個佇列的動態因子是 0.3,那麼應該優先選擇動態因子為 0.5 的佇列作業進行排程,而不管作業本身的權重;最後影響作業排程優先順序的因子是作業權重,例如在同一個佇列中,有兩個權重分別為 0.2 和 0.5 的作業,那麼為了避免更多的作業超時,權重為 0.2 的作業應該被優先排程。

  簡單描述作業的排序過程就是,首先按佇列權重排序所有的佇列;對於有重複的佇列,則會計算每個佇列的動態因子,並按此因子排序;對於每一個佇列,作業的排序規則按作業的超時比率來排序;最終依次按序遍歷每一個佇列,嘗試從中選擇足夠多的作業執行,直到作業都被執行或是達到叢集限制條件。這裡說足夠多,是指每一個佇列都會有一個最大的並行度和最大資源佔比,這兩個限制佇列的引數組合,是為了避免因某一個佇列的容量和並行度被設定的過大,可能超過了整個叢集,導致其它佇列被“餓死”的情況。

  SQL作業流程

  使用者透過 Client 提交原始 SQL,這裡以 Presto SQL 為例,Client 在提交作業時,指定了 SQL 路由,則會首先透過訪問 SQLParser 服務,在傳送給 Master 之前,會首先提交 SQL 語句到 SQLParser 伺服器,將 SQL 解析成後端計算叢集可以支援的 SQL 語句,如 Spark、Presto、ClickHouse 等,為了能夠減少 RPC 互動次數,SQLParser 會一次返回所有可能被改寫的 SQL 語句。

  在接收到 SQLParser 服務返回的多個可能 SQL 語句後,就會填充當前的作業物件,真正開始向 Master 提交執行。

  Master 在收到使用者提交的作業後,會根據一定的排程策略,最終將任務分發到合適的 Worker 上,開始執行。Worker 會首先採用 SQL 作業預設的執行引擎,比如 Presto,提交到對應的計算叢集執行,但如果因為某種原因不能得到結果,則會嘗試使用其它的計算引擎進行計算。當然這裡也可以同時向多個計算叢集提交作業,一旦某個叢集首先返回結果時,就取消所有其它的作業,不過這需要其它計算叢集的入口能夠支援取消操作。

  當 SQL 作業完成後,將結果返回到 Worker 端,為了能夠更加高效地將查詢結果返回給 Client 端,Worker 會從 Master 傳送的任務物件中提取 Client 側資訊,並將結果直接傳送給 Client,直到收到確認資訊,至此整個任務才算執行完畢。

  在整個作業的流轉過程中,會以任務的概念在排程系統中進行傳播,並經歷幾個狀態的更新,分別標識 new、waiting、running、succeed、failed 階段。  

圖 7. SQL 作業處理流程

  Metrics 採集

  資料服務蒐集兩類 metrics,一類靜態的,用於描述 master/worker/client 的基本資訊;一類是動態的,描述 master/worker 的執行時資訊。這裡主要說明一下有關叢集動態資訊的採集過程及作用。以 worker 為例,當 worker 成功註冊到 master 時,就會開啟定時心跳彙報動作,並借道心跳請求,將自己的執行時資訊彙報給 master。這裡主要是記憶體使用情況,例如當前 worker 透過估算方法,統計目前執行的任務佔據了多少記憶體,以便 master 能夠在後續的任務分發過程中,能夠根據記憶體資訊進行決策。master 會統計它所管理的叢集整個情況,例如每個任務佇列的快照資訊、worker 的快照資訊、叢集的執行時配置資訊等,並透過引數控制是否列印這些資訊,以便除錯。

  解決的效能問題

  資料服務主要解決 SLA 方面的問題。如人群計算、資料無縫遷移、資料產品 SLA 等,這裡用人群舉例說明如下:

  人群計算遇到的問題:

  人群計算任務的資料本地性不好;

  HDFS 存在資料熱點問題;

  HDFS 讀寫本身存在長尾現象。

  資料服務改造新的架構方案:

  計算與儲存同置,這樣資料就不需透過網路反覆讀取,造成網路流量浪費。

  減少 HDFS 讀寫長尾對人群計算造成的額外影響,同時減少人群計算對於 HDFS 穩定性的影響。

  廣告人群計算介於線上生產任務跟離線任務之間的任務型別。這裡我們希望能保證這類應用的可靠性和穩定性,從而更好地為公司業務賦能

  透過資料服務執行人群計算。 

圖 8. Alluxio 和 Spark 叢集混部

  基於 Alluxio 的快取表同步

  將 Hive 表的 location 從 HDFS 路徑替換為 Alluxio 路徑,即表示該表的資料儲存於 Alluxio 中。我們使用的方案不是直接寫透過 ETL 任務寫 Alluxio 表的資料,而是由 Alluxio 主動去拉取同樣 Hive 表結構的 HDFS 中的資料,即我們建立了一個 HDFS 表的 Alluxio 快取表。

  由於 Alluxio 不能感知到分割槽表的變化,我們開發了一個定時任務去自動感知源表的分割槽變化,使得 Hive 表的資料能夠同步到 Alluxio 中。

  具體步驟如下:

  定時任務發起輪詢,檢測源表是否有新增分割槽。

  發起一個 SYN2ALLUXIO 的任務由資料服務執行。

  任務執行指令碼為將 Alluxio 表新增與 HDFS 表相同的分割槽。

  分割槽新增完成之後,Alluxio 會自動從 mount 的 HDFS 路徑完成資料同步。  

圖 9. Alluxio 快取表同步

  人群計算任務

  上小節介紹瞭如何讓 Alluxio 和 HDFS 的 Hive 表保持資料同步,接下來需要做的就是讓任務計算的 Spark 任務跑在 Spark 與 Alluxio 混部的叢集上,充分利用資料的本地性以及計算資源的隔離性,提高人群計算效率。

  人群服務透過呼叫資料服務執行。資料服務根據底表分割槽是否同步到 Alluxio 決定是否需要下推是用 Alluxio 表來完成計算。如果底表資料已經同步到 Alluxio,則使用 Alluxio 表來做為底表計算人群。

  依靠資料服務排程系統,透過使用者 SQL 改寫以及 Alluxio 和 Spark 計算結點混部模式,人群計算任務提速了 10%~30%。

   小結

  雖然截至今天,Hera 資料服務已經支援了很多生產業務,但目前仍有很多需要完善的地方:

  不同 engine 存在同一個含義函式寫法不一致的情況。這種情況在 Presto 跟 ClickHouse 的函式比較時尤為突出,如 Presto 的 strpos(string, substring)函式,在 Clickhouse 中為 position(haystack, needle[, start_pos]),且這些函式的引數順序存在不一致的情況,如何更優雅地支援不同 engine 的差異情況還需要進一步思考。

  人群計算採用業界通用的 ClickHouse BitMap 解決方案落地,提升人群的計算效率同時擴充套件資料服務的業務邊界。

來自 “ 談資料 ”, 原文作者:談資料;原文連結:http://server.it168.com/a2022/1109/6773/000006773571.shtml,如有侵權,請聯絡管理員刪除。

相關文章