1.RPC簡(jiǎn)介
RPC(Remote Process Call), 即遠(yuǎn)程過(guò)程調(diào)用仍律, 是一個(gè)分布式系統(tǒng)間通信的必備技術(shù)。分布式系統(tǒng)的通信一般都會(huì)采用四層的TCP協(xié)議或七層的Http協(xié)議星著。
Http協(xié)議以其中的Restful規(guī)范為代表特笋, 可讀性好,且得到防火墻的支持拢切、跨語(yǔ)言的支持蒂萎。 缺點(diǎn)是Http協(xié)議有用信息占比太少, 效率低失球, 包含了大量的Http頭信息岖是。
RPC最核心要解決的問(wèn)題, 就是在分布式系統(tǒng)之間如何調(diào)用另外一個(gè)空間的方法实苞, 就仿佛本地調(diào)用一樣豺撑。
- 屏蔽遠(yuǎn)程調(diào)用和本地調(diào)用的區(qū)別, 讓我們感覺(jué)就像是調(diào)用本地的方法黔牵。
- 屏蔽網(wǎng)絡(luò)通信的復(fù)雜性聪轿, 更加專注于業(yè)務(wù)層。
一個(gè)典型的RPC的使用場(chǎng)景猾浦, 包含了服務(wù)發(fā)現(xiàn)陆错、負(fù)載均衡、容錯(cuò)金赦、限流音瓷、網(wǎng)絡(luò)傳輸、序列化等組件夹抗, 其中RPC協(xié)議就指明了如何進(jìn)行網(wǎng)絡(luò)傳輸和序列化绳慎。
2、RPC核心功能
RPC的核心功能是指實(shí)現(xiàn)一個(gè)RPC最重要的部分,就是上圖的RPC協(xié)議部分杏愤。
要讓網(wǎng)絡(luò)通信對(duì)使用者透明靡砌, 就需要對(duì)網(wǎng)絡(luò)通信細(xì)節(jié)進(jìn)行封裝, 一個(gè)RPC通信的流程如下:
具體可參考這里:RPC入門(mén)
RPC調(diào)用的核心的組成部分:
- Client: 負(fù)責(zé)發(fā)起請(qǐng)求調(diào)用珊楼。
- Client stub: 存放服務(wù)端地址信息通殃,負(fù)責(zé)將請(qǐng)求內(nèi)容進(jìn)行編碼打包成網(wǎng)絡(luò)消息,再通過(guò)網(wǎng)絡(luò)傳輸發(fā)送給服務(wù)端厕宗。
- Server stub: 接收客戶端發(fā)送來(lái)的消息并進(jìn)行解碼画舌,然后再調(diào)用本地服務(wù)處理。
- Sever: 服務(wù)的真正提供者媳瞪。
- Network Service: 底層傳輸骗炉, 可以是TCP或者HTTP。
2.1蛇受、 RPC中的通信流程
RPC分為服務(wù)提供方和服務(wù)調(diào)用方句葵。 服務(wù)方和調(diào)用方之間需要通過(guò)傳遞消息來(lái)進(jìn)行通信:
1)服務(wù)調(diào)用方都先需要把請(qǐng)求轉(zhuǎn)發(fā)給本地代理;
2)本地代理將請(qǐng)求數(shù)據(jù)進(jìn)行序列化轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)并按照RPC協(xié)議進(jìn)行編碼并通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)提供方兢仰;
3)服務(wù)提供方接收到網(wǎng)絡(luò)上傳輸?shù)牡腞PC請(qǐng)求后先按照RPC協(xié)議進(jìn)行解碼乍丈;
4)服務(wù)提供方將解碼后的二進(jìn)制數(shù)據(jù)進(jìn)行反序列化并調(diào)用本地服務(wù)實(shí)現(xiàn)方去執(zhí)行;
5)服務(wù)方將執(zhí)行結(jié)果序列化并按照RPC協(xié)議編碼發(fā)送給服務(wù)調(diào)用方把将。
6)服務(wù)調(diào)用方收到請(qǐng)求進(jìn)行解碼和反序列化就可以得到遠(yuǎn)程執(zhí)行結(jié)果轻专。
2.2、RPC中的網(wǎng)絡(luò)傳輸
遠(yuǎn)程調(diào)用往往發(fā)生在網(wǎng)絡(luò)上察蹲, 客戶端和服務(wù)端通過(guò)網(wǎng)絡(luò)連接的请垛。消息數(shù)據(jù)結(jié)構(gòu)被序列化為二進(jìn)制后,就需要進(jìn)行網(wǎng)絡(luò)通信了洽议。所有的數(shù)據(jù)都需要通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸宗收, 因此需要一個(gè)網(wǎng)絡(luò)傳輸層。
在RPC可選的網(wǎng)絡(luò)傳輸中有多種方式亚兄, 比如TCP混稽、UDP、HTTP協(xié)議等审胚。
每種協(xié)議都有自己的優(yōu)缺點(diǎn)匈勋, 比較流行的幾種開(kāi)源RPC框架使用的協(xié)議也各有不同, 如Dubbo膳叨、Thrift等通過(guò)傳輸層TCP協(xié)議來(lái)完成網(wǎng)絡(luò)傳輸洽洁, 而gRPC則通過(guò)應(yīng)用層HTTP 2.0協(xié)議進(jìn)行網(wǎng)絡(luò)傳輸。
-
基于TCP協(xié)議的RPC調(diào)用
由服務(wù)的服務(wù)方和服務(wù)的調(diào)用方建立tcp連接菲嘴,由服務(wù)的調(diào)用方將請(qǐng)求內(nèi)容序列化后并按照RPC協(xié)議進(jìn)行編碼(本質(zhì)上就是基于tcp私有協(xié)議報(bào)文的組裝)通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)提供方诡挂, 服務(wù)提供方調(diào)用本地服務(wù)獲得結(jié)果后再通過(guò)同樣的方式把結(jié)果發(fā)送給服務(wù)調(diào)用方碎浇。 -
基于HTTP協(xié)議的RPC調(diào)用
由服務(wù)的調(diào)用方向服務(wù)的提供者發(fā)送HTTP請(qǐng)求, 這種請(qǐng)求可以是HTTP方法中的一種璃俗, 如GET、POST等悉默。服務(wù)的提供者可以通過(guò)不同的調(diào)用方式做出不同的處理城豁, 或者某個(gè)方法只允許一種某種請(qǐng)求方式。(這樣說(shuō)抄课, web也是RPC的一種形式嗎唱星?)
基于TCP協(xié)議和基于HTTP協(xié)議的RPC服務(wù)的區(qū)別:
基于TCP協(xié)議的RPC調(diào)用
優(yōu)點(diǎn):由于TCP協(xié)議處于OSI七層模型的下層, 能夠更加靈活的對(duì)協(xié)議字段進(jìn)行定制跟磨、壓縮间聊、減少網(wǎng)絡(luò)開(kāi)銷等, 這樣可以提高性能抵拘,實(shí)現(xiàn)更大的吞吐量和并發(fā)數(shù)哎榴。
缺點(diǎn):需要更多關(guān)注底層的細(xì)節(jié)、實(shí)現(xiàn)的代價(jià)更高僵蛛、適應(yīng)不同的平臺(tái)尚蝌, 造成工作量大、難以快速響應(yīng)用戶需求充尉、使用成本高等特點(diǎn)飘言; 同時(shí)由于采用私有協(xié)議, 對(duì)于生態(tài)的推廣也存在較大的問(wèn)題驼侠。基于HTTP協(xié)議的RPC調(diào)用
缺點(diǎn):
1)由于HTTP協(xié)議為OSI七層模型的上層姿鸿, 需要層層封裝報(bào)文, 所以HTTP協(xié)議所占用的字節(jié)數(shù)會(huì)比使用TCP協(xié)議所占用的字節(jié)數(shù)要多倒源, 同等情況下苛预, 傳輸效率低;
2) 其次HTTP1.0和HTTP1.1是文本協(xié)議相速, 而TCP協(xié)議是二進(jìn)制協(xié)議碟渺,文本協(xié)議的開(kāi)銷更大, 占用字節(jié)數(shù)更多突诬,也會(huì)造成同樣的問(wèn)題苫拍, 不過(guò)gRPC使用HTTP2.0協(xié)議,本身就是二進(jìn)制協(xié)議旺隙。
3)HTTP協(xié)議報(bào)文頭中無(wú)用的內(nèi)容太多绒极, 導(dǎo)致有效信息占比低, 傳輸效率低下蔬捷。
優(yōu)點(diǎn):協(xié)議使用范圍廣垄提, 上手簡(jiǎn)單榔袋, 推廣性強(qiáng)。
2.3铡俐、 RPC中的網(wǎng)絡(luò)IO選擇
RPC網(wǎng)絡(luò)通信過(guò)程中凰兑, 服務(wù)提供方和服務(wù)調(diào)用方需要建立連接,然而直接使用底層的socket成本比較高审丘,IO阻塞且無(wú)法復(fù)用吏够。這里就涉及到多種IO模型, 如阻塞IO滩报、非阻塞IO锅知、信號(hào)驅(qū)動(dòng)IO、IO多路復(fù)用脓钾、異步IO等售睹。
IO多路復(fù)用更加適合高并發(fā)的場(chǎng)景, 可以用較少的線程處理較多的IO請(qǐng)求可训。 RPC調(diào)用在大多數(shù)的情況下都是一個(gè)高并發(fā)的場(chǎng)景昌妹, 考慮到系統(tǒng)內(nèi)核的支持、編程語(yǔ)言的支持沉噩、IO模型的特點(diǎn)等捺宗, 一般都會(huì)選擇IO多路復(fù)用作為RPC通信的IO模型。
基于Java語(yǔ)言的RPC框架IO首選Netty, JDK自帶的NIO框架支持不太友好川蒙, Netty是基于Reactor 模式實(shí)現(xiàn)的NIO框架, 支持完全零拷貝蚜厉,對(duì)于RPC框架整體性能的提升作用非常大。
2.4畜眨、 RPC中的網(wǎng)絡(luò)協(xié)議
從RPC的核心流程中可以看到昼牛, RPC的通信過(guò)程中需要對(duì)通信內(nèi)容按照RPC協(xié)議編碼解碼。在第2.2小節(jié)中我們講到康聂, RPC的網(wǎng)絡(luò)通信會(huì)使用諸如TCP贰健、UDP、HTTP等協(xié)議恬汁, 但是這些協(xié)議更多的是通信的傳輸部分伶椿,對(duì)上層應(yīng)用是透明的, 在使用RPC過(guò)程中并不需要太多關(guān)注這方面細(xì)節(jié)氓侧。
這里講的RPC協(xié)議是指應(yīng)用層的網(wǎng)絡(luò)協(xié)議脊另。舉個(gè)例子: RPC 在通信過(guò)程中, 二進(jìn)制數(shù)據(jù)通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸约巷, 但是在傳輸過(guò)程中RPC并不會(huì)一下子把所有的二進(jìn)制報(bào)文通過(guò)網(wǎng)絡(luò)都發(fā)送給另一端偎痛,中間可能會(huì)拆成多個(gè)數(shù)據(jù)組, 也可能會(huì)把多個(gè)報(bào)文合并發(fā)送独郎, 這個(gè)中間如何識(shí)別每個(gè)請(qǐng)求報(bào)文的邊界踩麦, 如何保證接收方和發(fā)送方的語(yǔ)義一致枚赡,就需要定義協(xié)議來(lái)規(guī)定報(bào)文的細(xì)節(jié)。
RPC協(xié)議需要規(guī)定協(xié)議長(zhǎng)度谓谦、協(xié)議標(biāo)示贫橙、消息ID、消息類型等內(nèi)容反粥,因此我們可以將協(xié)議定義為如下這種格式:
魔術(shù)位代表是什么協(xié)議料皇,如REDIS、JDBC等等星压。
設(shè)計(jì)RPC協(xié)議需要考慮到可擴(kuò)展、向下兼容鬼譬, 并且盡可能的減少資源損耗娜膘。 協(xié)議好比定義符號(hào)、規(guī)則优质, 需要兼具功能性和可擴(kuò)展性竣贪, 這樣才能更好的提供服務(wù)。
2.5巩螃、RPC中的序列化和反序列化
序列化可以簡(jiǎn)單理解為對(duì)象->二進(jìn)制數(shù)據(jù)演怎, 反序列化則是相反的過(guò)程。
在本地調(diào)用中避乏,我們只需要把參數(shù)壓入到棧中爷耀, 讓棧去調(diào)用即可。但是在遠(yuǎn)程調(diào)用中拍皮, 客戶端和服務(wù)端在不同的進(jìn)程中歹叮, 不能通過(guò)內(nèi)存來(lái)傳遞參數(shù), 甚至跨不同的語(yǔ)言铆帽。這個(gè)時(shí)候就需要客戶端先把請(qǐng)求序列化為二進(jìn)制數(shù)據(jù)咆耿, 服務(wù)端讀取二進(jìn)制數(shù)據(jù)在轉(zhuǎn)化為能讀取的格式。
常用的序列化方式:
- Java原生的序列化: 僅支持java語(yǔ)言爹橱、性能較差萨螺。
- JSON: 天生跨語(yǔ)言、擴(kuò)展性強(qiáng)愧驱, 但是額外空間開(kāi)銷比較大慰技。
- Hession: 動(dòng)態(tài)類型、二進(jìn)制且緊湊冯键, 可跨語(yǔ)言移植惹盼。
- Protobuf: 需要定義IDL, 序列化后體積更小惫确, 序列化速度快且擴(kuò)展性強(qiáng)手报, 支持跨語(yǔ)言等特性蚯舱。
除過(guò)以上幾種, 還有諸如Thrift掩蛤、Avro等很多序列化框架枉昏。每種序列化框架都有自己的優(yōu)點(diǎn)和缺點(diǎn), 它們?cè)谠O(shè)計(jì)之初有自己獨(dú)特的應(yīng)用場(chǎng)景揍鸟。
從RPC的角度來(lái)講兄裂, 主要看以下幾點(diǎn):
- 通用性: 是否支持Map等復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
- 性能: 包括時(shí)間復(fù)雜度和空間復(fù)雜度阳藻, 由于RPC框架將會(huì)被公司所有服務(wù)使用晰奖, 如果序列化上節(jié)約一點(diǎn)時(shí)間, 那整個(gè)公司的收益將非承饶啵可觀匾南。
- 可擴(kuò)展性:如果序列化協(xié)議有良好的可擴(kuò)展性、支持自動(dòng)增加新的字段蛔外、而不影響老的服務(wù)蛆楞,將大大提高系統(tǒng)的靈活度。
2.6夹厌、 RPC中的代理
RPC中的代理負(fù)責(zé)遠(yuǎn)程接口的代理實(shí)現(xiàn)豹爹,代理發(fā)生在客服端、服務(wù)端矛纹, RPC框架需要解決:像本地調(diào)用一樣調(diào)用遠(yuǎn)程的接口臂聋。于是如何組裝數(shù)據(jù)報(bào)文, 經(jīng)過(guò)網(wǎng)絡(luò)傳輸報(bào)文崖技,屏蔽遠(yuǎn)程接口的細(xì)節(jié)逻住, 便是動(dòng)態(tài)代理需要做的工作。
RPC會(huì)自動(dòng)給接口生成一個(gè)代理類迎献, 當(dāng)我們?cè)陧?xiàng)目中注入初始化接口的時(shí)候瞎访, 運(yùn)行過(guò)程中實(shí)際綁定的是這個(gè)接口的代理類, 在接口方法中被調(diào)用的時(shí)候吁恍, 它實(shí)際上是被生成代理類攔截到了扒秸, 這樣就可以在生成的代理類中, 添加一些其他的執(zhí)行邏輯冀瓦。
為什么要加入動(dòng)態(tài)代理伴奥?
如果沒(méi)有動(dòng)態(tài)代理, 服務(wù)端大量的接口將不便于管理翼闽, 需要大量的if判斷拾徙, 如果擴(kuò)展了新的接口, 需要更改調(diào)用蘿莉感局, 不利于擴(kuò)展維護(hù)尼啡。
動(dòng)態(tài)代理可以做到內(nèi)部方法級(jí)攔截暂衡, 并且添加其他額外功能, 必須負(fù)載管理崖瞭,鑒權(quán)狂巢,日志埋點(diǎn)等。
動(dòng)態(tài)代理調(diào)用過(guò)程
Java中動(dòng)態(tài)代理的實(shí)現(xiàn)
- jdk動(dòng)態(tài)代理
- cglib 動(dòng)態(tài)代理
- javassist 動(dòng)態(tài)代理
- asm 字節(jié)碼
不同的動(dòng)態(tài)代理實(shí)現(xiàn)各有千秋书聚, 其中cglib底層依賴于asm字節(jié)碼唧领, javassist自成一派。 由于cglib和javassist需要直接操作字節(jié)碼雌续, 使用門(mén)檻比較高斩个,但實(shí)際上它們的應(yīng)用非常廣泛, Spring就使用了jdk動(dòng)態(tài)代理和cglib驯杜。
RPC框架無(wú)論使用何種動(dòng)態(tài)代理技術(shù)萨驶, 所需要完成的任務(wù)也是固定的, 不外乎:組裝報(bào)文艇肴、網(wǎng)絡(luò)尋址、網(wǎng)絡(luò)傳輸叁温、序列化再悼、反序列化、返回結(jié)果膝但、埋點(diǎn)監(jiān)控等等冲九。
3、RPC的架構(gòu)設(shè)計(jì)
RPC的整體架構(gòu)設(shè)計(jì)如下所示跟束, 除過(guò)RPC核心功能里面包括的序列化莺奸、協(xié)議編碼、網(wǎng)絡(luò)傳輸?shù)炔糠滞猓?還包含服務(wù)發(fā)布中的注冊(cè)中心冀宴、服務(wù)發(fā)現(xiàn)灭贷、負(fù)載均衡、配置管理等等:
3.1略贮、RPC中的服務(wù)發(fā)現(xiàn)
成熟的RPC框架中甚疟, 布置客戶端和服務(wù)端2個(gè)角色,