華為技術總監經驗分享:淺談服務化架構

2019-05-26     IT技術分享

談到服務化架構(SOA)在保證系統擴展性上,是一個比較好的架構設計實踐。也談到了通過服務網關的形式來進行多服務的註冊與管理等。但困於篇幅,並未展開講關於服務化架構實現層面上的具體細節。本文就結合我這兩年來,在服務化架構設計上的一些實踐經驗,談談一個服務化框架其應該具備的一些功能以及其基本實現方式。

這裡說到的「服務」,本質上來說,就是指「RPC」。單純的RPC功能實現,其實很簡單,無非就是client發起調用,中間某個組件(甚至就是client本身)攔截調用信息,序列化後將信息傳輸到server端,server端收到調用請求後反序列化,根據請求詳細發起實際調用後返迴響應傳輸回給client端。這樣的RPC很常見,比如常見的存儲過程調用就是一例。但是在一個複雜的業務環境,如何管理和協同這些大量的RPC才是最麻煩的事情。所以,在此談的「服務化」更多指的是對RPC的管理。

一個複雜業務環境下的大量RPC究竟會遇到哪些問題呢?換句話說,一個服務化管理框架究竟應該具備哪些功能特性才算基本完備呢?以下是我的一些看法。

1.協議選型

數據序列化

為整個環境里的服務採用統一的數據序列化協議,其益處是顯而易見的,能大大降低服務提供者和服務調用者之間的溝通成本,同時也可以為服務提供者減少應對不同數據協議需求而帶來的代碼複雜性。所以,在開始設計一個服務化框架時,第一件重要的事情就是選定一個標準的數據序列化協議。如何選擇合適的序列化協議重點需要從 擴展性,傳輸性能以及業界通用性(換句話說就是不同技術/語言的支持程度) 三個因素里來協調選擇。當前看來,在這三個方面都做的比較好,也是使用最廣泛的就是 JsonProtobuf 了,基於文本的Json在可讀性和靈活性上占優,而基於二進位的Protobuf在傳輸性能生更勝一籌。而如果整個環境開發的技術棧比較統一,比如全是Java/.NET,也可以選擇對這一技術更加友好的序列化協議。我這一次選擇的就是Json,因為從面對的業務情況來看,傳輸性能不是根本矛盾,而靈活性要求較高,同時服務使用者使用的技術也較為多樣化。

在序列化協議的選定上 要避免的一個誤區就是採用自定義協議而不是業界通用協議 ,自定義協議將很容易面臨擴展性和使用推廣方面的問題,同時,當有新的開發人員加入進來,其需要花費時間來學習與了解。

通訊協議選擇

通訊協議上的選擇上靈活性比較大,有多種選擇,可以在基於HTTP或TCP連結上建立自己的通訊協議。比如可以設計一個簡單的header(定長)+body(序列化的請求/響應)。如果採取json作序列化協議的情況下,可以跟我本次的選擇一樣,採取一個類似 json-rpc , 完全基於json的通訊協議:

Resust:

{
"ActionName":"Do",
"AppId":"xxxxxx",
"RequestContent":{}
}

Response:

{
「RequestId」:「xxxxxxxxx」,
「HasError」:false,
"ResponseContent":{}
}

對於服務訪問對象主要為企業內部的情況,不太建議採取與http完全綁定的restful協議,這將犧牲連結層選擇的靈活性。

2.註冊與授權管理

註冊管理是解決系統交互複雜性的必備良藥,我建議超過三個系統之間的系統交互,都應該具備註冊管理功能。對於服務化架構來說,註冊管理也是最為核心的一項功能。當服務數量和服務使用者數量爆發性增長時,最難回答的問題就是「服務被誰使用了?」以及「有哪些服務可供使用?」,註冊管理就是解決這兩個問題的最佳方式與實踐。

註冊管理的實現上其實也很簡單,提供一個Config Server(配置中心),收集服務提供者的註冊信息(包括服務名稱,服務地址(可以多個),版本,超時時間控制等),我們稱為 服務的元信息 。而當服務使用者需要調用相應的服務時,就可以利用這些元信息來查找和調用相應的服務了。

不過,在元信息的使用上,存在兩者架構方式

1.服務使用者訪問統一的服務中轉器,由服務中轉器按照註冊信息以及負載情況將請求轉發到相應的服務地址上。服務執行後,響應信息返回到服務中心,服務中心將響應回送給調用方。

這種方式的優點是能比較好的控制所有請求的調度。當服務元信息發生變化時,能及時地調整請求轉發(負載)與超時控制等。缺點是請求和響應均需要由中轉中心負責轉發,性能耗費較大。同時,中轉中心的可用性也容易產生問題,必須通過集群的方式來解決。

2.服務使用者負責從配置中心獲取服務地址等信息,然後有由服務使用者直接向相對應地址上的服務發送請求,請求也直接由服務提供者返回給服務調用者。同時,服務使用者本身可以緩存一定的服務元信息,防止每次訪問都要從配置中心獲取,以降低配置中心的負載,增強整個系統的可用性。當配置中心的服務元信息發生變化時,通過通知的方式告知服務使用者更新本地緩存。

這種架構方式與第一種架構相比,能顯著降低性能的損耗,以及服務使用者對中心節點的直接依賴。但代價是需要徹底改造服務使用者的調用方式,框架的代碼必須侵入到客戶端的開發中去。一般會針對不同的客戶端提供clientLib,但當客戶端實現方式多樣化時,這種代價是非常大的。

由於我這次面對的客戶端多樣性,客戶端開發也不在控制範圍內,所以選擇就是第一種方式。

關於授權,可以與註冊管理相互結合,將授權信息同一保存到配置中心。對於企業內部訪問的服務,做到通過IP+AppId授權應該就夠了。這裡有個經驗是可以將授權和服務版本確認兩者結合起來,即在 授權的同時完成服務版本的確定 ,而不採取由客戶端發起訪問時指定版本的方式,這樣做的好處是框架和服務提供者對於服務版本變更和灰度發布具有更高的可控制性。

3.路由與過載保護

《可擴展架構設計的三個維度》 一文里談到通過單元化架構以滿足Z軸擴展,以滿足差異性的需求或者做到安全隔離。而 服務路由是實現這種單元化架構的基本保障 ,以保證能將來自不同訪問者請求或者不同的請求內容,分發到不同的服務提供區域去,形成單元化架構的閉環。當然,路由功能並不一定需要框架來獨立實現,業界許多通用的(軟)負載均衡器可以協助實現,如Nginx/HAProxy/LVS這些。但是這類通用的負載均衡軟體的問題是路由算法比較通用,當需要擴展到與業務邏輯相關的路由綁定時,比較麻煩,比如需要用戶ID按權重分配路由。在此建議, 可以採取通用的負載均衡軟體當第一層接入,而在服務節點之間採取自己實現路由模塊的方式。而在實現路由模塊時,需要將擴展性上的考慮放在第一位。

對於服務化架構,保障提供服務提供者的業務系統不受「惡意」調用或突發性激增調用的破壞,過載保護功能至關重要,它能起到系統「保險絲」的效果。前文提到可用於接入的Nginx/HAProxy/LVS這些軟體,也多少提供了過載保護的功能。如果自己實現過載保護模塊,具體可參見我的《過載保護算法淺析》一文。對於過載保護的一個經驗是: 過載保護越靠近服務訪問前端越好。

4.服務拆分與組合化

傳統的SOA概念,指的是不同的應用系統之間相互通過大粒度服務的方式進行集成。而當今的服務化架構已經擺脫了這一概念的束縛,更多講的是系統內部模塊級甚至是功能級的服務化模式。也就是說服務實現的粒度更小了。這當然為應用和服務的實現帶來了更強的靈活性,服務交付周期也大大縮短了。但這樣的細粒度拆分服務,帶來的問題是項功能的實現需要訪問的服務數量成倍的增加。如下圖所示:一個客戶下訂單的功能實現需要分別訪問:客戶信息服務,產品類別服務,庫存服務,訂單管理服務等。

這將顯著增加功能實現的複雜性。為了解決這一問題,我們只能再次使用那條永遠有效的「中間層定律」: 任何計算機問題都可以通過中間加一層來解決。 我們可以將相應的服務組合成一個新的服務提供出去,比如上面的例子,我們可以按以下方式組合:

5.基於配置的服務運行時提供

前文已經概述了一個服務化框架應該具有的一些基本功能以及一些基本的架構實現方式。但這個服務框架究竟如何與業務開發相結合呢?也就是說業務邏輯代碼與框架代碼之間如何隔離,而不是讓框架的功能代碼侵入到業務邏輯代碼的開發中來?這裡通用的做法就是 通過基於配置,由框架提供運行時,動態加載業務代碼的方式 。做到這點,只需要約束業務邏輯代碼實現相應的接口/基類,然後打包成相應的組件(如jar/dll/so等)提供給框架加載運行即可,類似於java servlet的開發,業務開發完全不用關心服務化框架任何功能,專注開發業務邏輯即可。同時,對於既有代碼的服務化也將變得簡單,只需要稍加重構封裝出實現相應的接口即可。

配置類似於:

 



同時,這種基於組件配置的服務實現,對於組合組件實現服務也非常簡單。只需要將上面的配置改為嵌套的方式既可以實現組合。比如對於訂單生成服務只要組合如下:

 







總結:

基本來看,服務化架構已經在業界完成了落地,尤其是網際網路公司,更是基於這一架構的領先者,有許多經驗值得借鑑。當然,這個落地的服務化架構,與當年被各大商業公司用WS-*和ESB玩壞的SOA概念相去甚遠。也再一次證明,那些被鼓吹出來的技術概念,只有當那些商業公司不再炒作之時,方是其真正落地之日(SOA如此,當今熱炒的「大數據」,「雲計算」這些概念又何嘗不會是如此呢?)。在技術被鼓吹得風頭正勁時,千萬要保持冷靜,別被那些商業公司所忽悠,你完全可以自己實現更輕量級更具有擴展性的架構。不信的話,可以去問問,那些當年花大價錢去買SOA商業組件的公司,他們還好嗎?

end:如果您覺得這篇文字有意思,歡迎點贊轉發!

文章來源: https://twgreatdaily.com/ueb_DWwBmyVoG_1Zck6B.html