RPC框架詳細(xì)內(nèi)容:
http://www.reibang.com/p/193634cca86a
在一個(gè)典型 RPC 的使用場景中烹玉,包含了服務(wù)發(fā)現(xiàn)附较、負(fù)載刃泡、容錯脱盲、網(wǎng)絡(luò)傳輸邑滨、序列化等組件,其中“RPC 協(xié)議”就指明了程序如何進(jìn)行網(wǎng)絡(luò)傳輸和序列化钱反。
RPC 核心功能
RPC 的核心功能是指實(shí)現(xiàn)一個(gè) RPC 最重要的功能模塊,就是上圖中的”RPC 協(xié)議”部分:
一個(gè) RPC 的核心功能主要有 5 個(gè)部分組成匣距,分別是:客戶端面哥、客戶端 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 個(gè)模塊組成协饲,如果想要自己實(shí)現(xiàn)一個(gè) RPC畏腕,最簡單的方式要實(shí)現(xiàn)三個(gè)技術(shù)點(diǎn),分別是:
服務(wù)尋址
數(shù)據(jù)流的序列化和反序列化
網(wǎng)絡(luò)傳輸
服務(wù)尋址
服務(wù)尋址可以使用 Call ID 映射茉稠。在本地調(diào)用中描馅,函數(shù)體是直接通過函數(shù)指針來指定的,但是在遠(yuǎn)程調(diào)用中而线,函數(shù)指針是不行的铭污,因?yàn)閮蓚€(gè)進(jìn)程的地址空間是完全不一樣的。
所以在 RPC 中膀篮,所有的函數(shù)都必須有自己的一個(gè) ID嘹狞。這個(gè) ID 在所有進(jìn)程中都是唯一確定的。
客戶端在做遠(yuǎn)程過程調(diào)用時(shí)誓竿,必須附上這個(gè) ID磅网。然后我們還需要在客戶端和服務(wù)端分別維護(hù)一個(gè)函數(shù)和Call ID的對應(yīng)表。
當(dāng)客戶端需要進(jìn)行遠(yuǎn)程調(diào)用時(shí)筷屡,它就查一下這個(gè)表涧偷,找出相應(yīng)的 Call ID,然后把它傳給服務(wù)端毙死,服務(wù)端也通過查表燎潮,來確定客戶端需要調(diào)用的函數(shù),然后執(zhí)行相應(yīng)函數(shù)的代碼规哲。
實(shí)現(xiàn)方式:服務(wù)注冊中心跟啤。
要調(diào)用服務(wù),首先你需要一個(gè)服務(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 就是一個(gè)注冊表秃症,服務(wù)端將服務(wù)對象放入到注冊表中候址,客戶端從注冊表中獲取服務(wù)對象。
RMI 服務(wù)在服務(wù)端實(shí)現(xiàn)之后需要注冊到 RMI Server 上种柑,然后客戶端從指定的 RMI 地址上 Lookup 服務(wù)岗仑,調(diào)用該服務(wù)對應(yīng)的方法即可完成遠(yuǎn)程方法調(diào)用。
Registry 是個(gè)很重要的功能聚请,當(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)成一個(gè)字節(jié)流返帕,傳給服務(wù)端后,再把字節(jié)流轉(zhuǎn)成自己能讀取的格式篙挽。
只有二進(jìn)制數(shù)據(jù)才能在網(wǎng)絡(luò)中傳輸,序列化和反序列化的定義是:
將對象轉(zhuǎn)換成二進(jìn)制流的過程叫做序列化
將二進(jìn)制流轉(zhuǎn)換成對象的過程叫做反序列化
這個(gè)過程叫序列化和反序列化镊靴。同理铣卡,從服務(wù)端返回的值也需要序列化反序列化的過程。
網(wǎng)絡(luò)傳輸
網(wǎng)絡(luò)傳輸:遠(yuǎn)程調(diào)用往往用在網(wǎng)絡(luò)上偏竟,客戶端和服務(wù)端是通過網(wǎng)絡(luò)連接的煮落。
所有的數(shù)據(jù)都需要通過網(wǎng)絡(luò)傳輸,因此就需要有一個(gè)網(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ī)制定期檢測建立的連接是否存活有效)亥揖,多個(gè)遠(yuǎn)程過程調(diào)用共享同一個(gè)連接珊擂。
所以,要實(shí)現(xiàn)一個(gè) RPC 框架徐块,只需要把以下三點(diǎn)實(shí)現(xiàn)了就基本完成了:
Call ID 映射:可以直接使用函數(shù)字符串未玻,也可以使用整數(shù) ID。映射表一般就是一個(gè)哈希表胡控。
序列化反序列化:可以自己寫扳剿,也可以使用 Protobuf 或者 FlatBuffers 之類的。
網(wǎng)絡(luò)傳輸庫:可以自己寫 Socket昼激,或者用 Asio庇绽,ZeroMQ,Netty 之類橙困。
RPC 核心之網(wǎng)絡(luò)傳輸協(xié)議
在第三節(jié)中說明了要實(shí)現(xiàn)一個(gè) RPC瞧掺,需要選擇網(wǎng)絡(luò)傳輸?shù)姆绞健?/p>
在 RPC 中可選的網(wǎng)絡(luò)傳輸方式有多種,可以選擇 TCP 協(xié)議凡傅、UDP 協(xié)議辟狈、HTTP 協(xié)議。
每一種協(xié)議對整體的性能和效率都有不同的影響夏跷,如何選擇一個(gè)正確的網(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)用方佣蓉,整個(gè)基于 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ù)不同的請求方式做出不同的處理,或者某個(gè)方法只允許某種請求方式柳譬。
而調(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 直接通信俯在。一條線程一條信道,多條線程多條信道娃惯,公用一個(gè) TCP 連接跷乐。
一條 TCP 連接可以容納無限條信道(硬盤容量足夠的話),不會造成性能瓶頸趾浅。