微服務7:通訊之RPC

Brand發表於2022-03-15

★微服務系列

微服務1:微服務及其演進史

微服務2:微服務全景架構 

微服務3:微服務拆分策略

微服務4:服務註冊與發現

微服務5:服務註冊與發現(實踐篇)

微服務6:通訊之閘道器

微服務7:通訊之RPC

1 什麼是RPC通訊

RPC:Remote Procedure Call Protocol,指的是遠端過程呼叫協議,一般使用在分散式業務或者微服務架構風格中。
即一個節點通過網路呼叫的方式來請求另一個節點提供的服務的過程,也可以簡單的理解為client訪問server上提供的函式(像呼叫本地函式一樣,去呼叫一個遠端服務)。

2 RPC通訊詳解

2.1 RCP角色和職能

在RPC框架中主要有三個角色:Provider、Consumer和Registry。如下圖所示:
 
節點角色說明,這邊看起來,跟其他的服務註冊與發現框架原理差不多(如  Eureka、Consul):
Service(provider): 暴露服務的服務提供方。
Client(consumer): 呼叫遠端服務的服務消費方。
Registry: 服務註冊與發現的註冊中心。

2.2 RPC呼叫流程

RPC(Remote Procedure Call)遠端過程呼叫,即一個節點通過網路呼叫的方式來請求另一個節點提供的服務的過程,也可以簡單的理解為client訪問server上提供的函式。

他的基本呼叫流程如下:

上面是一次完整的RPC呼叫流程(這邊指的是同步呼叫情況下),步驟順序如下:
  1. 客戶端(client)以本地呼叫方式(即以介面的方式)呼叫服務;
  2. 客戶端存根(client stub)接收到呼叫後,負責將方法、引數等組裝成能夠進行網路傳輸的訊息體(將訊息體物件序列化為二進位制 byte[]);
  3. 客戶端通過sockets將訊息傳送到服務端;
  4. 服務端存根( server stub)收到訊息後進行解碼(將訊息物件反序列化);
  5. 服務端存根( server stub)根據解碼結果呼叫本地的服務;
  6. 本地服務執行並將結果返回給服務端存根( server stub);
  7. 服務端存根( server stub)將返回結果打包成訊息(將結果訊息物件序列化);
  8. 服務端(server)通過sockets將訊息傳送到客戶端;
  9. 客戶端存根(client stub)接收到結果訊息,並進行解碼(將結果訊息反序列化);
  10. 客戶端(client)得到最終結果。
RPC的目標是要把2、3、4、7、8、9這些步驟都封裝起來。
無論是何種型別的資料,最終都需要轉換成二進位制流在網路上進行傳輸,資料的傳送方需要將物件轉換為二進位制流,而資料的接收方則需要把二進位制流再恢復為物件。

2.3 多服務RPC通訊模型

理解對服務的呼叫和RPC模式的結果返回,注意箭頭的指向的區別。

2.4 RPC通訊使用到的技術 

1、動態代理(Spring中重點了解下)
生成 client stub和server stub需要用到 Java 動態代理技術 ,我們可以使用JDK原生的動態代理機制,可以使用一些開源位元組碼工具框架 如:CgLib、Javassist等。
2、序列化
序列化:將Java物件轉換成byte[]的過程,也就是編碼的過程;
反序列化:將byte[]轉換成Java物件的過程;
3、NIO 
當前很多RPC框架都直接基於netty這一IO通訊框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推薦使用 Netty 作為底層通訊框架。
4、服務註冊中心
可選技術:Redis 、Zookeeper,
一般的RPC框架會接入註冊中心來進行註冊與發現的管理,比如Dubb與Zookeeper 的完美結合。
所以實現RPC呼叫過程,結構分為三部分:client、grpc、server。

2.5 RPC通訊與普通函式呼叫的區別

雖然RPC可以簡單的理解為client 去呼叫server 中的函式。但是RPC通訊和直接的函式呼叫還是有很大的區別的:

內容項 RPC呼叫 本地呼叫
函式定址 IP埠路由(NamingService + LoadBalancer)+函式路由 記憶體指標
傳遞資料 序列化後的資料流 記憶體物件
呼叫方異常處理 timeout、retry、curcuit breaker 丟擲Exception / 函式返回固定異常標識的資料
被呼叫方異常處理 認證鑑權、過載保護 入參檢查 / 執行異常捕獲和處理
問題定位 分散式Trace、監控、日誌中心 日誌記錄 / 斷點除錯跟蹤
效能優化 連線池、多路複用、執行緒池、輕量級執行緒、non-block IO 等 編譯器優化(inline等)

這裡可以看出rpc比函式呼叫複雜的多,比如:

  • 函式定址:你怎樣調到你想要的哪個函式?在本地呼叫中其實就是一個函式指標,但是在RPC場景下,你要找到這個函式其實非常複雜,一個服務一般有多個下游例項,首先要選擇一個下游例項,一般這個是由NamingService+LB來實現,到達對應的例項後,服務端還要解析請求體,找到函式名,然後做函式路由。
  • 資料傳遞:在本地呼叫過程中其實就是傳遞一個指標或者值,在RPC場景下其實是通過網路傳遞的,網路上需要傳遞一個記憶體物件序列化之後的一個二進位制網路資料流,response回來的時候也需要經過一個反序列化的過程。
  • 異常處理:本地呼叫的情況下,無非就是判斷下這個函式的返回值或者有沒有拋一些異常,但是在RPC場景下就很複雜,比如網路擁塞了,服務端處理慢了或者超時,還有很多異常的情況,所以我們要做很多系統容錯的事情,比如:超時、重試等策略來解決這些問題。本地呼叫的時候我只需要檢查引數是否合法,但是在RPC的場景下我們要做一些類似認證鑑權,過載保護等策略,避免流量過大將server打掛。
  • 問題定位:本地呼叫方法很多,比如:斷點除錯,打本地日誌。但是在RPC場景下,這些方法其實是行不通的,我們需要分散式tracing、監控和分散式日誌中心來幫助我們定位問題。
  • 效能優化:本地呼叫其實我們不用關心太多,因為編譯器會幫我們做一些列的優化,但是在RPC的場景下,就需要我們自己優化通訊效率,常用的優化手段比如:連線池、多路複用、執行緒池等等很多方法,這些方法實現起來都非常的複雜。現在大家應該能理解RPC場景是非常複雜的

正因為有如此的複雜性,所以我們需要一個RPC框架來處理這些複雜的事情,讓RPC看起來就像本地呼叫一樣簡單。  

2.6 RPC 框架呼叫流分析

2.6.1 RPC框架功能(簡單版本)

 

實現的過程:

  • client初始化一個channel,監聽NamingService,從服務名字中解析出來服務真正的上游例項地址
  • 客戶端將請求的的資料進行序列化
  • 上游可能多個例項,需要LB去選擇一個下游的IP+Port,選出來之後需要和上游例項建立連線和傳送請求
  • 建立連線之後傳送請求
  • 服務端接著接受連線和接受資料,收到資料之後將二進位制資料反序列化為一個記憶體物件request
  • 然後再呼叫server的響應方法進行處理
  • 服務端通過sockets將訊息傳送到客戶端;
  • 客戶端接收到結果訊息,並進行解碼(將結果訊息反序列化)

2.6.2 RPC框架功能(複雜版本)

有些RPC框架不只是處理通訊相關的工作(如資料的序列化和反序列化,協議的解析/打包,資料的壓縮解壓縮,資料的加密和解密),還可以做很多微服務治理的工作。

比如Dubbo支援對服務的治理,包括 服務註冊與發現、故障注入、超時重試、負載均衡、連線管理和健康檢查等。除此之外,服務端還有認證鑑權、併發流量限制、函式路由、協議適配和引數校驗等等複雜的策略。

所以一個成熟的RPC框架也可以是一個非常複雜全面的分散式系統,在一定程度上協助工程進行微服務建設。

2.7 主流RPC 框架對比

對比項 Dubbo gRPC brpc Thrift
公司 Ali Google Baidu FaceBook
通訊協議 tcp/http http2 多種協議 tcp/http
序列化協議 可擴充套件 protobuf protobuf/json/mcpack 可擴充套件
開發語言 Java 跨語言 C++ / Java 跨語言
主要特點 服務治理、擴充套件性 跨語言、效能 高效能、擴充套件性 跨語言

github star

36.9K 33.5K 12.9K 8.9K

2.8 與RESTful API 的區別

RPC 主要用於公司內部的服務呼叫,效能消耗低,傳輸效率高,實現複雜。

HTTP 主要用於對外的異構環境,瀏覽器介面呼叫,App 介面呼叫,第三方介面呼叫等。

RPC 使用場景(大型的網站,內部子系統較多、介面非常多的情況下適合使用 RPC):

  • 長連結。不必每次通訊都要像 HTTP 一樣去 3 次握手,減少了網路開銷。
  • 註冊釋出機制。RPC 框架一般都有註冊中心,有豐富的監控管理;釋出、下線介面、動態擴充套件等,對呼叫方來說是無感知、統一化的操作。
  • 安全性,沒有暴露資源操作。
  • 微服務支援。就是最近流行的服務化架構、服務化治理,RPC 框架是一個強力的支撐。

3 總結 

通過本篇我們詳細學習了RPC的概念和原理,以及它能夠提供的能力。也對目前業內主流的RPC的框架有了一定的瞭解。後面一篇我們以Dobbo為例子,來學習下怎麼使用RPC框架來進行服務之間的通訊。

相關文章