復雜分布式架構下的計算治理之路
引子
在當前的復雜分布式架構環境下,服務治理已經大行其道。但目光往下一層,從上層 APP、Service,到底層計算引擎這一層面,卻還是各個引擎各自為政,Client-Server模式緊耦合滿天飛的情況。如何做好“計算治理”,讓復雜環境下各種類型的大量計算任務,都能更簡潔、靈活、有序、可控的提交執行,和保障成功返回結果?計算中間件 Linkis 就是上述問題的實踐。
一、復雜分布式架構環境下的計算治理有什么問題?
1. 什么是復雜分布式架構環境?
分布式架構,指的是系統的組件分布在通過網絡相連的不同計算機上,組件之間通過網絡傳遞消息進行通信和協調,協同完成某一目標。一般來說有水平(集群化)和垂直(功能模塊切分)兩個拆分方向,以解決高內聚低耦合、高并發、高可用等方面問題。
多個分布式架構的系統,組成分布式系統群,就形成了一個相對復雜的分布式架構環境。通常包含多種上層應用服務,多種底層基礎計算存儲引擎。如下圖所示:

2. 什么是計算治理?
就像《微服務設計》一書中提到的,如同城市規劃師在面對一座龐大、復雜且不斷變化的城市時,所需要做的規劃、設計和治理一樣,龐大復雜的軟件系統環境中的各種區域、元素、角色和關系,也需要整治和管理,以使其以一種更簡潔、優雅、有序、可控的方式協同運作,而不是變成一團亂麻。
在當前的復雜分布式架構環境下,大量 APP、Service 間的通信、協調和管理,已經有了從 SOA(Service-Oriented Architecture)到微服務的成熟理念,及從 ESB 到 Service Mesh 的眾多實踐,來實現其從服務注冊發現、配置管理、網關路由,到流控熔斷、日志監控等一系列完整的服務治理功能。服務治理框架的“中間件”層設計,可以很好的實現服務間的解耦、異構屏蔽和互操作,并提供路由、流控、狀態管理、監控等治理特性的共性提煉和復用,增強整個架構的靈活性、管控能力、可擴展性和可維護性。
但目光往下一層,你會發現在從 APP、Service,到后臺引擎這一層面,卻還是各個引擎各自為政,Client-Server 模式緊耦合滿天飛的情況。在大量的上層應用,和大量的底層引擎之間,缺乏一層通用的“中間件”框架設計。類似下圖的網狀。

計算治理,關注的正是上層應用和底層計算(存儲)引擎之間,從 Client 到 Server 的連接層范圍,所存在的緊耦合、靈活性和管控能力欠缺、缺乏復用能力、可擴展性、可維護性差等問題。要讓復雜分布式架構環境下各種類型的計算任務,都能更簡潔、靈活、有序、可控的提交執行,和成功返回結果。如下圖所示:

3. 計算治理問題描述
更詳細的來看計算治理的問題,可以分為如下治(architecture,架構層面)和理(insight,細化特性)兩個層面。
(1)計算治理之治(architecture)- 架構層面問題。
緊耦合問題,上層應用和底層計算存儲引擎間的 CS 連接模式。
所有 APP& Service 和底層計算存儲引擎,都是通過 Client-Server 模式相連,處于緊耦合狀態。以 Analytics Engine 的 Spark 為例,如下圖:

這種狀態會帶來如下問題:
- 引擎 client 的任何改動(如版本升級),將直接影響每一個嵌入了該 client 的上層應用;當應用系統數量眾多、規模龐大時,一次改動的成本會很高;
- 直連模式,導致上層應用缺乏,對跨底層計算存儲引擎實例級別的,路由選擇、負載均衡等能力;或者說依賴于特定底層引擎提供的特定連接方式實現,有的引擎有一些,有的沒有;
- 隨著時間推移,不斷有新的上層應用和新的底層引擎加入進來,整體架構和調用關系將愈發復雜,可擴展性、可靠性和可維護性降低。
重復造輪子問題,每個上層應用工具系統都要重復解決計算治理問題。
每個上層應用都要重復的去集成各種 client,創建和管理 client 到引擎的連接及其狀態,包括底層引擎元數據的獲取與管理。在并發使用的用戶逐漸變多、并發計算任務量逐漸變大時,每個上層應用還要重復的去解決多個用戶間在 client 端的資源爭用、權限隔離,計算任務的超時管理、失敗重試等等計算治理問題。

想象你有 10 個并發任務數過百的上層應用,不管是基于 Web 的 IDE 開發環境、可視化 BI 系統,還是報表系統、工作流調度系統等,每個接入 3 個底層計算引擎。上述的計算治理問題,你可能得逐一重復的去解決 10*3=30 遍,而這正是當前在各個公司不斷發生的現實情況,其造成的人力浪費不可小覷。
擴展難問題,上層應用新增對接底層計算引擎,維護成本高,改動大。
在 CS 的緊耦合模式下,上層應用每新增對接一個底層計算引擎,都需要有較大改動。
以對接 Spark 為例,在上層應用系統中的每一臺需要提交 Spark 作業的機器,都需要部署和維護好 Java 和 Scala 運行時環境和變量,下載和部署 Spark Client 包,且配置并維護 Spark 相關的環境變量。如果要使用 Spark on YARN 模式,那么你還需要在每一臺需要提交 Spark 作業的機器上,去部署和維護 Hadoop 相關的 jar 包和環境變量。再如果你的 Hadoop 集群需要啟用 Kerberos 的,那么很不幸,你還需要在上述的每臺機器去維護和調試 keytab、principal 等一堆 Kerberos 相關配置。

這還僅僅是對接 Spark 一個底層引擎。隨著上層應用系統和底層引擎的數量增多,需要維護的關系會是個笛卡爾積式的增長,光 Client 和配置的部署維護,就會成為一件很令人頭疼的事情。
應用孤島問題,跨不同應用工具、不同計算任務間的互通問題。
多個相互有關聯的上層應用,向后臺引擎提交執行的不同計算任務之間,往往是有所關聯和共性的,比如需要共享一些用戶定義的運行時環境變量、函數、程序包、數據文件等。當前情況往往是一個個應用系統就像一座座孤島,相關信息和資源無法直接共享,需要手動在不同應用系統里重復定義和維護。
典型例子是在數據批處理程序開發過程中,用戶在數據探索開發 IDE 系統中定義的一系列變量、函數,到了數據可視化系統里往往又要重新定義一遍;IDE 系統運行生成的數據文件位置和名稱,不能直接方便的傳遞給可視化系統;依賴的程序包也需要從 IDE 系統下載、重新上傳到可視化系統;到了工作流調度系統,這個過程還要再重復一遍。不同上層應用間,計算任務的運行依賴缺乏互通、復用能力。

(2)計算治理之理(insight)- 細化特性問題:
除了上述的架構層面問題,要想讓復雜分布式架構環境下,各種類型的計算任務,都能更簡潔、靈活、有序、可控的提交執行,和成功返回結果,計算治理還需關注高并發,高可用,多租戶隔離,資源管控,安全增強,計算策略等等細化特性問題。這些問題都比較直白易懂,這里就不一一展開論述了。
二、基于計算中間件 Linkis 的計算治理 - 治之路(Architecture)
1. Linkis 架構設計介紹
核心功能模塊與流程
計算中間件 Linkis,是微眾銀行專門設計用來解決上述緊耦合、重復造輪子、擴展難、應用孤島等計算治理問題的。當前主要解決的是復雜分布式架構的典型場景 - 數據平臺環境下的計算治理問題。
Linkis 作為計算中間件,在上層應用和底層引擎之間,構建了一層中間層。能夠幫助上層應用,通過其對外提供的標準化接口(如 HTTP, JDBC, Java …),快速的連接到多種底層計算存儲引擎(如 Spark、Hive、TiSpark、MySQL、Python 等),提交執行各種類型的計算任務,并實現跨上層應用間的計算任務運行時上下文和依賴的互通和共享。且通過提供多租戶、高并發、任務分發和管理策略、資源管控等特性支持,使得各種計算任務更靈活、可靠、可控的提交執行,成功返回結果,大大降低了上層應用在計算治理層的開發和運維成本、與整個環境的架構復雜度,填補了通用計算治理軟件的空白。


要更詳細的了解計算任務通過 Linkis 的提交執行過程,我們先來看看 Linkis 核心的“計算治理服務”部分的內部架構和流程。如下圖:

計算治理服務:計算中間件的核心計算框架,主要負責作業調度和生命周期管理、計算資源管理,以及引擎連接器的生命周期管理。
公共增強服務:通用公共服務,提供基礎公共功能,可服務于 Linkis 各種服務及上層應用系統。
其中計算治理服務的主要模塊如下:
- 入口服務 Entrance,負責接收作業請求,轉發作業請求給對應的 Engine,并實現異步隊列、高并發、高可用、多租戶隔離
- 應用管理服務 AppManager,負責管理所有的 EngineConnManager 和 EngineConn,并提供 EngineConnManager 級和 EngineConn 級標簽能力;加載新引擎插件,向 RM 申請資源, 要求 EM 根據資源創建 EngineConn;基于標簽功能,為作業分配可用 EngineConn。
- 資源管理服務 ResourceManager,接收資源申請,分配資源,提供系統級、用戶級資源管控能力,并為 EngineConnManager 級和 EngineConn 提供負載管控。
- 引擎連接器管理服務 EngineConn Manager,負責啟動 EngineConn,管理 EngineConn 的生命周期,并定時向 RM 上報資源和負載情況。
- 引擎連接器 EngineConn,負責與底層引擎交互,解析和轉換用戶作業,提交計算任務給底層引擎,并實時監聽底層引擎執行情況,回推相關日志、進度和狀態給 Entrance。
如上圖所示,一個作業的提交執行主要分為以下 11 步:
1. 上層應用向計算中間件提交作業,微服務網關 SpringCloud Gateway 接收作業并轉發給 Entrance。
2. Entrance 消費作業,為作業向 AppManager 申請可用 EngineConn。
3. 如果不存在可復用的 Engine,AppManager 嘗試向 ResourceManager 申請資源,為作業啟動一個新 EngineConn。
4. 申請到資源,要求 EngineConnManager 依照資源啟動新 EngineConn
5.EngineConnManager 啟動新 EngineConn,并主動回推新 EngineConn 信息。
6. AppManager 將新 EngineConn 分配給 Entrance,Entrance 將 EngineConn 分配給用戶作業,作業開始執行,將計算任務提交給 EngineConn。
7.EngineConn 將計算任務提交給底層計算引擎。
8.EngineConn 實時監聽底層引擎執行情況,回推相關日志、進度和狀態給 Entrance,Entrance 通過 WebSocket,主動回推 EngineConn 傳過來的日志、進度和狀態給上層應用系統。
9.EngineConn 執行完成后,回推計算任務的狀態和結果集信息,Entrance 將作業和結果集信息更新到 JobHistory,并通知上層應用系統。
10. 上層應用系統訪問 JobHistory,拿到作業和結果集信息。
11. 上層應用系統訪問 Storage,請求作業結果集。
計算任務管理策略支持
在復雜分布式環境下,一個計算任務往往不單會是簡單的提交執行和返回結果,還可能需要面對提交失敗、執行失敗、hang 住等問題,且在大量并發場景下還需通過計算任務的調度分發,解決租戶間互相影響、負載均衡等問題。
Linkis 通過對計算任務的標簽化,實現了在任務調度、分發、路由等方面計算任務管理策略的支持,并可按需配置超時、自動重試,及灰度、多活等策略支持。

基于 Spring Cloud 微服務框架
說完了業務架構,我們現在來聊聊技術架構。在計算治理層環境下,很多類型的計算任務具有生命周期較短的特征,如一個 Spark job 可能幾十秒到幾分鐘就執行完,EngineConn(EnginConnector)會是大量動態啟停的狀態。前端用戶和 Linkis 中其他管理角色的服務,需要能夠及時動態發現相關服務實例的狀態變化,并獲取最新的服務實例訪問地址信息。同時需要考慮,各模塊間的通信、路由、協調,及各模塊的橫向擴展、負載均衡、高可用等能力。
基于以上需求,Linkis 實際是基于 Spring Cloud 微服務框架技術,將上述的每一個模塊 / 角色,都封裝成了一個微服務,構建了多個微服務組,整合形成了 Linkis 的完整計算中間件能力。

從多租戶管理角度,上述服務可區分為租戶相關服務,和租戶無關服務兩種類型。租戶相關服務,是指一些任務邏輯處理負荷重、資源消耗高,或需要根據具體租戶、用戶、物理機器等,做隔離劃分、避免相互影響的服務,如 Entrance, EnginConn(EnginConnector) Manager, EnginConn;其他如 App Manger, Resource Manager、Context Service 等服務,都是租戶無關的。
Eureka 承擔了微服務動態注冊與發現中心,及所有租戶無關服務的負載均衡、故障轉移功能。
Eureka 有個局限,就是在其客戶端,對后端微服務實例的發現與狀態刷新機制,是客戶端主動輪詢刷新,最快可設 1 秒 1 次(實際要幾秒才能完成刷新)。這樣在 Linkis 這種需要快速刷新大量后端 EnginConn 等服務的狀態的場景下,時效得不到滿足,且定時輪詢刷新對 Eureka server、對后端微服務實例的成本都很高。
為此我們對 Spring Cloud Ribbon 做了改造,在其中封裝了 Eureka client 的微服務實例狀態刷新方法,并把它做成滿足條件主動請求刷新,而不會再頻繁的定期輪詢。從而在滿足時效的同時,大大降低了狀態獲取的成本。

Spring Cloud Gateway 承擔了外部請求 Linkis 的入口網關的角色,幫助在服務實例不斷發生變化的情況下,簡化前端用戶的調用邏輯,快速方便的獲取最新的服務實例訪問地址信息。
Spring Cloud Gateway 有個局限,就是一個 WebSocket 客戶端只能將請求轉發給一個特定的后臺服務,無法完成一個 WebSocket 客戶端通過網關 API 對接后臺多個 WebSocket 微服務,而這在我們的 Entrance HA 等場景需要用到。
為此 Linkis 對 Spring Cloud Gateway 做了相應改造,在 Gateway 中實現了 WebSocket 路由轉發器,用于與客戶端建立 WebSocket 連接。建立連接成功后,會自動分析客戶端的 WebSocket 請求,通過規則判斷出請求該轉發給哪個后端微服務,然后將 WebSocket 請求轉發給對應的后端微服務實例。詳見 Github 上 Linkis 的 Wiki 中,“Gateway 的多 WebSocket 請求轉發實現”一文。

Spring Cloud OpenFeign提供的 HTTP 請求調用接口化、解析模板化能力,幫助 Linkis 構建了底層 RPC 通信框架。
但基于 Feign 的微服務之間 HTTP 接口的調用,只能滿足簡單的 A 微服務實例根據簡單的規則隨機選擇 B 微服務之中的某個服務實例,而這個 B 微服務實例如果想異步回傳信息給調用方,是無法實現的。同時,由于 Feign 只支持簡單的服務選取規則,無法做到將請求轉發給指定的微服務實例,無法做到將一個請求廣播給接收方微服務的所有實例。
Linkis 基于 Feign 實現了一套自己的底層 RPC 通信方案,集成到了所有 Linkis 的微服務之中。一個微服務既可以作為請求調用方,也可以作為請求接收方。作為請求調用方時,將通過 Sender 請求目標接收方微服務的 Receiver;作為請求接收方時,將提供 Receiver 用來處理請求接收方 Sender 發送過來的請求,以便完成同步響應或異步響應。如下圖示意。詳見 Github 上 Linkis 的 Wiki 中,“Linkis RPC 架構介紹”一文。

至此,Linkis 對上層應用和底層引擎的解耦原理,其核心架構與流程設計,及基于 Spring Cloud 微服務框架實現的,各模塊微服務化動態管理、通信路由、橫向擴展能力介紹完畢。
2. 解耦:Linkis 如何解耦上層應用和底層引擎
Linkis 作為計算中間件,在上層應用和底層引擎之間,構建了一層中間層。上層應用所有計算任務,先通過 HTTP、WebSocket、Java 等接口方式提交給 Linkis,再由 Linkis 轉交給底層引擎。原有的上層應用以 CS 模式直連底層引擎的緊耦合得以解除,因此實現了解耦。如下圖所示:

通過解耦,底層引擎的變動有了 Linkis 這層中間件緩沖,如引擎 client 的版本升級,無需再對每一個對接的上層應用做逐個改動,可在 Linkis 層統一完成。并能在 Linkis 層,實現對上層應用更加透明和友好的升級策略,如灰度切換、多活等策略支持。且即使后繼接入更多上層應用和底層引擎,整個環境復雜度也不會有大的變化,大大降低了開發運維工作負擔。
3. 復用:對于上層應用,Linkis 如何凝練計算治理模塊供復用,避免重復開發
上層應用復用 Linkis 示例(Scriptis)
有了 Linkis,上層應用可以基于 Linkis,快速實現對多種后臺計算存儲引擎的對接支持,及變量、函數等自定義與管理、資源管控、多租戶、智能診斷等計算治理特性。
好處:
以微眾銀行與 Linkis 同時開源的,交互式數據開發探索工具 Scriptis 為例,Scriptis 的開發人員只需關注 Web UI、多種數據開發語言支持、腳本編輯功能等純前端功能實現,Linkis 包辦了其從存儲讀寫、計算任務提交執行、作業狀態日志更新、資源管控等等幾乎所有后臺功能?;?Linkis 的大量計算治理層能力的復用,大大降低了 Scriptis 項目的開發成本,使得 Scritpis 目前只需要有限的前端人員,即可完成維護和版本迭代工作。
如下圖,Scriptis 項目 99.5% 的代碼,都是前端的 JS、CSS 代碼。后臺基本完全復用 Linkis。

4. 快速擴展:對于底層引擎,Linkis 如何以很小的開發量,實現新底層引擎快速對接
模塊化可插拔的計算引擎接入設計,新引擎接入簡單快速
對于典型交互式模式計算引擎(提交任務,執行,返回結果),用戶只需要 buildApplication 和 executeLine 這 2 個方法,沒錯,2 個方法,2 個方法,就可以完成一個新的計算引擎接入 Linkis,代碼量極少。示例如下。
(1). AppManager 部分:用戶必須實現的接口是 ApplicationBuilder,用來封裝新引擎連接器實例啟動命令。
- // 用戶必須實現的方法: 用于封裝新引擎連接器實例啟動命令defbuildApplication(protocol:Protocol):ApplicationRequest
(2). EngineConn 部分:用戶只需實現 executeLine 方法,向新引擎提交執行計算任務:
- // 用戶必須實現的方法:用于調用底層引擎提交執行計算任務defexecuteLine(context:EngineConnContext,code:String):ExecuteResponse
引擎相關其他功能 / 方法都已有默認實現,無定制化需求可直接復用。
5. 連通,Linkis 如何打通應用孤島
通過 Linkis 提供的上下文服務,和存儲、物料庫服務,接入的多個上層應用之間,可輕松實現環境變量、函數、程序包、數據文件等,相關信息和資源的共享和復用,打通應用孤島。

Context Service 上下文服務介紹
Context Service(CS)為不同上層應用系統,不同計算任務,提供了統一的上下文管理服務,可實現上下文的自定義和共享。在 Linkis 中,CS 需要管理的上下文內容,可分為元數據上下文、數據上下文和資源上下文 3 部分。

元數據上下文,定義了計算任務中底層引擎元數據的訪問和使用規范,主要功能如下:
- 提供用戶的所有元數據信息讀寫接口(包括 Hive 表元數據、線上庫表元數據、其他 NOSQL 如 HBase、Kafka 等元數據);
- 計算任務內所需元數據的注冊、緩存和管理。
- 數據上下文,定義了計算任務中數據文件的訪問和使用規范。管理數據文件的元數據。
- 運行時上下文,管理各種用戶自定義的變量、函數、代碼段、程序包等。
- 同時 Linkis 也提供了統一的物料管理和存儲服務,上層應用可根據需要對接,從而可實現腳本文件、程序包、數據文件等存儲層的打通。
三、基于計算中間件 Linkis 的計算治理 - 理之路(Insight)
Linkis 計算治理細化特性設計與實現介紹,在高并發、高可用、多租戶隔離、資源管控、計算任務管理策略等方面,做了大量細化考量和實現,保障計算任務在復雜條件下成功執行。
1. 計算任務的高并發支持
Linkis 的 Job 基于多級異步設計模式,服務間通過高效的 RPC 和消息隊列模式進行快速通信,并可以通過給 Job 打上創建者、用戶等多種類型的標簽進行任務的轉發和隔離來提高 Job 的并發能力。通過 Linkis 可以做到 1 個入口服務(Entrance)同時承接超 1 萬 + 在線的 Job 請求。
多級異步的設計架構圖如下:

如上圖所示 Job 從 GateWay 到 Entrance 后,Job 從生成到執行,到信息推送經歷了多個線程池,每個環節都通過異步的設計模式,每一個線程池中的線程都采用運行一次即結束的方式,降低線程開銷。整個 Job 從請求—執行—到信息推送全都異步完成,顯著的提高了 Job 的并發能力。
這里針對計算任務最關鍵的一環 Job 調度層進行說明,海量用戶成千上萬的并發任務的壓力,在 Job 調度層中是如何進行實現的呢?
在請求接收層,請求接收隊列中,會緩存前端用戶提交過來的成千上萬計算任務,并按系統 / 用戶層級劃分的調度組,分發到下游 Job 調度池中的各個調度隊列;到 Job 調度層,多個調度組對應的調度器,會同時消費對應的調度隊列,獲取 Job 并提交給 Job 執行池進行執行。過程中大量使用了多線程、多級異步調度執行等技術。示意如下圖:

2. 其他細化特性
Linkis 還在高可用、多租戶隔離、資源管控、計算任務管理策略等方面,做了很多細化考量和實現。篇幅有限,在這里不再詳述每個細化特性的實現,可參見 Github 上 Linkis 的 Wiki。后繼我們會針對 Linkis 的計算治理 - 理之路(Insight)的細化特性相關內容,再做專題介紹。