網上充斥着各類類似於這樣的文章:rpc 比 http 快了多少倍?既然有了 http,為什么還要用 rpc 調用等等。遇到這類文章,說明對 http 和 rpc 是由理解誤區的。
這里再次重復強調一遍,通信協議不是 rpc 最重要的部分,不要被這類回答帶偏。如果要了解 rpc 請更多的去了解服務治理(SOA)的一些基本策略,推薦去看看 dubbo 的相關文檔。
詳解
rpc是遠端過程調用,其調用協議通常包含:傳輸協議 和 序列化協議。
傳輸協議
比如著名的 grpc,它底層使用的是 http2 協議;還有 dubbo 一類的自定義報文的 tcp 協議
序列化協議
例如基於文本編碼的 json 協議;也有二進制編碼的 protobuf、hession 等協議;還有針對 java 高性能、高吞吐量的 kryo 和 ftc 等序列化協議
因此我理解大部人理解誤區的問題應該是:
為什么要使用自定義 tcp 協議的 rpc 做后端進程通信?
解答
要解決這個問題就應該搞清楚 http 使用的 tcp 協議,和我們自定義的 tcp 協議在報文上的區別。
首先要 否認 一點 http 協議相較於 自定義tcp 報文協議,增加的開銷在於連接的建立與斷開。
第一、http協議是支持連接池復用的,也就是建立一定數量的連接不斷開,並不會頻繁的創建和銷毀連接
第二、http也可以使用 protobuf 這種二進制編碼協議對內容進行編碼
因此二者即 http 和 rpc 最大的區別還是在傳輸協議上。
通用定義的http1.1協議的tcp報文包含太多廢信息,一個POST協議的格式大致如下
HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 137582 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT Server: Apache 0.84 <html> <body>Hello World</body> </html>
即使編碼協議也就是 body 是使用二進制編碼協議,報文元數據也就是header頭的鍵值對卻使用了文本編碼,非常占字節數。如上圖所使用的報文中有效字節數僅僅占約 30%,也就是70%的時間用於傳輸元數據廢編碼。當然實際情況下報文內容可能會比這個長,但是報頭所占的比例也是非常可觀的。
那么假如我們使用自定義tcp協議的報文如下

這也就是為什么后端進程間通常會采用 自定義tcp協議 的 rpc 來進行通信的原因。
不單效率那么簡單
簡單來說成熟的 rpc庫相對 http容器,更多的是封裝了 “服務發現”,"負載均衡",“熔斷降級” 一類面向服務的高級特性。可以這么理解,rpc框架是面向服務的更高級的封裝。如果把一個http servlet 容器上封裝一層服務發現 和 函數代理調用,那它就已經可以做一個rpc框架了。
所以為什么要用rpc調用?
因為良好的 rpc 調用是 面向服務的封裝,針對服務的 可用性 和 效率 等都做了優化。單純使用http調用則缺少了這些特性。
可以這樣說:用http不是因為它性能好,而是因為它普適,隨便一個web容器就能跑起來你的應用。
RPC 底層是怎么實現的
之前有看過幾個帖子,評論區有激烈的爭吵,主要圍繞兩點,具體如下:
1. HTTP 和 RPC 是同一級別,還是被 RPC 包含?
2. Restful 也屬於 RPC 嗎?
對於以上兩個問題,這里用一個圖來一一說明:
上圖是一個比較完整的關系圖,這時我們發現HTTP(圖中藍色框)出現了兩次。
其中一個是 和 RPC並列的,都是跨應用調用方法的解決方案;
另一個則是被RPC包含的,是RPC通信過程的可選協議之一。
因此,第一個問題的答案是都對。看指的是哪一個藍色框。從題主的提問看,既然題主在糾結這兩者,應該是指與RPC並列的藍色框。
第二個問題是在問遠程過程調用(紅色框)是不是包含了Restful(黃色框),這種理解的關鍵在於對RPC的理解。
RPC字面理解是"遠程過程調用",即在一個應用中調用另一個應用的方法。那Restful是滿足的,通過它可以實現在一個應用中調用另一個應用的方法。
但是,上述理解使得RPC的定義過於寬泛。RPC通常特指在一個應用中調用另一個應用的接口而實現的遠程調用,即紅色框所指的范圍。這樣,RPC是不包含Restful的。
因此,第二個問題的答案是Restful不屬於RPC。
要了解遠程過程調用,那先理解過程調用。非常簡單,如下圖,就是調用一個方法。這太常見了,不多解釋。

而在分布式系統中,因為每個服務的邊界都很小,很有可能調用別的服務提供的方法。這就出現了服務A 調用 服務B 中方法的需求,即遠程過程調用。
要想讓服務A 調用 服務B 中的方法,最先想到的就是通過 HTTP 請求實現。是的,這是很常見的,例如 服務B 暴露 Restful接口,然后讓 服務A 調用它的接口。基於Restful的調用方式因為可讀性好(服務B暴露出的是Restful接口,可讀性當然好)而且HTTP請求可以通過各種防火牆,因此非常不錯。
然而,如前面所述,基於Restful的遠程過程調用有着明顯的缺點,主要是效率低、封裝調用復雜。當存在大量的服務間調用時,這些缺點變得更為突出。
服務A 調用 服務B 的過程是應用間的內部過程,犧牲可讀性提升效率、易用性是可取的。基於這種思路,RPC產生了。

那要想實現這個過程該怎么辦呢?別急,咱們一步一步來。
第一、首先,調用方調用的是接口,必須得為接口構造一個假的實現。顯然,要使用動態代理。這樣,調用方的調用就被動態代理接收到了。
第二、動態代理接收到調用后,應該想辦法調用遠程的實際實現。這包括下面幾步:
1. 識別具體要調用的遠程方法的 IP、端口
2. 將調用方法的入參進行序列化
3. 通過通信將請求發送到遠程的方法中
第三、這樣,遠程的服務就接收到了調用方的請求。它應該:
1. 反序列化各個調用參數
2. 定位到實際要調用的方法,然后輸入參數,執行方法
3. 按照調用的路徑返回調用的結果
整個過程如下所示。

這樣,RPC操作就完成了。
調用方調用內部的一個方法,但是被RPC框架偷梁換柱為遠程的一個方法。之間的通信數據可讀性不需要好,只需要RPC框架能讀懂即可,因此效率可以更高。通常使用UDP或者TCP作為通訊協議,當然也可以使用HTTP。