從 0 到 1:輕鬆搞定從 RPC 到服務化框架的設計!
一、從 RPC 到服務化框架設計
(一)RPC 基本框架
-
理解 RPC
RPC 就是遠程過程調用。我們本地的函數調用,就是 A 方法調 B 方法,然後獲取結果,RPC 就是讓你像本地函數調用一樣進行跨服務的函數調用。我們現在都在講微服務,服務都拆分爲微服務了,那麼相關依賴的調用,就會變成跨服務之間的調用,他們的通信方式就是依靠 RPC。
-
RPC 基礎結構(RPC 協議)
-
Client
-
Client-stub
-
RPCRuntime
-
Server-stub
-
Server
當 Client 發起一個遠程調用時,它實際上是調用本地的 Stub。本地 Stub 負責將調用的接口、方法和參數,通過約定的協議規範進行編碼,並通過本地的 RPCRuntime 進行傳輸,然後將數據包發送到網絡上傳輸出去。當 Server 端的 RPCRuntime 收到請求後,交給 Server-Stub 進行解碼,然後調用服務端的方法,服務端執行方法,返回結果,Server-Stub 將返回結果編碼後,發送給 Client,Client 端的 RPCRuntime 收到結果,發給 Client-Stub 解碼得到結果,返回給客戶端。
這裏面分了 3 個層次:
-
對於客戶端和服務端,都像是本地調用一樣,專注於業務邏輯的處理就可以了。
-
對於 Stub 層,處理雙方約定好的語法、語義、封裝、解封裝。
-
對於 RPCRuntime,主要處理高性能的傳輸,以及網絡的錯誤和異常。
(二)RPC 框架的重點
從 RPC 基礎結構中,我們總結出 RPC 框架的重點,包括 4 部分,如下:
-
數據序列化
序列化就是將數據結構或對象轉換成二進制的過程,也就是編碼的過程,序列化後數據才方便進行網絡傳輸;反序列化就是在序列化過程中所生成的二進制轉換成數據結構或者對象的過程,將二進制轉換爲對象後業務纔好進行後續的邏輯處理。
常見的序列化協議如下:
-
protobuf(IDL)
-
json
-
xml
-
Hessian2 (JAVA 系)
常見的 RPC 框架如 gRPC、Thrift、Dubbo、RPCX 、Motan 等會支持上述協議中的大部分,尤其是 PB 和 json。目前從性能上和使用廣泛度上來看,現在一般推薦使用 PB,當然很多自研的框架裏面他們也會自己實現他們自己的序列化協議。
-
網絡傳輸(網絡通信)
在數據被序列化爲二進制後就可以行網絡傳輸了,網絡傳輸就是我們的數據怎麼傳輸到對方服務器上,目前來說,常見的通信傳輸方式包括:TCP、UDP、HTTP(HTTP2.0)、QUIC 協議,TCP 是大部分框架都會默認支持的,額外這裏要說明一下,RPCX 支持 QUIC 而 gRPC 支持 HTTP2.0。
-
RPC 調用方式
-
同步 RPC:最常用的服務調用方式,開發比較簡單,比較符合編程人員的習慣,代碼相對容易維護些。
-
異步 RPC:客戶端發起服務調用之後,不同步等待響應,而是註冊監聽器或者回調函數,待接收到響應之後發起異步回調,驅動業務流程繼續執行,實現起來相對複雜,但是高併發場景下性能會更好。
-
並行 RPC:並行服務調用,一次 I/O 操作,可以發起批量調用,然後同步等待響應;
-
服務治理
RPC 協議只規定了 Client 與 Server 之間的點對點調用流程,包括 Stub、通信協議、RPC 消息解析等部分。但是在實際應用中,遠程過程調用的時候還需要考慮服務的路由、負載均衡、高可用等問題,而保障服務之間的調用就需要進行服務治理,服務治理基本就涵蓋:服務註冊和發現、限流、降級、熔斷、重試、失敗處理、負載均衡等。
(三)常見 RPC 框架
RPC 框架就是在 RPC 協議的基礎上,來完善一些偏向業務實際應用的功能,從而滿足不同場景的業務訴求。綜合來看,目前的 RPC 框架大致有兩種不同的側重方向,一種偏重於服務治理,另一種偏重於跨語言調用。
-
服務治理型 RPC 框架
服務治理型的 RPC 框架有 Dubbo、DubboX、Motan、RPCX 等。
-
Dubbo 是阿里開源的分佈式服務框架,能夠實現高性能 RPC 調用,並且提供了豐富的管理功能,是十分優秀的 RPC 框架。
-
序列化 Protobuf 、JsonRPC、Hessian2。
-
兼容協議:gRPC、Thrift。
-
Triple 協議是 Dubbo3 的主力協議,完整兼容 gRPC over HTTP/2。
-
DubboX 是基於 Dubbo 框架開發的 RPC 框架,支持 REST 風格遠程調用,並增加了一些新的 feature。
-
Motan 是微博開源的一套輕量級、方便使用的 Java 語言的 RPC 框架
-
使用對 java 更友好的 hessian2 進行序列化。
-
通訊協議支持 Motan、http、tcp(默認 TCP)。
-
RPCX: 一個用 Go 實現的類似 Dubbo 的分佈式 RPC 框架
-
支持多種編解碼協議,如 Gob、Json、MessagePack、gencode、ProtoBuf 等。
-
支持常見的服務治理策略。
-
支持 TCP、HTTP、QUIC 和 KCP。
這類的 RPC 框架的特點是功能豐富,提供高性能的遠程調用以及服務發現及治理功能,適用於大型服務的微服務化拆分以及管理,對於特定語言的項目可以十分友好的透明化接入。但缺點是語言耦合度較高,跨語言支持難度較大。
-
跨語言調用型 RPC 框架
跨語言調用型的 RPC 框架有:
-
gRPC 是 Google 開發的高性能、通用的開源 RPC 框架。
-
支持 ProtoBuf
-
基於 HTTP2
-
支持多中語言
-
業界很多基於 gRPC 來開發自己的 RPC 框架(美圖、華爲)
-
Thrift 是 Apache 的一個跨語言的高性能的服務框架,也得到了廣泛的應用。
-
Hprose 是一款先進的輕量級、跨語言、跨平臺、無侵入式、高性能動態遠程對象調用引擎庫。
這一類的 RPC 框架重點關注於服務的跨語言調用,能夠支持我們常見的大部分的語言進行語言無關的調用,非常適合於爲不同語言提供通用遠程服務的場景,但這類框架沒有服務發現、服務治理等機制,應用時需要我們自己來實現服務發現、服務路由等策略。
跨語言指的是,客戶端和服務端可以在各種環境中運行和相互通信,並且可以用框架支持的任何語言編寫(gRPC 如下:)
對比
我們一般講的微服務框架包含了 RPC 框架,微服務體系中最重要的就是 RPC 框架,並且是一般是偏向服務治理的 RPC 框架。微服務需要提供的核心能力包括:微服務架構中通訊的基礎協議 RPC、服務發現與註冊、負載均衡、容錯、熔斷、限流、降級、權限、全鏈路日誌跟蹤。
(一)微服務框架的核心能力(服務治理策略)
-
服務註冊與發現
微服務後,服務大量增加,因此我們一定要能夠有一個合適的方案能夠發現對方的所有服務,業界比較常見的服務發現的組件如 zookeeper、etcd、consul 等,基本原理就是先將自己的服務列表註冊,然後提供服務發現能力。
服務發現機制有服務端發現和客戶端發現兩種實現方式。
- 服務端發現模式 (server-side): 可以通過 DNS 或者帶 VIP 的負載均衡實現。
優點是對客戶端無侵入性,客戶端只需要簡單的向負載均衡或者服務域名發起請求,無需關係服務發現的具體細節,也不用引入服務發現的邏輯。
缺點是不靈活,不方便難異化處理;並且同時需要引入一個統一的負載均衡器。
- 客戶端發現模式 (client-side): 需要客戶端服務註冊中心中查詢服務地址列表,然後再決定通過哪個地址請求服務。
其靈活性更高,可以根據客戶端的訴求進行滿足自身業務的負載均衡,但是客戶端需要引入服務發現的邏輯,同時依賴服務註冊中心。
-
服務路由 & 負載均衡
服務路由核心的功能就是路由算法,也就是負載均衡,從業界來看,負載均衡的實現方案一般可以分爲三類:
- 服務端負載均衡:
負載均衡器在一臺單獨的主機上,可以採用軟負載,如 Nginx,LVS 等,也可以採用硬負載,如 F5 等。
實現簡單單存在單點問題,所有的流量都需要通過負載均衡器,如果負載均衡器存在問題,則直接導致服務不能正常提供服務;中間經過負載均衡器做代理,性能也有一定損耗。
- 客戶端負載均衡
解決了服務端負載的單點問題,每個客戶端都實現了自己的負載功能,負載能力和客戶端進程在一起。
負載均衡要求每個客戶端自己實現,如果不同的技術棧,每個客戶端則需要使用不同的語言實現自己的負載能力。
目前業界主流的微服務框架都是採用客戶端負載均衡方案。
- 客戶端主機獨立負載均衡
服務發現和負載的能力從客戶端進程移出,客戶端進程和負載均衡進程是 2 個獨立的進程,在同一個主機上。也就是 SideCar 模式。
沒有單點問題,如果一個主機的負載均衡器出問題,隻影響一個節點調用,不影響其他的節點,負載均衡器本身負載也較小,性能損耗較低。
常見的負載均衡算法有:隨機路由、輪詢路由、hash、權重、最小壓力路由、最小連接數路由、就近路由等。
-
服務容錯
負載均衡和容錯是服務高可用的重要手段。服務容錯的設計有個基本原則,就是 “Design for Failure”。常見的服務容錯策略如請求重試、流控、隔離。
-
超時與重試
超時是一種最常見的服務容錯模式,在微服務調用的場景中,它主要解決了當依賴服務出現建立網絡連接或響應延遲,不用無限等待的問題,調用方可以根據事先設計的超時時間中斷調用,及時釋放關鍵資源(如連接數),避免整個系統資源耗盡出現拒絕對外提供服務這種情況。常見的超時裏面,一般如網絡連接超時時間、RPC 的響應超時時間等。
重試一般和超時模式結合使用,適用於對於下游服務的數據強依賴的場景(不強依賴的場景不建議使用!),通過重試來保證數據的可靠性或一致性,常用於因網絡抖動等導致服務調用出現超時的場景。在重試的設計中,我們一般都會引入,Exponential Backoff 的策略,也就是所謂的 “指數級退避”,每一次重試所需要的 sleep 時間都會指數增加,否則可能會導致拖累到整個系統。
-
服務限流
限流和降級用來保證核心服務的穩定性;限流是指限制每個服務的最大訪問量、降級是指高峯期對非核心的系統進行降級從而保證核心服務的可用性。
限流的實現方式:
-
計數器方式(最簡單)
-
隊列算法:常規隊列,FIFO;優先級隊列;帶權重隊列。
-
漏斗 (漏桶) 算法 Leaky Bucket。
-
令牌桶算法 Token Bucket。
-
基於響應時間的動態限流:參考 TCP 協議中算法,TCP 使用 RTT 來探測網絡的延時和性能,從而設定相應的 “滑動窗口” 的大小。
分佈式限流和單機限流:
單機限流:有多種限流算法可供選擇,最主要的是兩種,漏桶算法及令牌桶算法。如果要對線上併發總數進行嚴格限定的話,漏桶算法可能會更合適一些,這是單機限流機制。
分佈式限流(集羣限流):集羣限流的情況要更復雜一些,一般是中心化的設計。
一個可選的方案是先在各個微服務節點上實現一個計數器,對單位時間片內的調用進行計數,計數值會被定期的彙總到日誌中心,由統計分析器進行統一彙總,算出這個時間片的總調用量,集羣限流分析器會拿到這個總調用量,並和預先定義的限流閾值進行比對,計算出一個限流比例,這個限流比例會通過服務註冊中心下發到各個服務節點上,服務節點基於限流比例會各自算出當前節點對應的最終限流閾值,最後利用單機限流進行流控。
簡單的基於 Redis 來做,但是方案的缺點顯而易見,每取一次令牌都會進行一次網絡開銷,而網絡開銷起碼是毫秒級,所以這種方案支持的併發量是非常有限的。
分佈式限流業界常用的框架包括 Hystrix、resilience4j。
-
容錯降級
容錯降級可以分爲三大類,從小到大依次是:
接口降級:對非核心接口,可以設置爲直接返回空或者異常,可以在高峯期減少接口對資源如 CPU、內存、磁盤、網絡的佔用和消耗。
功能降級:對非核心功能,可以設置該功能直接執行本地邏輯,不做跨服務、跨網絡訪問;也可設置降級開關,一鍵關閉指定功能,保全整體穩定;還可以通過熔斷機制實現。
服務降級:對非核心服務,可以通過服務治理框架根據錯誤率或者響應時間自動觸發降級策略;還可以通過斷路器實現。
-
熔斷
熔斷設計來源於日常生活中的電路系統,在電路系統中存在一種熔斷器(Circuit Breaker),它的作用就是在電流過大時自動切斷電路。熔斷器一般要實現三個狀態:閉合、斷開和半開,分別對應於正常、故障和故障後檢測故障是否已被修復的場景。
閉合:正常情況,後臺會對調用失敗次數進行積累,到達一定閾值或比例時則自動啓動熔斷機制。
斷開:一旦對服務的調用失敗次數達到一定閾值時,熔斷器就會打開,這時候對服務的調用將直接返回一個預定的錯誤,而不執行真正的網絡調用。同時,熔斷器內置了一個時間間隔,當處理請求達到這個時間間隔時會進入半熔斷狀態。
半開:在半開狀態下,熔斷器會對通過它的部分請求進行處理,如果對這些請求的成功處理數量達到一定比例則認爲服務已恢復正常,就會關閉熔斷器,反之就會打開熔斷器。
熔斷設計的一般思路是,在請求失敗 N 次後在 X 時間內不再請求,進行熔斷;然後再在 X 時間後恢復 M% 的請求,如果 M% 的請求都成功則恢復正常,關閉熔斷,否則再熔斷 Y 時間,依此循環。
在熔斷的設計中,根據 Netflix 的開源組件 hystrix 的設計,最重要的是三個模塊:熔斷請求判斷算法、熔斷恢復機制、熔斷報警:
-
熔斷請求判斷機制算法:根據事先設置的在固定時間內失敗的比例來計算。
-
熔斷恢復:對於被熔斷的請求,每隔 X 時間允許部分請求通過,若請求都成功則恢復正常。
-
熔斷報警:對於熔斷的請求打異常日誌和監控,異常請求超過某些設定則報警。
-
隔離
隔離,也就是 Bulkheads 隔板的意思,這個術語是用在造船上的,也就是船艙裏防漏水的隔板。
在服務化框架的隔離設計中,我們同樣是採用類似的技術來讓我們的故障得到隔離。因此這裏的重點就是需要我們對系統進行分離。一般來說,有兩種方式,一種是以服務的類型來做分離,一種是以用戶來做分離。
-
以服務的種類來做分離的方式:比如一個社交 APP,服務類型包括賬號系統、聊天系統,那麼可以通過不同系統來做隔離。
-
以用戶來做分離的方式:比如通過策略來實現不同的用戶訪問到不同的實例。
-
集羣容錯
在分佈式場景下,我們的服務在集羣中的都是有冗餘的,一個是爲容錯,一個是爲了高併發,針對大量服務實例的情況下,因此就有了集羣容錯的設計。集羣容錯是微服務集羣高可用的保障,它有很多策略可供選擇,包括:
-
快速失敗(Failfast):快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
-
失敗轉移(Failover):失敗自動切換,當出現失敗,重試集羣其它服務實例 。通常用於讀操作,但重試會帶來更長延遲。一般都會設置重試次數。
-
失敗重試(Failback): 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。
-
聚合調用(Forking): 並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。一般會設置最大並行數。
-
廣播調用(Broadcast): 廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息。
(二)微服****務框架的基礎能力
-
服務監控和告警
開源代表作:Prometheus+Grafana,他們都遵循 OpenMetrics,基本數據格式分爲 Gauge、Count、Summary、Histogram。
-
分佈式服務 Tracing 跟蹤系統
目前有兩種協議規範:
-
OpenTracing:鏈路跟蹤領域的標準,目前業界系統支持最多的標準,開源代表作:jaeger、zipkin。
-
OpenTelemetry:可觀測性領域的標準,對 Trace,Metrics,Log 統一支持的唯一標準。
-
配置中心
配置中心用來管理大量微服務之間的業務配置,並且是中心化的統一配置中心來進行管理。
- 遠程日誌
遠程日誌組件的代表作是 ELK 系統:Elasticsearch、Logstash、Kibana。
在微服務架構中,一個客戶端請求的接入,往往涉及到後端一系列服務的調用,如何將這些請求串聯起來?業界常用的方案是採用全局流水號【traceID】串聯起來。通過全局流水號【traceID】,從日誌裏面可以拉出整條調用鏈路。
這裏關於整體鏈路又和分佈式服務 Tracing 跟蹤系統關聯起來,Tracing 可以知道整體鏈路的請求質量,遠程日誌 + traceID 可以知道整體鏈路的日誌詳情。
(三)微服務框架依託的自動化運維能力
微服務框架建設好後,那麼大量服務怎麼運維,這就依託自動化運維能力,包括如下幾個方面:
-
自動化測試
-
自動化部署
-
生命週期管理
業界目前一般採用容器平臺,微服務框架 + K8s 容器平臺 是當今互聯網業務的黃金標準。我們在 123 上部署的容器,其實就是運行在 K8s 平臺上的。123 只是一個管理頁面,我們服務部署的容器是運行在 K8s 平臺上,一個實例就是一個 Pod,K8s 就用來管理這些 Pod 的生命週期。
自己搭建一個服務化框架的思路:
首先,要確定好基本的 RPC 通信協議,一般會選擇開源方案,重點關注:功能需求的滿足度、多語言的支持、性能和穩定性、社區活躍度、成熟度。
其次,基於開源的 RPC 框架來搭建而不是完全從 0 開始。可選的框架包括 Dubbo、Motan、gRPC、Thrift。
關於方案的對比,這裏不再陳述,網上可以搜索得到,想要表達的是,每個公司的情況不一樣,開發人員的能力和語言也不一樣,因此方案選型需要根據自身情況而定,沒有最好,只有最合適!
最後,Go 語言方面,gRPC 是業界公認的比較好的 RPC 框架,基於 gRPC + 一些服務治理策略可以實現一個服務化框架。這些服務治理的策略,很多也都可以用一些開源的組件。
** 作者簡介**
吳德寶
騰訊 PCG 後臺開發工程師
騰訊 PCG 後臺開發工程師,目前負責騰訊 PCG 電商業務開發。擅長 IM、後臺基礎服務,容器等技術。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/TcRQu9qth2_adENRi8HcSA