從 RPC 到服務化框架設計

目前互聯網系統都是微服務化,那麼就需要 RPC 調用,因此本文梳理了從 RPC 基本框架協議到整個服務化框架體系建設中所包含的知識點,重點在於 RPC 框架 和 服務治理能力的梳理,本文定位於一個科普性質的文章,在於讓大家瞭解一個全貌。

一、RPC 基本框架

1-1、RPC 基本框架
理解 RPC

RPC 的概念就是遠程過程調用。我們本地的函數調用,就是 A 方法調 B 方法,然後得到調用的結果,RPC 就是讓你像本地函數調用一樣進行跨服務之間的函數調用。互聯網發展到現在,我們都在講微服務,服務都拆分爲微服務了,那麼相關依賴的調用,就會變成跨服務之間的調用,而他們之間的通信方式就是依靠 RPC。

RPC 基礎結構(RPC 協議)

Nelson 的論文 Implementing Remote Procedure Calls 告訴我們, RPC 協議包括 5 個部分:

  1. Client
  2. Client-stub
  3. RPCRuntime
  4. Server-stub
  5. Server

當 Client 發起一個遠程調用時,它實際上是調用本地的 Stub。本地 stub 負責將調用的接口、方法和參數,通過約定的協議規範進行編碼,並通過本地的 RPCRuntime 進行傳輸,然後將數據包發送到網絡上傳輸出去。當 Server 端的 RPCRuntime 收到請求後,交給 Server-Stub 進行解碼,然後調用 Server 端的函數或者方法,執行完畢就開始返回結果,Server-Stub 將返回結果編碼後,發送給 Client,Client 端的 RPCRuntime 收到結果,發給 Client-Stub 解碼得到結果,返回給 Client。

這裏面分了三個層次:

  • 對於客戶端和服務端,都和原來本地調用一樣,只需要關注自身的業務邏輯。
  • 對於 Stub 層,處理雙方約定好的語法、語義、封裝、解封裝。
  • 對於 RPCRuntime,主要處理高性能的傳輸,以及網絡的錯誤和異常。
1-2、RPC 框架的重點

從 RPC 基礎結構中,我們總結出 RPC 框架的重點,包括 4 部分,如下:

1-2-1、數據序列化

序列化就是將數據結構或對象轉換成二進制的過程,也就是編碼的過程,序列化後數據才方便進行網絡傳輸;反序列化就是在序列化過程中所生成的二進制轉換成數據結構或者對象的過程,將二進制轉換爲對象後業務纔好進行後續的邏輯處理。

常見的序列化協議如下:

  • ProtoBuf(IDL)
  • JSON
  • XML
  • Hessian2 (JAVA 系)

常見的 RPC 框架如 gRPC、Thrift、Dubbo、RPCX 、Motan 等會支持上述協議中的大部分,尤其是 ProtoBuf 和 JSON 。目前從性能上和使用廣泛度上來看,現在一般推薦使用 ProtoBuf,當然很多自研的框架裏面他們也會自己實現他們自己的序列化協議。

1-2-2、網絡傳輸(網絡通信)

在數據被序列化爲二進制後就可以行網絡傳輸了,網絡傳輸就是我們的數據怎麼傳輸到對方服務器上,目前來說,常見的通信傳輸方式包括 :TCP、UDP、HTTP(HTTP2.0)、QUIC 協議,TCP 是大部分框架都會默認支持的,額外這裏要說明一下,RPCX 支持 QUIC 而 gRPC 支持 HTTP2.0。

QUIC(Quick UDP Internet Connection)是谷歌制定的一種互聯網傳輸層協議,它基於 UDP 傳輸層協議,同時兼具 TCP、TLS、HTTP/2 等協議的可靠性與安全性,可以有效減少連接與傳輸延遲,更好地應對當前傳輸層與應用層的挑戰。QUIC 在應用程序層面就能實現不同的擁塞控制算法,不需要操作系統和內核支持,這相比於傳統的 TCP 協議,擁有了更好的改造靈活性,非常適合在 TCP 協議優化遇到瓶頸的業務。

1-2-3、RPC 調用方式

網絡傳輸只是數據傳輸非常基礎的一方面,從業務上來看,我們發起一次 RPC 調用,那麼還需要 RPC 的調用方式,包括如下三大類:

  • 同步 RPC:最常用的服務調用方式,發起調用請求後同步等待結果,符合我們開發的一貫認知和習慣。開發簡單、容易維護、容易理解。
  • 異步 RPC:客戶端發起服務調用之後,不同步等待響應,而是註冊監聽器或者回調函數,待接收到響應之後發起異步回調,驅動業務流程繼續執行,實現起來相對複雜,但是高併發場景下性能會更好。
  • 並行 RPC:並行服務調用,一次 I/O 操作,可以發起批量調用,這個並行的批量請求一般是通過協程來實現,然後同步等待響應;
    • 這裏需要注意,這個 並行 RPC 和 stream 流式調用是有區別的,流式是說,批量發送請求後,可以不必等所有的消息全收到後纔開始響應,而是接收到第一條消息的時候就可以及時的響應。
1-2-4、服務治理

RPC 協議只是定義了 Client 與 Server 之間的點對點調用流程,包括 stub、通信協議、RPC 消息解析等部分。但是在實際應用中,遠程過程調用的時候還需要考慮服務的路由、負載均衡、高可用等問題,而保障服務之間的調用就需要進行服務治理,服務治理基本就涵蓋:服務註冊和發現、限流、降級、熔斷、重試、失敗處理、負載均衡等各種服務治理策略。

到這裏,RPC 框架的重點的 4 大部分就介紹完畢了,現在再來看看,常見的 RPC 框架:

1-3、常見 RPC 框架

RPC 框架就是在 RPC 協議的基礎上,來完善一些偏向業務實際應用的功能,從而滿足不同場景的業務訴求。綜合來看,目前業界的 RPC 框架大致有兩種不同的側重方向,一種偏向於服務治理型,一種偏向於跨語言調用型。

1-3-1、服務治理型 RPC 框架

業界比較出名的服務治理型的 RPC 框架有 Dubbo、DubboX、Motan、RPCX 等。

服務治理型 RPC 框架的特點是功能豐富,提供高性能的遠程調用以及服務發現、服務治理等功能;常用於微服務化的業務系統中,對於特定語言的項目可以十分友好的透明化接入,是當前業界的主流。但缺點是語言耦合度較高,跨語言支持難度較大。

1-3-2、跨語言調用型 RPC 框架

業界比較出名的跨語言調用型的 RPC 框架有 :

跨語言調用型 RPC 框架的重點是關注於服務的跨語言調用,能夠支持我們常見的大部分的語言進行語言無關的調用,非常適合於爲不同語言提供通用遠程服務的場景,但這類框架沒有服務發現、服務治理等機制,使用這些框架的時候需要我們自己來實現服務發現、服務治理等相關策略。

那麼,跨語言調用指的是啥意思呢,具體是:客戶端和服務端可以在各種環境中運行和相互通信,並且可以用框架支持的任何語言編寫(參考 gRPC 官網中的一張圖如下,比如 C++ 的服務可以調用 Ruby 的服務:)

1-3-3、常見 RPC 框架對比

二、通用的服務化框架設計

我們一般講的微服務框架包含了 RPC 框架,微服務體系中最重要的就是 RPC 框架,並且是一般是偏向服務治理的 RPC 框架。微服務需要提供的核心能力包括:微服務架構中通訊的基礎協議 RPC、服務發現與註冊、負載均衡、容錯、熔斷、限流、降級、權限、全鏈路日誌跟蹤。

2-1、微服務框架的核心能力(服務治理策略)
2-1-1、服務註冊與發現

微服務後,服務大量增加,因此我們一定要能夠有一個合適的方案能夠發現對方的所有服務,業界比較常見的服務發現的組件如 zookeeper、etcd、consul 等,基本原理就是先將自己的服務列表到註冊中心註冊,然後再提供服務發現能力。

服務發現機制有服務端發現和客戶端發現兩種實現方式:

  • 服務端發現模式(server-side):可以通過 DNS 或者帶 VIP 的負載均衡實現。
    • 優點是對客戶端無侵入性,客戶端只需要簡單的向負載均衡或者服務域名發起請求,無需關係服務發現的具體細節,也不用引入服務發現的邏輯
    • 缺點是不靈活,不方便異化處理;並且同時需要引入一個統一的負載均衡器
  • 客戶端發現模式(client-side):需要客戶端到服務註冊中心查詢服務地址列表,然後再決定通過哪個地址請求服務。
    • 靈活性更高,可以根據客戶端的訴求進行滿足自身業務的負載均衡,但是客戶端需要引入服務發現的邏輯,同時依賴服務註冊中心
    • 常見服務註冊組件包括:zookeeper、Etcd、Consul。java 系的一般選擇 zookeeper ,而 Golang 的一般選擇 consul 或 etcd ,這個也就是各自選擇對應的語言。etcd 相比而言,是用的較多的,K8s 系統裏面也基於是 etcd。
2-1-2、服務路由 & 負載均衡

服務路由和服務發現緊密相關,服務路由一般不會設計成一個獨立運行的系統,通常情況下是和服務發現放在一起實現的。在服務路由中,最關鍵的能力就是負載均衡。我們一般常見的負載均衡算法有:隨機路由、輪詢路由、hash、權重、最小壓力路由、最小連接數路由、就近路由等。

從業界來看,負載均衡的實現方案一般可以分爲三類:

  • 服務端負載均衡:
    • 負載均衡器在一臺單獨的主機上,可以採用軟負載,如 Nginx,LVS 等,也可以採用硬負載,如 F5 等
    • 實現簡單,存在單點問題,所有的流量都需要通過負載均衡器,如果負載均衡器存在問題,則直接導致服務不能正常提供服務;中間經過負載均衡器做代理,性能也有一定損耗。
  • 客戶端負載均衡
    • 解決了服務端負載的單點問題,每個客戶端都實現了自己的負載功能,負載能力和客戶端進程在一起
    • 負載均衡要求每個客戶端自己實現,如果不同的技術棧,每個客戶端則需要使用不同的語言實現自己的負載能力。
    • 目前業界主流的 微服務框架都是採用 客戶端負載均衡方案
  • 客戶端主機獨立負載均衡
    • 服務發現和負載的能力從客戶端進程移出,客戶端進程和負載均衡進程是 2 個獨立的進程,在同一個主機上。也就是 SideCar 模式
    • 沒有單點問題,如果一個主機的負載均衡器出問題,隻影響一個節點調用,不影響其他的節點,負載均衡器本身負載也較小,性能損耗較低
2-1-3、服務容錯

負載均衡和容錯是服務高可用的重要手段。服務容錯的設計有個基本原則,就是“Design for Failure”。常見的服務容錯策略如請求重試、限流、降級、熔斷、隔離

超時與重試

超時機制算是一種最常見的服務容錯模式了,我們發起的任何請求調用,都不可能無限等待,對方服務可能因爲各種原因導致請求不能及時響應,因此超時機制是最基礎並且是必須的。超時可能有網絡超時、也可能是對方服務異常等多種情況。

重試一般和超時模式結合使用,適用於對於下游服務的數據強依賴的場景,通過重試來保證數據的可靠性或一致性,不強依賴的場景不建議使用。在對方服務超時之後,可以根據情況進行重試(對方服務返回異常就不要重試了)。但是一定注意,重試不能盲目重試,在重試的設計中,我們一般都會引入,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,服務類型包括賬號系統、聊天系統,那麼可以通過不同系統來做隔離
  • 以用戶來做分離的方式:比如通過策略來實現不同的用戶訪問到不同的實例
2-1-4、集羣容錯

在分佈式場景下,我們的服務在集羣中的都是有冗餘的,一個是爲容錯,一個是爲了高併發,針對大量服務實例的情況下,因此就有了集羣容錯的設計。集羣容錯是微服務集羣高可用的保障,它有很多策略可供選擇,包括:

  • 快速失敗(Failfast):快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
  • 失敗轉移(Failover):失敗自動切換,當出現失敗,重試集羣其它服務實例 。通常用於讀操作,但重試會帶來更長延遲。一般都會設置重試次數。
  • 失敗重試(Failback):失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。
  • 聚合調用(Forking):並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。一般會設置最大並行數。
  • 廣播調用(Broadcast):廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息。
2-2、微服務框架的基礎能力
服務監控和告警

開源代表作:Prometheus + Grafana,遵循 OpenMetrics 規範,基本數據格式分爲 Gauge、Count、Summary、Histogram

分佈式服務 Tracing 跟蹤系統

目前有兩種協議規範:

  • OpenTracing :鏈路跟蹤領域的標準,目前業界系統支持最多的標準,開源代表作:
    • jaeger
    • zipkin
  • OpenTelemetry:可觀測性領域的標準,對 Trace,Metrics,Log 統一支持的唯一標準。OpenTelemetry 由 OpenTracing 和 OpenCensus 合併而成,和 OpenTracing 是一個互補的形態。
    • 天機閣
配置中心

配置中心用來管理大量微服務之間的業務配置,並且是中心化的統一配置中心來進行管理。

遠程日誌

遠程日誌組件的代表作是 ELK 系統:Elasticsearch, Logstash, Kibana。

在微服務架構中,一個客戶端請求的接入,往往涉及到後端一系列服務的調用,如何將這些請求串聯起來?業界常用的方案是採用全局流水號【traceID】串聯起來。通過全局流水號【traceID】,從日誌裏面可以拉出整條調用鏈路。

這裏關於整體鏈路又和 分佈式服務 Tracing 跟蹤系統 關聯起來,Tracing 可以知道整體鏈路的請求質量,遠程日誌+ traceID 可以知道整體鏈路的日誌詳情。

2-3、微服務框架依託的自動化運維能力

微服務框架建設 ok 之後,那麼大量服務怎麼運維,這就依託自動化運維能力,包括如下幾個方面:

  • 自動化測試
  • 自動化部署
  • 生命週期管理

業界目前一般採用容器平臺,微服務框架 + K8s 容器平臺 是當今互聯網業務的黃金標準

2-4、小結:自己搭建一個服務化框架的思路

自己搭建一個服務化框架的思路:

  • 首先,要確定好基本的 RPC 通信協議,一般會選擇開源方案,重點關注:
    • 功能需求的滿足度
    • 多語言的支持
    • 性能和穩定性
    • 社區活躍度、成熟度
  • 其次,基於開源的 RPC 框架來搭建而不是完全從 0 開始。可選的框架包括
    • Dubbo
    • Motan
    • gRPC
    • Thrift
    • 關於方案的對比,這裏不再陳述,網上可以搜索得到,想要表達的是,每個公司的情況不一樣,開發人員的能力和語言也不一樣,因此方案選型需要根據自身情況而定,沒有最好,只有最合適!
  • 最後,Go 語言方面,gRPC 是業界公認的比較好的 RPC 框架,基於 gRPC + 一些服務治理策略可以實現一個服務化框架。這些服務治理的策略,很多也都可以用一些開源的組件。
  • 轉自 https://cloud.tencent.com/developer/article/1900812
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章