微服務7:通訊之RPC

★微服務系列

微服務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框架來進行服務之間的通訊。