簡單介紹RPC協議及常見框架,對比傳統restful api和RPC方式的優缺點。常見RPC框架,gRPC及序列化方式Protobuf等
HTTP協議
http協議是基于tcp協議的,tcp協議是流式協議,包頭部分可以通過多出的\r\n來分界,包體部分如何分界呢?這是協議本身要解決的問題。目前一般有兩種方式,第一種方式就是在包頭中有個content-Length字段,這個字段的值的大小標識了POST數據的長度,服務器收到一個數據包后,先從包頭解析出這個字段的值,再根據這個值去讀取相應長度的作為http協議的包體數據。
瀏覽器connect 80端口
RESTful API (http+json)
網站即軟件,而且是一種新型的軟件,這種"互聯網軟件"采用客戶端/服務器模式,建立在分布式體系上,通過互聯網通信,具有高延時(high latency)、高并發等特點。
它首次出現在 2000 年 Roy Fielding 的博士論文中,他是 HTTP 規范的主要編寫者之一。Representational State Transfer,翻譯是”表現層狀態轉化”,通俗來講就是:資源在網絡中以某種表現形式進行狀態轉移。
總結一下什么是RESTful架構:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的某種表現層,比如用JSON,XML,JPEG等;
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。
URL定位資源,用HTTP動詞(GET,POST,DELETE,DETC)描述操作。
用HTTP協議里的動詞來實現資源的添加,修改,刪除等操作。即通過HTTP動詞來實現資源的狀態扭轉:
GET 用來獲取資源,
POST 用來新建資源(也可以用于更新資源),
PUT 用來更新資源,
DELETE 用來刪除資源。
RPC
進程間通信(IPC,Inter-Process Communication),指至少兩個進程或線程間傳送數據或信號的一些技術或方法。進程是計算機系統分配資源的最小單位。每個進程都有自己的一部分獨立的系統資源,彼此是隔離的。為了能使不同的進程互相訪問資源并進行協調工作,才有了進程間通信。這些進程可以運行在同一計算機上或網絡連接的不同計算機上。 進程間通信技術包括消息傳遞、同步、共享內存和遠程過程調用。 IPC是一種標準的Unix通信機制。
有兩種類型的進程間通信(IPC)。
本地過程調用(LPC)LPC用在多任務操作系統中,使得同時運行的任務能互相會話。這些任務共享內存空間使任務同步和互相發送信息。
遠程過程調用(RPC)RPC類似于LPC,只是在網上工作。RPC開始是出現在Sun微系統公司和HP公司的運行UNIX操作系統的計算機中。
為什么RPC呢?就是無法在一個進程內,甚至一個計算機內通過本地調用的方式完成的需求,比如比如不同的系統間的通訊,甚至不同的組織間的通訊。由于計算能力需要橫向擴展,需要在多臺機器組成的集群上部署應用
RPC的核心并不在于使用什么協議。RPC的目的是讓你在本地調用遠程的方法,而對你來說這個調用是透明的,你并不知道這個調用的方法是部署哪里。通過RPC能解耦服務,這才是使用RPC的真正目的。RPC的原理主要用到了動態代理模式,至于http協議,只是傳輸協議而已。簡單的實現可以參考spring remoting,復雜的實現可以參考dubbo。
簡單的說,
RPC就是從一臺機器(客戶端)上通過參數傳遞的方式調用另一臺機器(服務器)上的一個函數或方法(可以統稱為服務)并得到返回的結果。
RPC 會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊) RPC 是一個請求響應模型。
客戶端發起請求,服務器返回響應(類似于Http的工作方式) RPC 在使用形式上像調用本地函數(或方法)一樣去調用遠程的函數(或方法)。
RPC通信過程
默認socket通信。本地機器的RPC框架反序列化出執行結果,函數return這個結果
RPC和restful api對比
REST是一種設計風格,它的很多思維方式與RPC是完全沖突的。 RPC的思想是把本地函數映射到API,也就是說一個API對應的是一個function,我本地有一個getAllUsers,遠程也能通過某種約定的協議來調用這個getAllUsers。至于這個協議是Socket、是HTTP還是別的什么并不重要; RPC中的主體都是動作,是個動詞,表示我要做什么。 而REST則不然,它的URL主體是資源,是個名詞。而且也僅支持HTTP協議,規定了使用HTTP Method表達本次要做的動作,類型一般也不超過那四五種。這些動作表達了對資源僅有的幾種轉化方式。
RPC的根本問題是耦合。RPC客戶端以多種方式與服務實現緊密耦合,并且很難在不中斷客戶端的情況下更改服務實現。RPC更偏向內部調用,REST更偏向外部調用。
Web 服務應該算是 RPC 的一個子集,理論上 RPC 能實現的功能, 用 Web 服務也能實現,甚至很多 RPC 框架選用 HTTP 協議作為傳輸層。
現在很多網站的 API 都是以 HTTP 服務的形式提供的,這也算是 RPC 的一種形式。
區別主要在這 2 個東西設計的出發點不太一樣:
HTTP 是面向瀏覽器設計的應用層協議,操作的核心在資源。我們更多的用 Web 服務在做網站。
RPC 是為了在像在本地調用一個函數那樣調用遠程的代碼而設計的,所以更關注減少本地調用和遠程調用的差異,像 SOAP(簡單對象訪問協議) 這種東西是可以把對象當參數傳的。
我們討論 RPC 和 Web 的區別,其實是在談論 2 個東西:序列化協議和傳輸協議。序列化協議比如常見的 XML,JSON 和比較現代的 Protocol Buffers、Thrift。 傳輸協議比如 TCP、UDP 以及更高層的 HTTP 1.1、HTTP 2.0。
一般我們考慮用 RPC 而不是 HTTP 構建自己的服務,通常是考慮到下面的因素:
接口是否需要 Schema 約束
是否需要更高效的傳輸協議(TCP,HTTP 2.0)
是否對數據包的大小非常敏感
比如 HTTP 是基于文本的協議,頭部有非常多冗余(對于 RPC 服務而言)。HTTP 中我們用的最多就是 RESTful ,而 RESTful 是個弱 Schema 約束,大家通過文檔溝通,但是如果我就是不在實現的時候對接口文檔約定的參數做檢查,你也不能把我怎么樣。這個時候 Thrift 這種序列化協議的優勢就體現出來了,由于 Schema 的存在,可以保證服務端接受的參數和 Schema 保持一致。
RPC框架
Call ID映射。我們怎么告訴遠程機器我們要調用Multiply,而不是Add或者FooBar呢?在本地調用中,函數體是直接通過函數指針來指定的,我們調用Multiply,編譯器就自動幫我們調用它相應的函數指針。但是在遠程調用中,函數指針是不行的,因為兩個進程的地址空間是完全不一樣的。所以,在RPC中,所有的函數都必須有自己的一個ID。這個ID在所有進程中都是唯一確定的。客戶端在做遠程過程調用時,必須附上這個ID。然后我們還需要在客戶端和服務端分別維護一個 {函數 <–> Call ID} 的對應表。兩者的表不一定需要完全相同,但相同的函數對應的Call ID必須相同。當客戶端需要進行遠程調用時,它就查一下這個表,找出相應的Call ID,然后把它傳給服務端,服務端也通過查表,來確定客戶端需要調用的函數,然后執行相應函數的代碼。
序列化和反序列化。客戶端怎么把參數值傳給遠程的函數呢?在本地調用中,我們只需要把參數壓到棧里,然后讓函數自己去棧里讀就行。但是在遠程過程調用時,客戶端跟服務端是不同的進程,不能通過內存來傳遞參數。甚至有時候客戶端和服務端使用的都不是同一種語言(比如服務端用C++,客戶端用Java或者Python)。這時候就需要客戶端把參數先轉成一個字節流,傳給服務端后,再把字節流轉成自己能讀取的格式。這個過程叫序列化和反序列化。同理,從服務端返回的值也需要序列化反序列化的過程。
網絡傳輸。遠程調用往往用在網絡上,客戶端和服務端是通過網絡連接的。所有的數據都需要通過網絡傳輸,因此就需要有一個網絡傳輸層。網絡傳輸層需要把Call ID和序列化后的參數字節流傳給服務端,然后再把序列化后的調用結果傳回客戶端。只要能完成這兩者的,都可以作為傳輸層使用。因此,它所使用的協議其實是不限的,能完成傳輸就行。盡管大部分RPC框架都使用TCP協議,但其實UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也屬于這層的東西。
目前有很多Java的RPC框架,有基于Json的,有基于XML,也有基于二進制對象的。
論復雜度,RPC框架肯定是高于簡單的HTTP接口的。但毋庸置疑,HTTP接口由于受限于HTTP協議,需要帶HTTP請求頭,導致傳輸起來效率或者說安全性不如RPC
常用RPC框架
支持Java最多,golang
Netty - Netty框架不局限于RPC,更多的是作為一種網絡協議的實現框架,比如HTTP,由于RPC需要高效的網絡通信,就可能選擇以Netty作為基礎。
brpc是一個基于protobuf接口的RPC框架,在百度內部稱為“baidu-rpc”,它囊括了百度內部所有RPC協議,并支持多種第三方協議,從目前的性能測試數據來看,brpc的性能領跑于其他同類RPC產品。
Dubbo是Alibaba開發的一個RPC框架,遠程接口基于Java Interface, 依托于Spring框架。
gRPC的Java實現的底層網絡庫是基于Netty開發而來,其Go實現是基于net庫。
Thrift是Apache的一個項目(http://thrift.apache.org),前身是Facebook開發的一個RPC框架,采用thrift作為IDL (Interface description language)。
jsonrpc
JSON-RPC
python web接口實現(restful方式、jsonrpc方式)
區塊鏈項目中用的較多?資料不是很多
JSON-RPC是一種序列化協議。JSON 是 JS 對象的字符串表示法,它使用文本表示一個 JS 對象的信息,本質是一個字符串。
非常簡單,方便,速度慢
相關Python 包(直接集成到flask和django)
Flask-JSONRPC,django-json-rpc;jsonrpcserver,jsonrpcclient
thrift
Python RPC 之 Thrift
Facebook開源的跨語言RPC框架。
gRPC
gRPC 官方文檔中文版
深入了解gRPC協議-知乎
tensorflow分布式與tensorflow serving底層通信都是是用的grpc
序列化用protobuf,通信使用http2
latest Google APIs will have gRPC versions of their interfaces, letting you easily build Google functionality into your applications.
支持 C, C++, Node.js, Python, Ruby, Objective-C,PHP and C#
HTTP2
HTTP/2 是 HTTP 協議自 1999 年 HTTP 1.1 發布后的首個更新,主要基于 SPDY 協議。
HTTP/2的主要目標是通過啟用完整請求和響應復用來減少延遲,通過有效壓縮HTTP頭字段來最大限度地降低協議開銷,并添加對請求優先級和服務器推送的支持;多路復用(同一tcp,多個流),頭部壓縮,服務推送。
Protobuf
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用于結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。
同 XML 相比, Protobuf 的主要優點在于性能高。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。
框架選擇
如何選擇
什么時候應該選擇gRPC而不是Thrift
需要良好的文檔、示例
喜歡、習慣HTTP/2、ProtoBuf
對網絡傳輸帶寬敏感
什么時候應該選擇Thrift而不是gRPC
需要在非常多的語言間進行數據交換
對CPU敏感
協議層、傳輸層有多種控制要求
需要穩定的版本
不需要良好的文檔和示例
總的來說,Python rpc框架選擇較少,thrift性能最好,grpc性能比thrift稍差,原因是多了http2,而thrift直接基于tcp,但grpc序列化方案更通用(protobuf)優秀,文檔較好;
jsonrpc 本身基于http/1進行通信,速度最慢,相對于之前速度無提升,只是接口和數據格式更為統一;
gRPC不足
1)GRPC尚未提供連接池
2)尚未提供“服務發現”、“負載均衡”機制
3)因為基于HTTP2,絕大部多數HTTP Server、Nginx都尚不支持,即Nginx不能將GRPC請求作為HTTP請求來負載均衡,而是作為普通的TCP請求。(nginx將會在1.9版本支持)