RPC框架詳細(xì)內(nèi)容:
http://www.lxweimin.com/p/193634cca86a
在一個典型 RPC 的使用場景中,包含了服務(wù)發(fā)現(xiàn)、負(fù)載、容錯、網(wǎng)絡(luò)傳輸、序列化等組件,其中“RPC 協(xié)議”就指明了程序如何進(jìn)行網(wǎng)絡(luò)傳輸和序列化。
RPC 核心功能
RPC 的核心功能是指實(shí)現(xiàn)一個 RPC 最重要的功能模塊,就是上圖中的”RPC 協(xié)議”部分:
一個 RPC 的核心功能主要有 5 個部分組成,分別是:客戶端、客戶端 Stub、網(wǎng)絡(luò)傳輸模塊、服務(wù)端 Stub、服務(wù)端等。
下面分別介紹核心 RPC 框架的重要組成:
客戶端(Client):服務(wù)調(diào)用方。
客戶端存根(Client Stub):存放服務(wù)端地址信息,將客戶端的請求參數(shù)數(shù)據(jù)信息打包成網(wǎng)絡(luò)消息,再通過網(wǎng)絡(luò)傳輸發(fā)送給服務(wù)端。
服務(wù)端存根(Server Stub):接收客戶端發(fā)送過來的請求消息并進(jìn)行解包,然后再調(diào)用本地服務(wù)進(jìn)行處理。
服務(wù)端(Server):服務(wù)的真正提供者。
Network Service:底層傳輸,可以是 TCP 或 HTTP。
在這兩次網(wǎng)絡(luò)傳輸中使用了 HTTP 協(xié)議,建立 HTTP 協(xié)議之間有 TCP 三次握手,斷開 HTTP 協(xié)議時(shí)有 TCP 四次揮手。
一次 RPC 調(diào)用流程如下:
服務(wù)消費(fèi)者(Client 客戶端)通過本地調(diào)用的方式調(diào)用服務(wù)。
客戶端存根(Client Stub)接收到調(diào)用請求后負(fù)責(zé)將方法、入?yún)⒌刃畔⑿蛄谢ńM裝)成能夠進(jìn)行網(wǎng)絡(luò)傳輸?shù)南Ⅲw。
客戶端存根(Client Stub)找到遠(yuǎn)程的服務(wù)地址,并且將消息通過網(wǎng)絡(luò)發(fā)送給服務(wù)端。
服務(wù)端存根(Server Stub)收到消息后進(jìn)行解碼(反序列化操作)。
服務(wù)端存根(Server Stub)根據(jù)解碼結(jié)果調(diào)用本地的服務(wù)進(jìn)行相關(guān)處理
服務(wù)端(Server)本地服務(wù)業(yè)務(wù)處理。
處理結(jié)果返回給服務(wù)端存根(Server Stub)。
服務(wù)端存根(Server Stub)序列化結(jié)果。
服務(wù)端存根(Server Stub)將結(jié)果通過網(wǎng)絡(luò)發(fā)送至消費(fèi)方。
客戶端存根(Client Stub)接收到消息,并進(jìn)行解碼(反序列化)。
服務(wù)消費(fèi)方得到最終結(jié)果。
RPC 核心之功能實(shí)現(xiàn)
RPC 的核心功能主要由 5 個模塊組成,如果想要自己實(shí)現(xiàn)一個 RPC,最簡單的方式要實(shí)現(xiàn)三個技術(shù)點(diǎn),分別是:
服務(wù)尋址
數(shù)據(jù)流的序列化和反序列化
網(wǎng)絡(luò)傳輸
服務(wù)尋址
服務(wù)尋址可以使用 Call ID 映射。在本地調(diào)用中,函數(shù)體是直接通過函數(shù)指針來指定的,但是在遠(yuǎn)程調(diào)用中,函數(shù)指針是不行的,因?yàn)閮蓚€進(jìn)程的地址空間是完全不一樣的。
所以在 RPC 中,所有的函數(shù)都必須有自己的一個 ID。這個 ID 在所有進(jìn)程中都是唯一確定的。
客戶端在做遠(yuǎn)程過程調(diào)用時(shí),必須附上這個 ID。然后我們還需要在客戶端和服務(wù)端分別維護(hù)一個函數(shù)和Call ID的對應(yīng)表。
當(dāng)客戶端需要進(jìn)行遠(yuǎn)程調(diào)用時(shí),它就查一下這個表,找出相應(yīng)的 Call ID,然后把它傳給服務(wù)端,服務(wù)端也通過查表,來確定客戶端需要調(diào)用的函數(shù),然后執(zhí)行相應(yīng)函數(shù)的代碼。
實(shí)現(xiàn)方式:服務(wù)注冊中心。
要調(diào)用服務(wù),首先你需要一個服務(wù)注冊中心去查詢對方服務(wù)都有哪些實(shí)例。Dubbo 的服務(wù)注冊中心是可以配置的,官方推薦使用 Zookeeper。
實(shí)現(xiàn)案例:RMI(Remote Method Invocation,遠(yuǎn)程方法調(diào)用)也就是 RPC 本身的實(shí)現(xiàn)方式。
Registry(服務(wù)發(fā)現(xiàn)):借助 JNDI 發(fā)布并調(diào)用了 RMI 服務(wù)。實(shí)際上,JNDI 就是一個注冊表,服務(wù)端將服務(wù)對象放入到注冊表中,客戶端從注冊表中獲取服務(wù)對象。
RMI 服務(wù)在服務(wù)端實(shí)現(xiàn)之后需要注冊到 RMI Server 上,然后客戶端從指定的 RMI 地址上 Lookup 服務(wù),調(diào)用該服務(wù)對應(yīng)的方法即可完成遠(yuǎn)程方法調(diào)用。
Registry 是個很重要的功能,當(dāng)服務(wù)端開發(fā)完服務(wù)之后,要對外暴露,如果沒有服務(wù)注冊,則客戶端是無從調(diào)用的,即使服務(wù)端的服務(wù)就在那里。
序列化和反序列化
客戶端怎么把參數(shù)值傳給遠(yuǎn)程的函數(shù)呢?在本地調(diào)用中,我們只需要把參數(shù)壓到棧里,然后讓函數(shù)自己去棧里讀就行。
但是在遠(yuǎn)程過程調(diào)用時(shí),客戶端跟服務(wù)端是不同的進(jìn)程,不能通過內(nèi)存來傳遞參數(shù)。
這時(shí)候就需要客戶端把參數(shù)先轉(zhuǎn)成一個字節(jié)流,傳給服務(wù)端后,再把字節(jié)流轉(zhuǎn)成自己能讀取的格式。
只有二進(jìn)制數(shù)據(jù)才能在網(wǎng)絡(luò)中傳輸,序列化和反序列化的定義是:
將對象轉(zhuǎn)換成二進(jìn)制流的過程叫做序列化
將二進(jìn)制流轉(zhuǎn)換成對象的過程叫做反序列化
這個過程叫序列化和反序列化。同理,從服務(wù)端返回的值也需要序列化反序列化的過程。
網(wǎng)絡(luò)傳輸
網(wǎng)絡(luò)傳輸:遠(yuǎn)程調(diào)用往往用在網(wǎng)絡(luò)上,客戶端和服務(wù)端是通過網(wǎng)絡(luò)連接的。
所有的數(shù)據(jù)都需要通過網(wǎng)絡(luò)傳輸,因此就需要有一個網(wǎng)絡(luò)傳輸層。網(wǎng)絡(luò)傳輸層需要把 Call ID 和序列化后的參數(shù)字節(jié)流傳給服務(wù)端,然后再把序列化后的調(diào)用結(jié)果傳回客戶端。
只要能完成這兩者的,都可以作為傳輸層使用。因此,它所使用的協(xié)議其實(shí)是不限的,能完成傳輸就行。
盡管大部分 RPC 框架都使用 TCP 協(xié)議,但其實(shí) UDP 也可以,而 gRPC 干脆就用了 HTTP2。
TCP 的連接是最常見的,簡要分析基于 TCP 的連接:通常 TCP 連接可以是按需連接(需要調(diào)用的時(shí)候就先建立連接,調(diào)用結(jié)束后就立馬斷掉),也可以是長連接(客戶端和服務(wù)器建立起連接之后保持長期持有,不管此時(shí)有無數(shù)據(jù)包的發(fā)送,可以配合心跳檢測機(jī)制定期檢測建立的連接是否存活有效),多個遠(yuǎn)程過程調(diào)用共享同一個連接。
所以,要實(shí)現(xiàn)一個 RPC 框架,只需要把以下三點(diǎn)實(shí)現(xiàn)了就基本完成了:
Call ID 映射:可以直接使用函數(shù)字符串,也可以使用整數(shù) ID。映射表一般就是一個哈希表。
序列化反序列化:可以自己寫,也可以使用 Protobuf 或者 FlatBuffers 之類的。
網(wǎng)絡(luò)傳輸庫:可以自己寫 Socket,或者用 Asio,ZeroMQ,Netty 之類。
RPC 核心之網(wǎng)絡(luò)傳輸協(xié)議
在第三節(jié)中說明了要實(shí)現(xiàn)一個 RPC,需要選擇網(wǎng)絡(luò)傳輸?shù)姆绞健?/p>
在 RPC 中可選的網(wǎng)絡(luò)傳輸方式有多種,可以選擇 TCP 協(xié)議、UDP 協(xié)議、HTTP 協(xié)議。
每一種協(xié)議對整體的性能和效率都有不同的影響,如何選擇一個正確的網(wǎng)絡(luò)傳輸協(xié)議呢?首先要搞明白各種傳輸協(xié)議在 RPC 中的工作方式。
基于 TCP 協(xié)議的 RPC 調(diào)用
由服務(wù)的調(diào)用方與服務(wù)的提供方建立 Socket 連接,并由服務(wù)的調(diào)用方通過 Socket 將需要調(diào)用的接口名稱、方法名稱和參數(shù)序列化后傳遞給服務(wù)的提供方,服務(wù)的提供方反序列化后再利用反射調(diào)用相關(guān)的方法。
最后將結(jié)果返回給服務(wù)的調(diào)用方,整個基于 TCP 協(xié)議的 RPC 調(diào)用大致如此。
但是在實(shí)例應(yīng)用中則會進(jìn)行一系列的封裝,如 RMI 便是在 TCP 協(xié)議上傳遞可序列化的 Java 對象。
基于 HTTP 協(xié)議的 RPC 調(diào)用
該方法更像是訪問網(wǎng)頁一樣,只是它的返回結(jié)果更加單一簡單。
其大致流程為:由服務(wù)的調(diào)用者向服務(wù)的提供者發(fā)送請求,這種請求的方式可能是 GET、POST、PUT、DELETE 等中的一種,服務(wù)的提供者可能會根據(jù)不同的請求方式做出不同的處理,或者某個方法只允許某種請求方式。
而調(diào)用的具體方法則是根據(jù) URL 進(jìn)行方法調(diào)用,而方法所需要的參數(shù)可能是對服務(wù)調(diào)用方傳輸過去的 XML 數(shù)據(jù)或者 JSON 數(shù)據(jù)解析后的結(jié)果,最后返回 JOSN 或者 XML 的數(shù)據(jù)結(jié)果。
由于目前有很多開源的 Web 服務(wù)器,如 Tomcat,所以其實(shí)現(xiàn)起來更加容易,就像做 Web 項(xiàng)目一樣。
兩種方式對比
基于 TCP 的協(xié)議實(shí)現(xiàn)的 RPC 調(diào)用,由于 TCP 協(xié)議處于協(xié)議棧的下層,能夠更加靈活地對協(xié)議字段進(jìn)行定制,減少網(wǎng)絡(luò)開銷,提高性能,實(shí)現(xiàn)更大的吞吐量和并發(fā)數(shù)。
基于 HTTP 協(xié)議實(shí)現(xiàn)的 RPC 則可以使用 JSON 和 XML 格式的請求或響應(yīng)數(shù)據(jù)。
使用 RabbitMQ 的好處:
同步變異步:可以使用線程池將同步變成異步,但是缺點(diǎn)是要自己實(shí)現(xiàn)線程池,并且強(qiáng)耦合。使用消息隊(duì)列可以輕松將同步請求變成異步請求。
低內(nèi)聚高耦合:解耦,減少強(qiáng)依賴。
流量削峰:通過消息隊(duì)列設(shè)置請求最大值,超過閥值的拋棄或者轉(zhuǎn)到錯誤界面。
網(wǎng)絡(luò)通信性能提高:TCP 的創(chuàng)建和銷毀開銷大,創(chuàng)建 3 次握手,銷毀 4 次分手,高峰時(shí)成千上萬條的鏈接會造成資源的巨大浪費(fèi),而且操作系統(tǒng)每秒處理 TCP 的數(shù)量也是有數(shù)量限制的,必定造成性能瓶頸。
RabbitMQ 采用信道通信,不采用 TCP 直接通信。一條線程一條信道,多條線程多條信道,公用一個 TCP 連接。
一條 TCP 連接可以容納無限條信道(硬盤容量足夠的話),不會造成性能瓶頸。