RPC 基本原理與 Apach Thrift 初體驗
首先了解什麼叫RPC,為什麼要RPC,RPC是指遠程過程調用,也就是說兩台伺服器A,B,一個應用部署在A伺服器上,想要調用B伺服器上應用提供的函數/方法,由於不在一個內存空間,不能直接調用,需要通過網路來表達調用的語義和傳達調用的數據。比如說,一個方法可能是這樣定義的:
Employee getEmployeeByName(String fullName)那麼:
首先,要解決通訊的問題,主要是通過在客戶端和伺服器之間建立TCP連接,遠程過程調用的所有交換的數據都在這個連接里傳輸。連接可以是按需連接,調用結束後就斷掉,也可以是長連接,多個遠程過程調用共享同一個連接。
第二,要解決定址的問題,也就是說,A伺服器上的應用怎麼告訴底層的RPC框架,如何連接到B伺服器(如主機或IP地址)以及特定的埠,方法的名稱名稱是什麼,這樣才能完成調用。比如基於Web服務協議棧的RPC,就要提供一個endpoint
URI,或者是從UDDI服務上查找。如果是RMI調用的話,還需要一個RMI Registry來註冊服務的地址。
第三,當A伺服器上的應用發起遠程過程調用時,方法的參數需要通過底層的網路協議如TCP傳遞到B伺服器,由於網路協議是基於二進位的,內存中的參數的值要序列化成二進位的形式,也就是序列化(Serialize)或編組(marshal),通過定址和傳輸將序列化的二進位發送給B伺服器。
第四,B伺服器收到請求後,需要對參數進行反序列化(序列化的逆操作),恢復為內存中的表達方式,然後找到對應的方法(定址的一部分)進行本地調用,然後得到返回值。
第五,返回值還要發送回伺服器A上的應用,也要經過序列化的方式發送,伺服器A接到後,再反序列化,恢復為內存中的表達方式,交給A伺服器上的應用
RPC 框架屏蔽了底層傳輸方式(TCP/UDP)、序列化和反序列化(XML/JSON/二進位)等內容,使用框架只需要知道被調用者的地址和介面就可以了,無須額外地為這些底層內部編程。 目前主流的 RPC 框架有如下幾種
Thrift:Facebook 開源的跨語言框架
gRPC:Google 基於 HTTP/2 和 Protobuf 的能用框架
Avro:Hadoop 的子項目
本文講述 RPC 的基本原理並以 Thrift 框架為例說明 RPC 的使用。
圖1:客戶端與伺服器之間的 RPC 通信過程
圖1描述了客戶端與伺服器 RPC 通信的基本過程,具體來說,包括幾下步驟:
(1)客戶過程以正常方式調用客戶樁(client stub,一段代碼);
(2)客戶樁生成一個消息,然後調用本地操作系統;
(3)客戶端操作系統將消息發送給遠程操作系統;
(4)遠程操作系統將消息交給伺服器樁(server stub,一段代碼);
(5)伺服器樁將參數提取出來,然後調用伺服器過程;
(6)伺服器執行要求的操作,操作完成後將結果返回給伺服器樁;
(7)伺服器樁將結果打包成一個消息,然後調用本地操作系統;
(8)伺服器操作系統將含有結果的消息發送回客戶端操作系統;
(9)客戶端操作系統將消息交給客戶樁;
(10)客戶樁將結果從從消息中提取出來,返回給調用它的客戶過程。
所有這些步驟的效果是,將客戶過程對客戶樁發出的本地調用轉換成對伺服器過程的本地調用,而客戶端和伺服器都不會意識到有中間步驟的存在。
這個時候,你可能會想,既然是調用另一台機器的服務,使用 RESTful API 也可以實現啊,為什麼要選擇 RPC 呢?我們可以從兩個方面對比:
資源粒度。RPC 就像本地方法調用,RESTful API 每一次添加介面都可能需要額外地組織開放介面的數據,這相當於在應用視圖中再寫了一次方法調用,而且它還需要維護開發介面的資源粒度、許可權等。
流量消耗。RESTful API 在應用層使用 HTTP 協議,哪怕使用輕型、高效、傳輸效率高的 JSON 也會消耗較大的流量,而 RPC 傳輸既可以使用 TCP 也可以使用 UDP,而且協議一般使用二制度編碼,大大降低了數據的大小,減少流量消耗。
對接異構第三方服務時,通常使用 HTTP/RESTful 等公有協議,對於內部的服務調用,應用選擇性能更高的二進位私有協議。
Thrift
Thrift是一個跨語言的服務部署框架,最初由Facebook於2007年開發,2008年進入Apache開源項目。Thrift通過一個中間語言(IDL, 介面定義語言)來定義RPC的介面和數據類型,然後通過一個編譯器生成不同語言的代碼(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),並由生成的代碼負責RPC協議層和傳輸層的實現。
Thrift實際上是實現了C/S模式,通過代碼生成工具將介面定義文件生成伺服器端和客戶端代碼(可以為不同語言),從而實現服務端和客戶端跨語言的支持。用戶在Thirft描述文件中聲明自己的服務,這些服務經過編譯後會生成相應語言的代碼文件,然後用戶實現服務(客戶端調用服務,伺服器端提服務)便可以了。其中protocol(協議層, 定義數據傳輸格式,可以為二進位或者XML等)和transport(傳輸層,定義數據傳輸方式,可以為TCP/IP傳輸,內存共享或者文件共享等)被用作運行時庫。
Thrift的協議棧如下圖所示:
在Client和Server的最頂層都是用戶自定義的處理邏輯,也就是說用戶只需要編寫用戶邏輯,就可以完成整套的RPC調用流程。用戶邏輯的下一層是Thrift自動生成的代碼,這些代碼主要用於結構化數據的解析,發送和接收,同時伺服器端的自動生成代碼中還包含了RPC請求的轉發(Client的A調用轉發到Server A函數進行處理)。
協議棧的其他模塊都是Thrift的運行時模塊:
底層IO模塊,負責實際的數據傳輸,包括Socket,文件,或者壓縮數據流等。
TTransport負責以位元組流方式發送和接收Message,是底層IO模塊在Thrift框架中的實現,每一個底層IO模塊都會有一個對應TTransport來負責Thrift的位元組流(Byte Stream)數據在該IO模塊上的傳輸。例如TSocket對應Socket傳輸,TFileTransport對應文件傳輸。
TProtocol主要負責結構化數據組裝成Message,或者從Message結構中讀出結構化數據。TProtocol將一個有類型的數據轉化為位元組流以交給TTransport進行傳輸,或者從TTransport中讀取一定長度的位元組數據轉化為特定類型的數據。如int32會被TBinaryProtocol Encode為一個四位元組的位元組數據,或者TBinaryProtocol從TTransport中取出四個位元組的數據Decode為int32。
TServer負責接收Client的請求,並將請求轉發到Processor進行處理。TServer主要任務就是高效的接受Client的請求,特別是在高並發請求的情況下快速完成請求。
Processor(或者TProcessor)負責對Client的請求做出相應,包括RPC請求轉發,調用參數解析和用戶邏輯調用,返回值寫回等處理步驟。Processor是伺服器端從Thrift框架轉入用戶邏輯的關鍵流程。Processor同時也負責向Message結構中寫入數據或者讀出數據。
Thrift的模塊設計非常好,在每一個層次都可以根據自己的需要選擇合適的實現方式。同時也應該注意到Thrift目前的特性並不是在所有的程序語言中都支持。例如C++實現中有TDenseProtocol沒有TTupleProtocol,而Java實現中有TTupleProtocol沒有TDenseProtocol。
Thrift安裝過程見https://www.jianshu.com/p/82a6bdaabcd3
python代碼實現server端和client端
實現 server 端:
server.py:
實現 client 端:
client.py:
執行驗證結果:
1、先啟動 server,之後再執行 client
2、client 側控制台如果列印的結果為: HELLO,WORLD! ,證明 Thrift 的 RPC 介面定義成功


TAG:keep求索 |