引子
在當前的複雜分散式架構環境下,服務治理已經大行其道。但目光往下一層,從上層 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 步:
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)的細化特性相關內容,再做專題介紹。