機器之間如何高效交流?90後技術專家帶你讀懂RPC框架

OB君:在單機環境下,進程之間的通信主要經過IPC來實現。可是在不一樣的機器上,RPC就成爲了進行通信的經典方式。本文由OceanBase的90後技術專家符風向你們娓娓道來RPC框架背後的What、Why和How,並對目前主流的C++ RPC框架進行對比分析。

本文做者:符風
現任螞蟻金服OceanBase團隊技術專家,2012年畢業後加入Oceanbase團隊,主要負責OceanBase基礎庫的建設工做。程序員

早期的計算機程序都是單機程序,一個程序只會在一臺機器上跑。若是一臺機器上的A進程和B進程,它們之間須要通信交流,就只能經過IPC(Inter-Process Communication)的方式。典型的就有管道、信號量、共享內存等方式。可是若是這兩個進程分別運行在不一樣的機器上,那麼使用IPC就不夠了,還須要把網絡通信這個功能加進來。其中,RPC就是在不一樣機器上進行通信的經典方式。數據庫

什麼是RPC?

說白了,RPC就是一種網絡上程序之間的溝通方式。稍微正式一點的定義咱們能夠參考維基百科上查詢到的結果:編程

RPC(Remote Procedure Call)或者叫作遠程過程調用是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的程序,而程序員無需額外地爲這個交互做用編程。
—— 維基百科

從維基百科的介紹中,咱們能夠總結出如下幾個關鍵字:通訊協議,跨計算機調用,易編程數組

  • RPC必需要有本身的通信協議,協議包括序列化協議和網絡協議,典型的有JSON、XML等等
  • 由於要跨計算機調用,因此RPC必需要有本身的網絡傳輸層,典型的有TCP/IP等等
  • RPC都是封裝成函數調用的方式,使用RPC時就像調用一個函數那樣就能夠了

爲何用RPC?

網絡上進程進行通信的方式有不少,好比像咱們瀏覽網頁使用的HTTP協議,爲何不是直接發個HTTP請求進行通信呢?使用RPC進行封裝的通信方式有什麼優點呢?最大的好處就是易用性安全

若是沒有RPC的封裝,一臺計算機上的進程要訪問另一臺機器上進程的內容須要怎麼作呢?bash

1. 以一種對方可以理解的協議構建請求包
2. 將這個請求包設法發送到對方機器的進程上
3. 對方機器根據協議理解請求包的內容
4. 對方機器處理請求,並以客戶機可以理解的協議構造回覆包
5. 想辦法把這個回覆包發送到請求包來源的遠程機器上的具體進程,並設法把請求包和回覆包關聯起來

咱們能夠看到,這是一個比較複雜而且煩人的工做,單單是若是把消息從一臺機器發送到另一臺機器就涉及不少問題:好比怎麼進行傳輸、如何通知進程等等。況且還要處理網絡上的各類異常狀況:超時、斷連接、網絡抖動等。微信

若是使用RPC進行通信須要幾個步驟的?最簡單的狀況就是直接調用相應函數就能夠了,由RPC框架把上面的這五部操做都完成了。網絡

List employees;
RPC.getEmployeeList(dep_id, employees);
複製代碼

是否是感受這個世界都變得清爽了?數據結構

典型的RPC框架介於傳輸層和應用中間,它會幫助處理:框架

  1. 可靠性
    好比傳輸層遇到的錯誤。
  2. 平臺無關性
    好比Windows平臺是否能夠和Linux平臺進行通信?64位的系統是否能夠和32位系統進行通信?
  3. 服務發現和路由選擇
    RPC調用其實是對某個服務的調用,那麼RPC框架須要解決具體調用須要落到哪臺機器的哪一個進程上。
  4. 消息分發
    通常一個進程上會提供多種RPC調用,RPC框架須要提供區別不一樣類型RPC消息並轉到相應處理函數上。
  5. 安全性

RPC框架的設計

RPC框架的核心部分有如下幾個:

  1. RPC接口
  2. 對象序列化和反序列化
  3. 傳輸協議和傳輸層
  4. RPC消息分發

RPC接口

RPC的做用就是讓使用者調用遠程請求就像調用本地函數,因此不論是本地客戶端仍是遠程服務端須要使用一套統一的接口,而後兩邊分別實現本身的邏輯。

舉個例子:

好比在設計一個獲取部門員工列表的RPC請求,接口可能以下設計,傳入一個部門ID,返回部門成員的列表:

void getEmployeeList(const DepartmentID &id, EmployeeList &list);
複製代碼

而後在客戶端部分根據接口能夠這樣實現:

// clientvoid getEmployeeList(const DepartmentID &id, EmployeeList &list){
    auto result = Transport.send(GET_EMPLOYEE_LIST, encode(id));
    list.decode(result.buffer());}
複製代碼

服務端部分一樣根據接口實現具體邏輯:

// servervoid getEmployeeList(const DepartmentID &id, EmployeeList &list){
    auto dep = department_map[id];
    list = dep.get_employees();}
複製代碼

接口的做用就是:告訴客戶端你只能這樣調用這個RPC,同時告訴服務端客戶端那邊只會這樣調用。

上面三段代碼中,具體RPC實現者只須要關心接口和服務端的實現邏輯。因此通常RPC框架會提供友好的封裝,最簡單的形式就是隻須要實現一個函數,函數的簽名就是接口,實現就是服務端的代碼,客戶端的代碼則由框架自動填充。

對象序列化和反序列化

對象序列化的方式有不少種,好比比較通用的JSON、Protocol Buffers等,也能夠按照本身的需求本身定製序列化和反序列化方式。

OceanBase就是設計了本身的對象序列化方式,以知足使用過程當中對高性能、少資源佔用和易用性各方面的權衡。它相比protobuf能夠擁有更高的序列化性能和更小的空間佔用,而且和OceanBase的數據結構深度結合,不用像protobuf那樣使用本身的DSL語言定義數據類型。

傳輸協議和傳輸層

RPC要實現網絡上的兩臺計算機間的通信,必須依賴具體的網絡傳輸層才能完成。

傳輸層的選擇也有不少,用得比較多的好比TCP、UDP這類的,也有爲了追求性能選擇RDMA/RoCE/DPDK,或者像gRPC那樣選擇更上層的HTTP2。

傳輸層的選擇須要考慮幾個因素:

  • 物理限制,有些協議須要在特定的硬件環境中才能運行
  • 傳輸特性,好比TCP協議對可靠性有必定保證,可是須要用戶本身處理黏包問題
  • 性能、安全性、是否好調試等因素也能夠進行參考

OceanBase的RPC框架使用的傳輸層主要是基於TCP協議的封裝。咱們沒有選擇UDP是爲了簡化傳輸層的邏輯,TCP相比UDP而言擁有更完善的控制能力,框架不須要再爲拆包組包這些邏輯編寫額外的代碼。而且,咱們爲TCP封裝了連接複用的功能,避免由於網絡延遲等緣由須要建立過多的TCP鏈接。固然咱們目前正在探索更多的協議加入至傳輸層,以知足不一樣場景下的傳輸需求。

RPC消息分發

當服務端收到一個RPC消息後,須要根據RPC類型和相應規則分發請求到相應的處理函數上。常見的作法有:

  1. 暴力switch/if else,由編譯器作這方面的優化
  2. 實現查找表,以RPC類型(常見爲枚舉類型)做爲下標定位處處理函數
  3. hashmap

其中,使用hashmap的方式最爲靈活。前兩種方案的實現都有必定的侷限性。

OceanBase早期用的是if else分支判斷:若是RPC請求包是該類型的,就選擇這個處理函數,不然選擇另外的處理函數。原先這樣作的是由於簡單易實現,而且當時請求的種類也不多,效率上並無太大差異。

後來隨着請求種類的增長,使用分支判斷風格分發請求在性能上的劣勢就慢慢凸顯出來了,咱們就改用了使用RPC Code做爲索引到數組中去查找對應的處理函數。使用查找表最大的問題是若是RPC Code跨度很大,就須要一個很是大的數組來保存這個隱射關係,它會使用更多的內存以及內存訪問的局部性變差。Hashmap是更通用一些的方式,後續OceanBase會考慮遷移到這個方案上。

主流C++ RPC框架對比

gRPC

gRPC是一個高性能、通用的開源RPC框架,其由Google主要面向移動應用開發並基於HTTP/2協議標準而設計,基於ProtoBuf(Protocol Buffers)序列化協議開發。它支持衆多開發語言,固然也包含了C++了。

gRPC最大的優點是由於IDL基於ProtoBuf的緣故因此很是簡單易用,生成的代碼也比較小巧易懂,文檔很是健全。缺點則是傳輸層綁定了HTTP/2,序列化層綁定了ProtoBuf,二者都不支持動態定製。HTTP/2對於分佈式數據庫場景下的對性能有極致追求的場景不太友好。

Thrift

Thrift是一種接口描述語言和二進制通信協議,它被用來定義和建立跨語言的服務。它被看成一個遠程過程調用(RPC)框架來使用,是由Facebook爲「大規模跨語言服務開發」而開發的,目前由Apache基金會管理。

Thrift最大的優點是靈活性很是高,每一層均可以讓用戶作出不一樣的選擇從而構建出合適本身場景的RPC需求。各層基本均可以用戶本身進行定製,好比用戶自定義的協議、傳輸方式和路由分發規則等。這個經過官網的模塊圖就能夠看出來:

PhxRPC

PhxRPC是微信後臺團隊推出的一個很是簡潔小巧的RPC框架,編譯生成的庫文件很是小巧。根據官網的介紹,它使用ProtoBuf做爲IDL,而且只支持ProtoBuf。使用半同步半異步模式,也就是說有專門的IO線程處理epoll。支持ucontext和過載保護。缺點是功能簡單,文檔也比較少。

brpc

brpc是百度開源的RPC框架。它是一套比較完整的RPC框架,和Thrift一個量級。brpc使用ProtoBuf做爲IDL,也可使用Thrift工具生成代碼整合至brpc後和Thrift服務進行通信。brpc也是一套很是靈活的框架,支持各層的自定義控制;而且從一些網站的介紹和對比測試來看,它的性能也頗有競爭力。

總結

RPC是實現網絡間不一樣計算進行通信的手段,它很好的屏蔽了網絡層的細節,讓用戶跨計算機訪問就像調用本地函數那樣方便。可是它並不適用於全部的通信場景,好比作大數據傳輸時可能直接使用傳輸層的API更加方便直觀,再好比它在處理服務端消息推送的時候會比較麻煩。

實現RPC框架也須要權衡不少因素,好比安全性和通信效率的平衡、異常處理機制的完善程度等等。

不管如何,一個好用的RPC框架在絕大多數的分佈式系統中都扮演着很是重要的角色。

符風邀請你加入OceanBase技術交流羣

想跟本文做者 符風 深刻交流嗎?

想認識螞蟻金服OceanBase的一線技術專家嗎?

掃描下方二維碼聯繫螞蟻金服加羣小助手,快速加入OceanBase技術交流羣!

相關文章
相關標籤/搜索