RPC詳解&跨語言RPC實踐

本文將從大的框架層面來聊聊RPC原理和實現,既然叫跨語言RPC,也將以thrift為例講講跨語言RPC如何實現。
在 SOA(面向服務架構,Service-Oriented Architecture)和微服務大行其道的今天,服務之間的遠程調用已經遍布各個互聯網公司。做為服務器端程序,需要考慮性能同時也要考慮與各種語言之間方便的通訊。采用http協議簡單,但性能不高。采用TCP通訊,則需要考慮封包、解包、粘包等等很多因素,而且想寫個高效的TCP服務,也很難。其實,對于此類需求,采用RPC(Remote Procedure Call Protocol)編程最靠譜。使用 RPC 編程被認為是在分布式環境中運行的客戶機和服務器應用程序之間進行可靠通信的最強大、最高效的方法之一。在分布式系統服務群中開發應用,了解RPC一些原理和實現架構,還是很有必要的。
遠程過程調用RPC,就是客戶端基于某種傳輸協議通過網絡向服務提供端請求服務處理,然后獲取返回數據(對于oneway模式則不返還響應結果);而這種調用對于客戶端而言,和調用本地服務一樣方便,開發人員不需要了解具體底層網絡傳輸協議。簡單講,就是本地調用的邏輯處理的過程放在的遠程的機器上,而不是本地服務代理來處理。

LPC(Local Procedure Call) VS RPC

既然存在RPC這種遠程過程調用,必然會有與之對應的本地過程調用了。本地過程調用在Windows編程中稱為LPC,在linux編程中更習慣稱之為IPC(Inter-Process Call,內部進程調用),即進程間通信。本質上就是本地機器上的不同進程之間通信協作的調用方式。進程間通信的方式有:管道、共享內存、信號量、Socket套接字、消息隊列和信號。這些方式的通信原理和實踐我就不細說了了,下面只介紹一下socket通信,因為socket通信同時也是RPC通信的方式。

Socket

Socket一般情況下是用在不同的兩臺機器的不同進程之間通信的,當Socket創建時的類型為 AF_LOCAL或AF_UNIX時,則是本地進程通信了(當然你也可以直接使用網絡套接字,如果你覺得走下網絡更酷,或者以后便于服務分離)。
關于Socket的API介紹,這里就省略了。服務端/客戶端模式的介紹和示例相對很常見,也很容易開發和理解。
從使用網絡套接字Socket來實現進程間通信這個角度來說,其和RPC并沒有什么不同了,所以有些文獻分類時,說廣義來講RPC也應該包括LPC(IPC),因為從大的來講,單機進程通信其實算是遠程過程調用的一種特殊簡化的方式而已。
說完單機的服務調用,在互聯網時代,自然要講web服務(Web Service)了。

Web Service技術

Web Service一般有兩種定義,狹義VS廣義:
狹義定義:特指 W3C組織制定的web service規范技術。其包括SOAP(一個基于XML的可擴展消息信封格式,需同時綁定一個網絡傳輸協議。這個協議通常是HTTP或HTTPS,但也可能是SMTP或XMPP)、WSDL(一個XML格式文檔,用以描述服務端口訪問方式和使用協議的細節。通常用來輔助生成服務器和客戶端代碼及配置信息)和UDDI(一個用來發布和搜索WEB服務的協議,應用程序可借由此協議在設計或運行時找到目標WEB服務)。從上面三個定義就可以看出,這種規范技術是一個重量級的協議。
廣義定義:泛指網絡系統對外提供web服務所使用的技術。可以參考下面web服務的技術體系結構圖來理解。

廣義web service體系架構

web service被W3C設立規范之初,SOAP( Simple Object Access Protocol,簡單對象訪問協議)方案就被提出來。但是,隨著服務化技術和架構的發展,SOAP多少有點過于復雜,因此就出現了簡化版的REST(REpresentational State Transfort,表示性狀態轉移)方案。此后,由于分布式服務應用越來越大,對性能和易用性上面要求越來越大,因此就出現了RPC框架(很多時候,RPC并不被當做一種web service方案。在絕大部分博客中,介紹web service 只會討論 SOAP和REST)。
SOAP
SOAP是基于XML數據格式來交換數據的;其內部定義了一套復雜完善的XML標簽,標簽中包含了調用的遠程過程、參數、返回值和出錯信息等等,通信雙方根據這套標簽來解析數據或者請求服務。與SOAP相關的配套協議是WSDL (Web Service Description Language),用來描述哪個服務器提供什么服務,怎樣找到它,以及該服務使用怎樣的接口規范,類似我們現在聊服務治理中的服務發現功能。SOAP服務整體流程是:1)獲得該服務的WSDL描述,根據WSDL構造一條格式化的SOAP請求發送給服務器,2)接收一條同樣SOAP格式的應答,3)根據先前的WSDL解碼數據。絕大多數情況下,請求和應答使用HTTP協議傳輸,那么發送請求就使用HTTP的POST方法。
REST
由于SOAP方案過于龐大復雜,在很多簡單的web服務應用場景中,輕量級的REST就出現替代SOAP方案了。和SOAP相比,REST只是對URI做了一些規范,數據才有JSON格式,底層傳輸使用HTTP/HTTPS來通信,因此,所有web服務器都可以快速支持該方案;開發人員也可以快速學習和使用。
由于數據返回格式是自定義的,絕大部分使用JSON,這種數據結構節省帶寬,并且前端JavaScript能天生支持。但是REST是基于HTTP協議的無狀態規范,所以只能適應無狀態場景。
RPC
RPC家族中,RMI是Java制定的遠程通信協議。RMI既然是Java的標準RPC組件,那必然其他編程語言就無法使用了;因此,Thrift這種基于IDL來跨語言的RPC組件就出現了。Thrift的使用者,只需要按照Thrift官方規定的方式來寫API結構,然后生成對應語言的API接口,繼而就可以跨語言完成遠程過程調用了。但是,作為服務化的組件,如果沒有服務治理來完成大規模應用集群中服務調用管理工作,則運維工作則是非常繁重的,因此類似dubbo這種包含服務治理的RPC組件出現了。
前面扯了很多跟RPC相關的定義以及RPC的演變。下面介紹RPC的原理。

RPC

RMI介紹

RMI(Remote Method Invocation,遠程方法調用)也就是RPC本身的實現方式。在JDK 1.2的時候,引入到Java體系的。當應用比較小,性能要求不高的情況下,使用RMI還是挺方便快捷的。下面先看看RMI的調用流程。

RMI服務調用結構圖
RMI調用時序圖

概念說明:
stub(樁):stub實際上就是遠程過程在客戶端上面的一個代理proxy。當我們的客戶端代碼調用API接口提供的方法的時候,RMI生成的stub代碼塊會將請求數據序列化,交給遠程服務端處理,然后將結果反序列化之后返回給客戶端的代碼。這些處理過程,對于客戶端來說,基本是透明無感知的。
remote:這層就是底層網絡處理了,RMI對用戶來說,屏蔽了這層細節。stub通過remote來和遠程服務端進行通信。
skeleton(骨架):和stub相似,skeleton則是服務端生成的一個代理proxy。當客戶端通過stub發送請求到服務端,則交給skeleton來處理,其會根據指定的服務方法來反序列化請求,然后調用具體方法執行,最后將結果返回給客戶端。
registry(服務發現):借助JNDI發布并調用了rmi服務。實際上,JNDI就是一個注冊表,服務端將服務對象放入到注冊表中,客戶端從注冊表中獲取服務對象。rmi服務,在服務端實現之后需要注冊到rmi server上,然后客戶端從指定的rmi地址上lookup服務,調用該服務對應的方法即可完成遠程方法調用。registry是個很重要的功能,當服務端開發完服務之后,要對外暴露,如果沒有服務注冊,則客戶端是無從調用的,即使服務端的服務就在那里。

RPC架構剖析

遠程過程調用RPC就是本地動態代理隱藏通信細節,通過組件序列化請求,走網絡到服務端,執行真正的服務代碼,然后將結果返回給客戶端,反序列化數據給調用方法的過程。
常用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)。是支持跨語言的RPC框架。

常見的RPC序列化協議

  • XML(Extensible Markup Language)是一種常用的序列化和反序列化協議,具有跨機器,跨語言等優點。狹義web service就是基于SOAP消息傳遞協議(一個基于XML的可擴展消息信封格式)來進行數據交換的。
  • Hessian是一個動態類型,簡潔的,可以移植到各個語言的二進制序列化對象協議。采用簡單的結構化標記、采用定長的字節記錄值、采用引用取代重復遇到的對象。
  • JSON(Javascript Object Notation)起源于弱類型語言Javascript, 是采用"Attribute-value"的方式來描述對象協議。與XML相比,其協議比較簡單,解析速度比較快。
  • Protocol Buffers 是google提供的一個開源序列化框架,是一種輕便高效的結構化數據存儲格式,可以用于結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。同 XML 相比, Protobuf 的主要優點在于性能高。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。
  • Thrift 既是rpc框架,同時也具有自己內部定義的傳輸協議規范(TProtocol)和傳輸數據標準(TTransports),通過IDL腳本對傳輸數據的數據結構(struct) 和傳輸數據的業務邏輯(service)根據不同的運行環境快速的構建相應的代碼,并且通過自己內部的序列化機制對傳輸的數據進行簡化和壓縮提高高并發、 大型系統中數據交互的成本。

本文將從兩個部分介紹RPC。以dubbo為例介紹一個RPC的完整架構以及一個java語言的RPC框架簡易實現demo;另一部分是剖析跨語言RPC框架thrift以及demo。

通用RPC架構

最近剛升為apache頂級項目的dubbo可以說是java語言中RPC架構最流行的框架。同時Dubbo的文檔也是開源軟件中寫的最詳細的文檔之一,細看dubbo官方文檔。下圖是dubbo的整體設計:

duboo分層架構

圖例說明:
圖中左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。
圖中從下至上分為十層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,Service 和 Config 層為 API,其它各層均為 SPI。
圖中綠色小塊的為擴展接口,藍色小塊為實現類,圖中只顯示用于關聯各層的實現類。
圖中藍色虛線為初始化過程,即啟動時組裝鏈,紅色實線為方法調用過程,即運行時調時鏈,紫色三角箭頭為繼承,可以把子類看作父類的同一個節點,線上的文字為調用的方法。
各層說明:
config 配置層:對外配置接口,以 ServiceConfig, ReferenceConfig 為中心,可以直接初始化配置類,也可以通過 spring 解析配置生成配置類
proxy 服務代理層:服務接口透明代理,生成服務的客戶端 Stub 和服務器端 Skeleton, 以 ServiceProxy 為中心,擴展接口為 ProxyFactory
registry 注冊中心層:封裝服務地址的注冊與發現,以服務 URL 為中心,擴展接口為 RegistryFactory, Registry, RegistryService
cluster 路由層(RMI沒有這一層,因為直接指定具體服務端或客戶端):封裝多個提供者的路由及負載均衡,并橋接注冊中心,以 Invoker 為中心,擴展接口為 Cluster, Directory, Router, LoadBalance
monitor 監控層(非核心,非必須):RPC 調用次數和調用時間監控,以 Statistics 為中心,擴展接口為 MonitorFactory, Monitor, MonitorService
protocol 遠程調用層(從protocol 遠程調用層往下4層可以看成RMI圖中的Remote層):封裝 RPC 調用,以 Invocation, Result 為中心,擴展接口為 Protocol, Invoker, Exporter
exchange 信息交換層:封裝請求響應模式,同步轉異步,以 Request, Response 為中心,擴展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 網絡傳輸層:抽象 mina 和 netty 為統一接口,以 Message 為中心,擴展接口為 Channel, Transporter, Client, Server, Codec
serialize 數據序列化層:可復用的一些工具,擴展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool

上面的dubbo展示了一個完整的帶服務治理功能的RPC框架的分層架構。下面我們寫一個簡易的RPC框架,我把dubbo中的一些非核心層省略掉。具體細節見demo

  • 省略掉config層,直接通過單例模式獲取服務。
  • 保留proxy 服務代理層,客戶端采用簡單的反射機制實現服務接口的動態代理,demo中對應ServiceProxyClient類;服務端初始化的時候,按一定規則寫進Map映射中,這樣直接獲取服務實例對象即可,類似RMI的skeleton模塊,demo中對應ServiceProcessor類。
  • 省略registry 注冊中心層,demo服務只有一個實例機器提供,故直接寫死ip和端口,在ClientRemoter類中getDataRemote方法中直接寫死。
  • 省略cluster 路由層,只有當服務實例有多個時才需要通過算法決定哪個服務實例“接待”請求。
  • 省略monitor 監控層,這個是服務治理需要的,不影響核心流程調用。
  • 保留protocol 遠程調用層以下四層 ,統稱為remote層。負責請求雙方調用協議的約定,序列化、傳輸。client端的remote層對應demo中ClientRemoter類,將請求服務接口轉化成二進制通過socket發送給服務端;服務端的remote層對應demo中的ServerRemoter類,負責客戶端的二進制按照協議轉化成本地的方法調用,然后又將返回結果通過按照協議翻譯成二進制通過socket送給客戶端。

跨語言RPC框架Thrif

Thrift 是一個輕量級、跨語言的開源RPC框架,并且能夠根據 *.thrift 文件自動生成聯合代碼。Thrift 提供了整潔的數據傳輸、序列化和應用層處理。通過簡單的接口定義語言,生成跨語言的的程序代碼,該代碼通過抽象棧構建了可供 RPC 使用的 Client 和 Server。開發者可以在生成的 Client 和 Sever 代碼的基礎上去實現自己的業務邏輯。

IDL(interface description language,接口定義語言)
java語言之間RPC調用中的share包類似這里講的IDL。因為跨語言之間的調用,需要一個跨語言的服務接口定義,IDL就是為了滿足這個場景而出現的。IDL是很多RPC框架用來支持跨語言環境調用的一個服務描述組件,一般都是采用文本格式來定義。接口定義文件是 Thrift 開發的核心,定義了 RPC 過程中通信的數據結構和通信的接口方法定義等。Thrift的不同版本定義IDL的語法也不太相同,這里使用Thrift-0.9.2這個版本來介紹Java下的IDL定義:

  • namespace 定義包名
  • struct 定義服務接口的參數,返回值使用到的類結構。如果接口的參數都是基本類型,則不需要定義struct
  • service 定義接口
  • 基本類型

詳細的接口定義參考官方文檔

生成代碼
生成代碼首先需要安裝thrift,網上關于thrift的安裝非常多,也可以參考官網說明
通過以下命令生成調用端的服務接口文件。
thrift -r --gen java xx.thrift //xx.thrift是使用idl定義的服務接口文件
方法調用模型分析
Thrift的方法調用模型很簡單,就是通過方法名和實際方法實現類的注冊完成,沒有使用反射機制,類加載機制,整個調用模型和上面講的RPC調用模型基本一致。RPC調用本質上就是一種網絡編程,客戶端向服務器發送消息,服務器拿到消息之后做后續動作。只是RPC這種消息比較特殊,它封裝了方法調用,包括方法名,方法參數。服務端拿到這個消息之后,解碼消息,然后要通過方法調用模型來完成實際服務器端業務方法的調用。
和方法調用相關的幾個核心類,都可以與上面的RPC核心類對應:

  • 自動生成的Iface接口,是遠程方法的頂層接口
  • 自動生成的Processor類及相關父類,包括TProcessor接口,TBaseProcess抽象類
  • ProcessFunction抽象類,抽象了一個具體的方法調用,包含了方法名信息,調用方法的抽象過程等
  • TNonblcokingServer,是NIO服務器的默認實現,通過Args參數來配置Processor等信息
  • FrameBuffer類,服務器NIO的緩沖區對象,這個對象在服務器端收到全包并解碼后,會調用Processor去完成實際的方法調用
  • 服務器端的方法的具體實現類,實現Iface接口

更詳細的看demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容