如何實現一個優質的微服務框架:Apache ServiceComb 的開放性設計
寫在前面
開源微服務框架 Apache ServiceComb 的前身為華為雲的 微服務引擎 CSE (Cloud Service Engine) 雲服務, ServiceComb 的早期版本和多數第一批做微服務或分散式框架先賢一樣,為了追求高效能,做過非常多如 改善編碼效率 和改進通訊協議等嘗試。然而,隨著業務規模的遞增,需求也逐漸呈現多樣化,單方面通過傳統手段追求高效能導致在面對多樣化需求時遇到了各種挑戰,遺留系統的通訊、接入各種不同的終端、協議健壯性、防攻擊等各種挑戰迎面而來。
Apache ServiceComb,願景是幫助企業快速構建雲原生應用,通過一系列解決方案幫助使用者快速開發微服務應用的同時實現對這些微服務應用的高效運維管理,保持中立性以避免廠商LockIn成為了關鍵任務。對於此, Apache ServiceComb 需要有友好的機制能夠對接各微服務主流技術棧技術 或 開發框架。
在系列挑戰的驅動下, Aapche ServiceComb 設計團隊逐步形成了 “全面開放,使用標準協議,架構易於拆分和擴充套件,對開發人員友好,可以與業界其他主流框架互通整合” 的共識, 本文將著重分享這些共識是如何體現在Apache ServiceComb 的設計中的。
開放和標準
開放和標準應用到設計的不同的層面。一方面是連線組織和開發人員,一方面是連線異構系統。組織和開發人員的複雜性來源於技能的多樣性,大家使用不同的開發語言,同一種開發語言存在多樣的開發習慣;系統的多樣性來源於系統之間的通訊協議,為了實現與異構系統的通訊,必須具備良好的適配不同通訊協議的能力。
連線組織和開發人員
程式設計風格
每位技術人員都或多或少擁有自己的 Coding 習慣或愛好的技術, 使用個人熟練的方式從事技術工作往往更加高效和舒適。
開源微服務框架 Apache ServiceComb 的早期版本實現了 gRPC 協議,然而在專案演進過程中我們發現大量技術人員並不熟悉書寫 IDL , 對 IDL 具體支援哪些特性也不清楚。 大多數情況下,使用者每碰到一個場景就需要翻開協議規範看一遍, 而 IDL 缺少配套的編輯或語法檢查等工具也導致了開發效率的降低。
於是 Apache ServiceComb 設計團隊開始思考是否有方法能夠在確保保持使用者開發習慣的前提下支援 gRPC 。
設計團隊結合自己的 Java 程式設計史,分析當下主流框架,並聽取社群使用者的反饋找到了一些共性:
- 使用 RPC 方式描述對外介面。gRPC 、Corba 、WebService 等技術人員諳於此道。
- 使用 JAX-RS 或 Spring MVC 風格開發 REST 介面。REST 風格開發隨著微服務架構興起,JAX-RS 和 Spring MVC 已然成為 Java REST 的開發事實標準, Spring 的擁抱者都比較熟悉。
Apache ServiceComb 很快在社群設計層面達成了一致,通過預設支援以上共性來擁抱90%的開發者, 讓大多數的 Java 開發者們能夠快速開始工作。
除以上共識外,Apache ServiceComb 還額外做了進一步的優化,以保證不同程式設計風格的相容性,使使用者或開發者倍感靈活及舒適。
在下面的例子中,展示了 Provider和Consumer 程式碼的各種實現,在同一個微服務中,這些程式設計方式可以同時出現;同一段 Consumer 程式碼中可以訪問各種不同的程式設計風格的 Provider 實現。
RPC 方式的 Provider
@RpcSchema(schemaId="hello")
public class HelloImpl implements Hello{
@Override
public String sayHi(String name){
return"Hello"+name;
}
@Override
public String sayHello(Person person){
return"Helloperson"+person.getName();
}
}
JAX-RS 方式的 Provider
@RestSchema(schemaId="jaxrsHello")
@Path("/jaxrshello")
@Produces(MediaType.APPLICATION_JSON)
public class JaxrsHelloImpl implements Hello{
@Path("/sayhi")
@POST
@Override
public String sayHi(String name){
return"Hello"+name;
}
@Path("/sayhello")
@POST
@Override
public String sayHello(Person person){
return"Helloperson"+person.getName();
}
}
Spring MVC 方式的 Provider
@RestSchema(schemaId="springmvcHello")
@RequestMapping(path="/springmvchello",produces=MediaType.APPLICATION_JSON)
public class SpringmvcHelloImpl implements Hello{
@Override
@RequestMapping(path="/sayhi",method=RequestMethod.POST)
public String sayHi(@RequestParam(name="name")String name){
return"Hello"+name;
}
@Override
@RequestMapping(path="/sayhello",method=RequestMethod.POST)
public String sayHello(@RequestBody Person person){
return"Helloperson"+person.getName();
}
}
RPC 方式訪問上述三種服務的 Consumer
@RpcReference(microserviceName="hello",schemaId="hello")
private Hello hello;
System.out.println(hello.sayHi("JavaChassis"));
直至此處,或許開發者會產生疑問,既然 Consumer 可以通過一致的 API 方式訪問不同的Provider,為何還需要額外的 JAX-RS 和 Spring MVC 標籤?
原因是,這裡的設計依據是 Apache ServiceComb的 Consumer考慮的不僅限於 類SDK 的 Consumer,還有瀏覽器等非 SDK 類的 Consumer,瀏覽器的 Conumer 識別的是 Http 形式的訊息。 通過定義和使用這些標籤, 我們可以更加精細的指定瀏覽器如何訪問後臺介面。 類似於 Web Service 的 WSDL 描述語言, Apache ServiceComb 稱之為服務契約。
服務的契約會在服務執行時通過程式碼定義自動生成,並註冊到服務中心。契約也可在執行時用於獨立的服務治理邏輯開發,生成 Consumer 程式碼。此外,也可作為 API 文件對外發布,供非 SDK 的 Consumer 參考。
服務契約
微服務強調服務自治,對外體現的功能全部以鬆耦合的介面方式提供,並且只能以通訊的方式實現相互訪問。此原則給團隊協作帶來了根本性的變革。
微服務的一個開發團隊通常由5~6個人的全功能團隊組成,端到端的完成 場景需求分析、架構功能設計、開發和運維,團隊組織結構和業務系統的架構相匹配。團隊建立後的核心問題就是團隊之間如何進行高效的協作溝通,以決定不同微服務之間的協作通訊。
Apache ServiceComb 通過確保讓開發人員保持自己的固有程式設計習慣及設計上的鬆耦合靈活性,讓微服務團隊之間可以進行高效協作,以避免在不同的微服務團隊討論程式設計風格受限於歷史舊賬而浪費寶貴的精力和時間。
在 RPC 的世界裡,有 Corda IDL,WSDL,ProtoBuffer 等可以參考的優秀實踐, REST 風格的介面讓團隊之間可以通過 HTTP 語義進行溝通,但卻不能像 IDL 一樣描述跨語言的資料格式。Open API 的出現很好地解決了這個問題。
Open API 首先是一個不斷髮展壯大中的開放的標準。Open API 能兼顧 RPC 、REST 等不同的開發方式,並且吸收了大量的跨語言經驗,能夠在不同的語言之間進行解析。
對於 Java 開發者,下面的程式碼片段是日常所打交道的:
User:
type:object
properties:
age:
type:integer
如果開發人員有豐富的跨語言開發經驗,可以看出 Swagger 在解決跨語言程式設計方面API定義衝突的努力, 如 Swagger 通過 format 來定義資料型別的儲存格式,以解決不同的語言在資料型別表示上的差異:
User:
type:object
properties:
age:
type:integer
format:int32
開源微服務框架 Apache SerivceComb 既遵循常規開發規範也特別關注開發效率。開發者可以先寫介面定義後寫程式碼, 也可直接通過自己熟悉的方式編寫寫程式碼, 兩種方式都會生成 服務契約(Open API 描述檔案),並且將內容註冊到服務中心。使用者可以從服務中心下載相關的服務契約進行開發。 Apache ServiceComb 的各種治理結構也是基於契約的,可以讓開發者獨立於業務實現對系統進行統一的管控治理。
連線異構系統
gRPC 相對於 REST 的最顯著優點就是效能,它採用長連線、高效的二進位制序列化方式,提供多種語言支援, 提供了 IDL 語言約束開發者按照標準的方式工作, 一切看起來是那麼的完美。
實際上,Apache ServiceComb 的第一輪重構,首選也是 gRPC 。歷史第一次在華為雲 微服務引擎 CSE 上線以後,面對了來自閘道器壓力挑戰。
閘道器作為業務接入端,必須高效的管理連線和保證公平,長連線容易導致拒絕服務。gRPC 程式開發完成後,開發人員無法利用系統提供的各種工具進行測試,網路包分析也變得困難,給生產環境上的開發聯調造成了困難。隨著業務規模的增長,gRPC 面臨了諸如“其他三方系統如何與之直接通訊? 如何跨閘道器與它間接通訊?”等更嚴峻的挑戰。
解決這些問題,將需要我們擴充套件和改善老的協議和程式,提供 gRPC 客戶端支援,開發者需自行提供一個額外的表示層用於業務介面的邏輯轉換,造成大量的重複程式碼。同時由於 gRPC 依賴於介面定義,並根據定義生成程式碼,一套程式碼只能跑在 gRPC 協議上,如果使用者希望業務應用可以使用如 REST等其他更加靈活的方式, 就需自行重新實現一套新的程式碼邏輯。據以上的血淚史, gRPC 最終被 Apache ServiceComb 設計團隊定義為只能在中小型系統內部之間使用,並通過協議閘道器與外部系統進行通訊。並實現了 高效能私有協議 Highway 作為RPC首選預設協議。
REST 相較 gRPC ,最大的痛點是效能。
多數技術人員腦海裡一個不成文的根深蒂固的觀點:”二進位制編碼效率遠高於文字協議,採用二進位制編碼的系統的效能遠高於採用文字的 HTTP ”。該觀點甚至會讓多數決策止步於理論,多數人甚至不願嘗試去優化 REST。
可喜的是 開源微服務框架 Apache ServiceComb 邁出了重構 REST 底層通訊實現的第一步,基於 Netty 的非同步框架來替換 Tomcat 實現,實踐的效果大大超出預期,部分基準測試資料結果顯示比 gRPC 還好, gRPC最終輸在了HTTP2 協議上的額外報文。
優化後的 REST 和業界開源的其他基於二進位制的 RPC 實現的效能基本持平。在一個簡單的提供資料庫查詢的程式碼邏輯中,優化後的REST通訊框架處理時間,佔比總處理時間遠小於千分之一,這意味著再繼續在框架層面進行大量優化也抵不上業務應用層面最簡單的一個操作帶來的消耗,Apache ServiceComb對 REST 的優化已經滿足要求,最終也選擇了 REST 作為首選和預設協議(HTTP + json)。
我們並沒有就此止步。
需要遷移到 華為雲 微服務引擎 CSE 的業務日益增長,部分歷史遺留系統也需進行對接。通訊協議對應不同的開發者介面,每每增加通訊協議,則需要對業務程式碼進行大量的重複構建,造成大量無謂消耗。這是當時的華為雲化轉型以及當下很多雲化轉型企業或者雲原生企業必將面臨的痛點。
於是乎,通訊協議層被剝離了出來,和業務程式碼隔離,系統執行基於契約,開源微服務框架 Apache ServiceComb 實現通訊協議擴充套件機制。通訊協議擴充套件機制,幫助使用者解決與 gRPC 框架、自定義二進位制框架等許多遺留系統的對接通訊問題。
在 Apache ServiceComb 框架中,切換協議非常簡單,不需要修改一行業務程式碼。多個協議共存也是允許的。
ServiceComb:
rest:
address:0.0.0.0:8084
highway:
address:0.0.0.0:8094
擴充套件性
擴充套件性是系統進一步發展的基石。開源微服務框架 Apache ServiceComb 創造性地將擴充套件性拓展到 Provider 和 Consumer,讓開發者擁有一致的開發體驗。
內部系統結構
連線開發者和通訊協議層面已經讓系統具備了很大的擴充套件性。微服務化給系統解耦、團隊自治帶來了很大的靈活性,加快了開發生產效率,但同時帶來了服務管控的複雜性,在微服務領域,不得不考慮雪崩效應、呼叫跟蹤、效能監控與分析等實際管控治理問題。
基於服務契約,開源微服務框架 Apache ServiceComb 提供了動態插拔擴充套件的處理鏈機制,並且為這些管控治理能力提供了預設實現,使用者可以靈活插拔這些處理模組,或調整它們的順序以應對不同的處理場景,或自行實現以增加新的處理模組。Provider 和 Consumer 都會經過該處理鏈,這給客戶端治理功能開發帶來了極大的便利性。Apache ServiceComb 的執行結構如下:
圖1 Apache ServiceComb 執行時架構
Apache ServiceComb 同時支援同步和非同步兩種程式設計介面,並在通訊實現上採用了純非同步方式,對於執行模型的擴充套件,也是基於非同步回撥介面的。該方式提供了比同步模式(比如 Filter)更加優雅靈活的擴充套件方式。
在Apache ServiceComb 結構中,幾個核心的擴充套件機制均在 core 模組 進行定義:
Provider 程式設計模型的擴充套件,通過實現這個介面,可以適配不同的 Provider編 程風格;預設支援 RPC、Spring MVC 和 JAX-RS 三種風格。
Consumer 程式設計模型的擴充套件,通過實現這個介面,可以適配不同的 Consumer 程式設計風格;預設支援 RPC和RestTemplate 兩種風格。RestTemplate 是 Spring MVC 提供的 REST 程式設計介面,可以在服務層解除介面依賴,只依賴資料模型。
處理鏈的介面,通過擴充套件該介面,可以在處理過程中插入任意的邏輯。預設已經支援負載均衡、錯誤注入、流量控制和呼叫鏈跟蹤等多個處理鏈。開發者可以針對 Consumer 和 Provider 定義不同的處理鏈,並且為訪問不同的微服務定製不同的處理鏈。
通訊協議擴充套件,預設支援REST over Vertx、Rest over Servlet、Highway協議。
Invocation
中立的物件。所有的執行模型都面向這個中立的物件進行程式設計,當定義好服務介面後,對服務的治理和服務業務邏輯的開發可並行進行。在程式設計模型和通訊模型裡面,也面向這個物件進行編解碼。
對接外部系統
Apache ServiceComb Java-chassis 預留了對接外部系統的介面,以讓開發者或使用者可以靈活快速切換使用第三方提供的服務,這裡所指的外部系統包括但不限於:服務註冊發現的服務中心、配置管控和治理的配置中心、執行監控和運維的治理中心等。
下圖展示了不同的開發框架支援和執行的第三方系統情況,這些基礎服務都給開發者預留了可以進行支援接入的介面。
圖2 Apache ServiceComb 外部擴充套件接入
重要的擴充套件:
實現這個介面以對接不同的註冊服務。
實現這個介面以對接不同的配置服務。
此外,ServiceComb還提供了對接Zipkin、Servo等開源系統的功能,這些可以從github程式碼中查詢到對應的例子。
執行環境整合
一個完整的業務系統不是使用RPC框架就算完成了,它們還需要其他的計算資源。對於一般的業務系統都需要訪問資料庫,或者基於 J2EE 的設施進行工作。
開源微服務框架 Apache ServiceComb 可以以輕量級的方式執行,也可整合到其他系統框架。下面的示意圖說明了 Apache ServiceComb 的一些工作環境。
圖3 Apache ServiceComb 執行環境整合
- 若業務只需 REST 介面,可以輕量級的方式執行 Apache ServiceComb 。所有的REST介面運行於ServiceComb 提供的 Netty HTTP 之上。
- 若業務是基於 J2EE 來構建,那麼 Apache ServiceComb 可以作為一個 Servlet ,運行於 Web 容器裡面(如 Tomcat、Jetty 等)。
- 若業務基於 Spring Boot 生態構建,Apache ServiceComb 可作為一個starter對外提供 REST 服務,開發者可以自由使用其他基於 Spring Boot 的功能。
由於 Apache ServiceComb 使用了Spring,因此天然繼承了Spring的原有優勢,可和很多通用的元件很好的整合,如 mybatis、JPA 等。各種整合方式,都可以從ServiceComb官網或者ServiceComb 示例庫找到對應的例子。
寫在最後
開源微服務框架 Apache ServiceComb 的主體程式碼是由華為雲微服務引擎捐贈給 Apache 軟體基金會的,願景是幫助企業快速構建雲原生應用,通過一系列解決方案幫助使用者快速開發微服務應用的同時實現對這些微服務應用的高效運維管理。本次設計團隊將開放性設計部分細節點點滴滴分享出來也是為了能夠對解放微服務開發者和使用者能有所幫助。
當前越來越多的貢獻者已加入到 社群行列,Apache ServiceComb 會和這些志願者們一起一如既往堅持這個理念,爭取給業界帶來更多好的技術和分享。也期望有更多有志者一起行動。
參考文獻
[1] 開源微服務框架 Apache ServiceComb 官網
[2] 開源微服務框架 Apache ServiceComb 程式碼
[3] 華為雲微服務引擎 CSE(Cloud Service Engine)