什么是PRC及RPC底层实现(1)
RPC(Remote Procedure Call Protocol)即遠(yuǎn)程過程調(diào)用,
允許一臺計(jì)算機(jī)調(diào)用另一臺計(jì)算機(jī)上的程序得到結(jié)果,
它是一種通過網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請求服務(wù)而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議簡言之RPC使得程序能夠像訪問本地系統(tǒng)資源一樣,
而代碼中不需要做額外的編程,就像在本地調(diào)用一樣,去訪問遠(yuǎn)端系統(tǒng)資源。
比較關(guān)鍵的一些方面包括:通訊協(xié)議、序列化、資源(接口)描述、服務(wù)框架、性能、語言支持等,注冊中心一般為ZooKeeper或者單機(jī)直連等
現(xiàn)在互聯(lián)網(wǎng)應(yīng)用的量級越來越大,單臺計(jì)算機(jī)的能力有限,需要借助可擴(kuò)展的計(jì)算機(jī)集群來完成,分布式的應(yīng)用可以借助RPC來完成機(jī)器之間的調(diào)用。
RPC不是一門技術(shù),RPC只是一個(gè)概念
直觀理解的RPC
1:屏蔽網(wǎng)絡(luò)編程細(xì)節(jié),實(shí)現(xiàn)調(diào)用遠(yuǎn)程方法就跟調(diào)用本地一樣的體驗(yàn)。
2:不需要因?yàn)檫@個(gè)方法是遠(yuǎn)程調(diào)用就需要編寫很多與業(yè)務(wù)無關(guān)的代碼。
這就好比建在小河上的橋一樣連接著河的兩岸,如果沒有小橋,
我們需要通過劃船、繞道等其他方式才能到達(dá)對面,但是有了小橋之后,
我們就能像在路面上一樣行走到達(dá)對面,并且跟在路面上行走的體驗(yàn)沒有區(qū)別
3:屏蔽遠(yuǎn)程調(diào)用跟本地調(diào)用的區(qū)別, 隱藏底層網(wǎng)絡(luò)通信的復(fù)雜性, 專注業(yè)務(wù)邏。
具體調(diào)用過程:
1、服務(wù)消費(fèi)者(client客戶端)通過調(diào)用本地服務(wù)的方式調(diào)用需要消費(fèi)的服務(wù);
2、客戶端存根(client stub)接收到調(diào)用請求后負(fù)責(zé)將方法、入?yún)⒌刃畔⑿蛄谢ńM裝)成能夠進(jìn)行網(wǎng)絡(luò)傳輸?shù)南Ⅲw;
3、客戶端存根(client stub)找到遠(yuǎn)程的服務(wù)地址,并且將消息通過網(wǎng)絡(luò)發(fā)送給服務(wù)端;
4、服務(wù)端存根(server stub)收到消息后進(jìn)行解碼(反序列化操作);
5、服務(wù)端存根(server stub)根據(jù)解碼結(jié)果調(diào)用本地的服務(wù)進(jìn)行相關(guān)處理;
6、本地服務(wù)執(zhí)行具體業(yè)務(wù)邏輯并將處理結(jié)果返回給服務(wù)端存根(server stub);
7、服務(wù)端存根(server stub)將返回結(jié)果重新打包成消息(序列化)并通過網(wǎng)絡(luò)發(fā)送至消費(fèi)方;
8、客戶端存根(client stub)接收到消息,并進(jìn)行解碼(反序列化);
9、服務(wù)消費(fèi)方得到最終結(jié)果;
而RPC框架的實(shí)現(xiàn)目標(biāo)則是將上面的第2-10步完好地封裝起來,也就是把調(diào)用、編碼/解碼的過程給封裝起來,讓用戶感覺上像調(diào)用本地服務(wù)一樣的調(diào)用遠(yuǎn)程服務(wù)
對RPC通信流程解
協(xié)議格式:明確數(shù)據(jù)內(nèi)容格式(json,xml,javabean,其他內(nèi)容格式)。
如:把數(shù)據(jù)格式的約定內(nèi)容叫做xml協(xié)議。大多數(shù)的協(xié)議會分成兩部分,分別是數(shù)據(jù)頭和消息體。 數(shù)據(jù)頭一般用于身份識別,包括協(xié)議標(biāo)識、數(shù)據(jù)大小、請求類型、序列化類型等信息;消息體主要是請求的業(yè)務(wù)參數(shù)信息和擴(kuò)展屬性等。
序列化: 網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)必須是二進(jìn)制數(shù)據(jù),但調(diào)用方請求的出入?yún)?shù)都是對象。
對象是肯定沒法直接在網(wǎng)絡(luò)中傳輸?shù)? 需要提前把它轉(zhuǎn)成可傳輸?shù)亩M(jìn)制. 并且要求轉(zhuǎn)換算法是可逆的。 調(diào)用方持續(xù)地把請求參數(shù)序列化成二進(jìn)制后,經(jīng)過TCP傳輸給了服務(wù)提供方。服務(wù)提供方從TCP 通道里面收到二進(jìn)制數(shù)據(jù),那如何知道一個(gè)請求的數(shù)據(jù)到哪里結(jié)束,是一個(gè)什么么類型的請求呢?
通信協(xié)議:TCP/HTTP/MQ/其他.均建立在TCP之上
反序列化: 根據(jù)協(xié)議格式,服務(wù)提供方就可以正確地從二進(jìn)制數(shù)據(jù)中分割出不同的請求來,同時(shí)根據(jù)請求類型 和序列化類型,把二進(jìn)制的消息體逆向還原成請求對象。這個(gè)過程叫作"反序列化。
服務(wù)返回: 服務(wù)提供方再根據(jù)反序列化出來的請求對象找到對應(yīng)的實(shí)現(xiàn)類,完成真正的方法調(diào)用,然后把執(zhí)行結(jié)果序列化后,回寫到對應(yīng)的TCP誦道里面。調(diào)用方獲取到應(yīng)答的數(shù)據(jù)包后,冉反序列化成應(yīng)答對象,這樣調(diào)用方就完成了一次RPC調(diào)用。
AOP代理:
通過springAOP動態(tài)代理的技術(shù),對方法進(jìn)行攔截增強(qiáng),以便于增加需要的額外處理邏輯。
由服務(wù)提供者給出業(yè)務(wù)接口聲明,在調(diào)用方的程序里面,RPC框架根據(jù)調(diào)用的服務(wù)接口提前生成動
態(tài)代理實(shí)現(xiàn)類,并通過依賴注入等技術(shù)注入到聲明了該接口的相關(guān)業(yè)務(wù)邏輯里面。該代理實(shí)現(xiàn)類會
攔截所有的方法調(diào)用,在提供的方法處理邏輯里面完成一整套的遠(yuǎn)程調(diào)用,并把遠(yuǎn)程調(diào)用結(jié)果返回
給調(diào)用方,這樣調(diào)用方在調(diào)用遠(yuǎn)程方法的時(shí)候就獲得了像調(diào)用本地接口一樣的體驗(yàn)。
RPC為什么要序列化?
例子幫助理解:
比如發(fā)快遞:
我們要發(fā)一個(gè)需要自行組裝的物件,發(fā)件人發(fā)之前,會把物件拆開裝箱,這就好比序列化;
這時(shí)候快遞員來了,不能磕碰呀,那就要打包,這就好比將序列化后的數(shù)據(jù)進(jìn)行編碼,封裝成一個(gè)固定格式的協(xié)議;
過了兩天,收件人收到包裹了,就會拆箱將物件拼接好,這就好比是協(xié)議解碼和反序列化。
因?yàn)榫W(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)必須是二進(jìn)制數(shù)據(jù),
所以在調(diào)用中,對入?yún)ο笈c返回值對象進(jìn)行序列化與反序列化是一個(gè)必須的過程。
序列化:
當(dāng)A機(jī)器上的應(yīng)用發(fā)起一個(gè)RPC調(diào)用時(shí),調(diào)用方法和其入?yún)⒌刃畔⑿枰ㄟ^底層的網(wǎng)絡(luò)協(xié)議如TCP傳輸?shù)紹機(jī)器,由于網(wǎng)絡(luò)協(xié)議是基于二進(jìn)制的,所有傳輸?shù)膮?shù)數(shù)據(jù)都需要先進(jìn)行序列化(Serialize)或者編組(marshal)成二進(jìn)制的形式才能在網(wǎng)絡(luò)中進(jìn)行傳輸。然后通過尋址操作和網(wǎng)絡(luò)傳輸將序列化或者編組之后的二進(jìn)制數(shù)據(jù)發(fā)送給B機(jī)器。
反序列化:
當(dāng)B機(jī)器接收到A機(jī)器的應(yīng)用發(fā)來的請求之后,又需要對接收到的參數(shù)等信息進(jìn)行反序列化操作(序列化的逆操作),
即將二進(jìn)制信息恢復(fù)為內(nèi)存中的表達(dá)方式,然后再找到對應(yīng)的方法(尋址的一部分)進(jìn)行本地調(diào)用(一般是通過生成代理Proxy去調(diào)用,通常會有JDK動態(tài)代理、CGLIB動態(tài)代理、Javassist生成字節(jié)碼技術(shù)等),之后得到調(diào)用的返回值
有哪些常用的序列化方式
1:JDK原生序列化
2:JSON序列化
3:Hessian序列化
4:Protobuf序列化
5:Kero序列化
RPC框架如何選擇序列化?
1:性能和效率(性能越好,序列化及反序列化就越快)
2:空間開銷(體積越小,網(wǎng)絡(luò)傳輸數(shù)據(jù)量就小,傳輸數(shù)據(jù)就越快)
3:協(xié)議的通用性及兼容性(優(yōu)先級最高,直接關(guān)系到服務(wù)的可用率和穩(wěn)定性)
4:協(xié)議的安全性(JDK原生序列化存在漏洞)
RPC框架在使用時(shí)注意的問題
1:對象構(gòu)造過于復(fù)雜:屬性很多,嵌套很多,聚合很多其他對象,依賴過于復(fù)雜,
序列化及反序列化的時(shí)候越浪費(fèi)性能,消耗CPU嚴(yán)重影響整體性能,出現(xiàn)問題概率就越高
2:對象過于龐大:如入?yún)ο鬄榇髄ist或大Map,序列化之后字節(jié)長度達(dá)到上兆字節(jié),嚴(yán)重浪費(fèi)服務(wù)器性能,CPU及帶寬
3:使用序列化框架不支持的類作為入?yún)㈩悾篽essian不支持linkhashmap,linkhashset,使用第三方集合類,盡量選擇原生常用的集合類hashMap,ArrayList
4:對象有復(fù)雜的繼承關(guān)系:大多數(shù)序列化框架在序列化對象的時(shí)候,都會將對象屬性進(jìn)行序列化,當(dāng)有繼承關(guān)系時(shí),會不停地尋找父類遍歷屬性,越復(fù)雜就越浪費(fèi)性能,出錯(cuò)概率就越高
總結(jié):
1:對象要盡量簡單,沒有太多依賴關(guān)系,屬性不要太多,盡量高聚合
2:入?yún)ο笈c返回值對象不要太大,更不要傳太大的集合
3:盡量使用簡單的常用的開發(fā)語言原生的對象,尤其是集合類
4:對象不要有復(fù)雜的繼承關(guān)系,最好不要有父子類的情況
網(wǎng)絡(luò)通信模型的選擇
RPC是解決進(jìn)程間通信的一種方式。
一次RPC調(diào)用,本質(zhì)就是服務(wù)消費(fèi)者與服務(wù)提供者間的一次網(wǎng)絡(luò)信息交換的過程。
服務(wù)調(diào)用者通過網(wǎng)絡(luò)發(fā)送一條請求消息,服務(wù)提供者接收并解析,處理完相關(guān)的業(yè)務(wù)邏輯之后,再發(fā)送一條響應(yīng)消息給服務(wù)調(diào)用者,服務(wù)調(diào)用者接收并解析響應(yīng)消息,處理完相關(guān)的響應(yīng)邏輯,一次RPC調(diào)便結(jié)束了,網(wǎng)絡(luò)通信是整個(gè)RPC調(diào)用流程的基礎(chǔ)。
下圖屬于通信方式屬于那種通信IO模型呢?
常見的網(wǎng)絡(luò)IO模型分為四種:
1:同步阻塞IO(BIO)
2:同步非阻塞IO(NIO)
3:IO多路復(fù)用和異步
4:非阻塞IO(AIO)
同步阻塞IO(BIO)
同步阻塞IO是最簡單最常見的IO模型,
默認(rèn)情況下所有的socket都是阻塞的.
操作流程。
1: 應(yīng)用進(jìn)程發(fā)起IO系統(tǒng)調(diào)用后,應(yīng)用進(jìn)程被阻塞,轉(zhuǎn)到內(nèi)核空間處理。
2: 內(nèi)核開始等待數(shù)據(jù),等待到數(shù)據(jù)之后,再將內(nèi)核中的數(shù)據(jù)拷貝到用戶內(nèi)存中,整個(gè)處理完畢后返回進(jìn)程。
3: 應(yīng)用的進(jìn)程解除阻塞狀態(tài),運(yùn)行業(yè)務(wù)邏輯。
這里我們可以看到,系統(tǒng)內(nèi)核處理IO操作分為兩個(gè)階段等待數(shù)據(jù)和拷貝數(shù)據(jù)。
而在這2階段中,應(yīng)用進(jìn)程中IO操作的線程會一直都處于阻塞狀態(tài),
也就是說,內(nèi)核準(zhǔn)備數(shù)據(jù)和數(shù)據(jù)從內(nèi)核拷貝到用戶空間這兩個(gè)過程都是阻塞的
如果是基于java多線程開發(fā),那么每一個(gè)IO操作都要占用線程,直至IO操作結(jié)束。
這個(gè)流程就好比我們?nèi)ゲ蛷d吃飯,我們到達(dá)餐廳,向服務(wù)員點(diǎn)餐,之后要一直在餐廳等待后廚將菜做好,然后服務(wù)員會將菜端給我們,我們才能享用。
優(yōu)點(diǎn):
能夠及時(shí)返回?cái)?shù)據(jù),無延遲;
調(diào)用代碼邏輯簡單;
缺點(diǎn):
等待浪費(fèi)很多時(shí)間,影響程序性能
同步非阻塞IO(NIO)
同步非阻塞IO即在IO系統(tǒng)調(diào)用的過程中,進(jìn)程不必阻塞,
而是采用定時(shí)輪詢(polling)的方式數(shù)據(jù)是否準(zhǔn)備就緒;
在此期間,進(jìn)程可以處理其他的任務(wù)。
1:進(jìn)程發(fā)起read,進(jìn)行recvfrom系統(tǒng)調(diào)用,如果內(nèi)核中的數(shù)據(jù)還沒有準(zhǔn)備好,就立刻返回一個(gè)error;
2:調(diào)用返回后進(jìn)程可以進(jìn)行其他操作,然后再次發(fā)起recvfrom系統(tǒng)調(diào)用,不斷重復(fù);(這個(gè)過程稱為輪詢polling)
3:內(nèi)核中的數(shù)據(jù)準(zhǔn)備好以后,再次收到recvfrom調(diào)用,就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回;
注意:在數(shù)據(jù)從內(nèi)核拷貝到用戶內(nèi)存的過程中,進(jìn)程仍然是屬于阻塞的狀態(tài)
優(yōu)點(diǎn):
能夠在IO操作過程中,處理其他的任務(wù)。
缺點(diǎn):
任務(wù)完成的響應(yīng)延遲增大了,因?yàn)槊窟^一段時(shí)間才去輪詢一次read操作,
而任務(wù)可能在兩次輪詢之間的任意時(shí)間完成,這會導(dǎo)致整體數(shù)據(jù)吞吐量的降低。
IO多路復(fù)用(IO multiplexing)
I/O多路復(fù)用就是通過一種機(jī)制,可以監(jiān)視多個(gè)文件描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。
常見的select, poll, epoll 都是IO多路復(fù)用。
需要注意的是select,poll,epoll本質(zhì)上都是同步I/O,
因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說這個(gè)讀寫過程是阻塞的。
Java底層的NIO,redis,nginx底層IO模型就是IO多路復(fù)用模型
異步IO(asynchronous IO)
異步IO是事件驅(qū)動IO。
用戶進(jìn)程發(fā)起IO操作之后,會立即返回,然后可以處理其他任務(wù)。
內(nèi)核會等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存。
當(dāng)這一切都完成之后,內(nèi)核會給用戶進(jìn)程發(fā)送一個(gè)信號,通知IO操作完成。
在IO兩個(gè)階段,進(jìn)程都是非阻塞的。目前有很多開源的異步IO庫,例如libevent、libev、libuv。
IO多路復(fù)用更適合高并發(fā)的場景,可以使用較少的進(jìn)程處理較多的socket的IO請求,如Netty
阻塞IO每處理一個(gè)socket的IO請求都會阻塞進(jìn)程,并發(fā)量低,業(yè)務(wù)邏輯同步進(jìn)行IO操作,阻塞IO可滿足,開銷比IO多路復(fù)用低
RPC調(diào)用在大多數(shù)的情況下,是高并發(fā)的調(diào)用場景 ,故網(wǎng)絡(luò)通信模型會選擇IO多路復(fù)用的方式,
最優(yōu)的選擇是基于Reactor模式實(shí)現(xiàn)的框架,Java環(huán)境.首選Netty框架
Reactor介紹:https://www.cnblogs.com/winner-0715/p/8733787.html
RPC中的動態(tài)代理
RPC 框架解決的問題是:像調(diào)用本地接口一樣調(diào)用遠(yuǎn)程的接口.
那么可使用動態(tài)代理去解決:
1:如何組裝數(shù)據(jù)報(bào)文
2:經(jīng)過網(wǎng)絡(luò)傳輸發(fā)送至服務(wù)提供方
3:屏蔽遠(yuǎn)程接口調(diào)用的細(xì)節(jié)
不需要改動原有代碼的前提下,實(shí)現(xiàn)非業(yè)務(wù)邏輯跟業(yè)務(wù)邏輯的解耦。
通過對字節(jié)碼進(jìn)行增強(qiáng),在方法調(diào)用的時(shí)候進(jìn)行攔截,以便于在方法調(diào)用前后,增加需要的額外處理邏輯,如屏蔽網(wǎng)絡(luò)通信過程
總結(jié)
以上是生活随笔為你收集整理的什么是PRC及RPC底层实现(1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度地图Marker优化方案
- 下一篇: spm总体说明