★微服務系列
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調用流程(這邊指的是同步調用情況下),步驟順序如下:
- 客戶端(client)以本地調用方式(即以接口的方式)調用服務;
- 客戶端存根(client stub)接收到調用后,負責將方法、參數等組裝成能夠進行網絡傳輸的消息體(將消息體對象序列化為二進制 byte[]);
- 客戶端通過sockets將消息發送到服務端;
- 服務端存根( server stub)收到消息后進行解碼(將消息對象反序列化);
- 服務端存根( server stub)根據解碼結果調用本地的服務;
- 本地服務執行並將結果返回給服務端存根( server stub);
- 服務端存根( server stub)將返回結果打包成消息(將結果消息對象序列化);
- 服務端(server)通過sockets將消息發送到客戶端;
- 客戶端存根(client stub)接收到結果消息,並進行解碼(將結果消息反序列化);
- 客戶端(client)得到最終結果。
RPC的目標是要把2、3、4、7、8、9這些步驟都封裝起來。
無論是何種類型的數據,最終都需要
轉換成二進制流在網絡上進行傳輸,數據的發送方需要將對象轉換為二進制流,而數據的接收方則需要把二進制流再恢復為對象。
2.3 多
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。
內容項 | 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看起來就像本地調用一樣簡單。
RPC 框架調用流分析
2.6.1 RPC框架功能(簡單版本)
實現的過程:
- client初始化一個channel,監聽NamingService,從服務名字中解析出來服務真正的上游實例地址
- 客戶端將請求的的數據進行序列化
- 上游可能多個實例,需要LB去選擇一個下游的IP+Port,選出來之后需要和上游實例建立連接和發送請求
- 建立連接之后發送請求
- 服務端接着接受連接和接受數據,收到數據之后將二進制數據反序列化為一個內存對象request
- 然后再調用server的響應方法進行處理
- 服務端通過sockets將消息發送到客戶端;
- 客戶端接收到結果消息,並進行解碼(將結果消息反序列化)
2.6.2 RPC框架功能(復雜版本)
有些RPC框架不只是處理通信相關的工作(如數據的序列化和反序列化,協議的解析/打包,數據的壓縮解壓縮,數據的加密和解密),還可以做很多微服務治理的工作。
比如Dubbo支持對服務的治理,包括 服務注冊與發現、故障注入、超時重試、負載均衡、連接管理和健康檢查等。除此之外,服務端還有認證鑒權、並發流量限制、函數路由、協議適配和參數校驗等等復雜的策略。
所以一個成熟的RPC框架也可以是一個非常復雜全面的分布式系統,在一定程度上協助工程進行微服務建設。
對比項 | Dubbo | gRPC | brpc | Thrift |
公司 | Ali | Baidu | ||
通訊協議 | 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框架來進行服務之間的通信。