什么是PRC及RPC底層實現(1)


RPC(Remote Procedure Call Protocol)即遠程過程調用
允許一台計算機調用另一台計算機上的程序得到結果,
它是一種通過網絡從遠程計算機程序上請求服務而不需要了解底層網絡技術的協議簡言之RPC使得程序能夠像訪問本地系統資源一樣,
而代碼中不需要做額外的編程,就像在本地調用一樣,去訪問遠端系統資源。
比較關鍵的一些方面包括:通訊協議、序列化、資源(接口)描述、服務框架、性能、語言支持等,注冊中心一般為ZooKeeper或者單機直連等
現在互聯網應用的量級越來越大,單台計算機的能力有限,需要借助可擴展的計算機集群來完成,分布式的應用可以借助RPC來完成機器之間的調用。
RPC不是一門技術,RPC只是一個概念

直觀理解的RPC
1:屏蔽網絡編程細節,實現調用遠程方法就跟調用本地一樣的體驗。
2:不需要因為這個方法是遠程調用就需要編寫很多與業務無關的代碼。
這就好比建在小河上的橋一樣連接着河的兩岸,如果沒有小橋,
我們需要通過划船、繞道等其他方式才能到達對面,但是有了小橋之后,
我們就能像在路面上一樣行走到達對面,並且跟在路面上行走的體驗沒有區別
3:屏蔽遠程調用跟本地調用的區別, 隱藏底層網絡通信的復雜性, 專注業務邏。

 

 

具體調用過程:
1、服務消費者(client客戶端)通過調用本地服務的方式調用需要消費的服務;
2、客戶端存根(client stub)接收到調用請求后負責將方法、入參等信息序列化(組裝)成能夠進行網絡傳輸的消息體;
3、客戶端存根(client stub)找到遠程的服務地址,並且將消息通過網絡發送給服務端;
4、服務端存根(server stub)收到消息后進行解碼(反序列化操作);
5、服務端存根(server stub)根據解碼結果調用本地的服務進行相關處理;
6、本地服務執行具體業務邏輯並將處理結果返回給服務端存根(server stub);
7、服務端存根(server stub)將返回結果重新打包成消息(序列化)並通過網絡發送至消費方;
8、客戶端存根(client stub)接收到消息,並進行解碼(反序列化);
9、服務消費方得到最終結果;
而RPC框架的實現目標則是將上面的第2-10步完好地封裝起來,也就是把調用、編碼/解碼的過程給封裝起來,讓用戶感覺上像調用本地服務一樣的調用遠程服務

 

 對RPC通信流程解

 協議格式:明確數據內容格式(json,xml,javabean,其他內容格式)。 

如:把數據格式的約定內容叫做xml協議。大多數的協議會分成兩部分,分別是數據頭和消息體。 數據頭一般用於身份識別,包括協議標識、數據大小、請求類型、序列化類型等信息;消息體主要是請求的業務參數信息和擴展屬性等。

序列化: 網絡傳輸的數據必須是二進制數據,但調用方請求的出入參數都是對象。
對象是肯定沒法直接在網絡中傳輸的. 需要提前把它轉成可傳輸的二進制. 並且要求轉換算法是可逆的。 調用方持續地把請求參數序列化成二進制后,經過TCP傳輸給了服務提供方。服務提供方從TCP 通道里面收到二進制數據,那如何知道一個請求的數據到哪里結束,是一個什么么類型的請求呢?

通信協議:TCP/HTTP/MQ/其他.均建立在TCP之上

反序列化: 根據協議格式,服務提供方就可以正確地從二進制數據中分割出不同的請求來,同時根據請求類型 和序列化類型,把二進制的消息體逆向還原成請求對象。這個過程叫作"反序列化。
服務返回: 服務提供方再根據反序列化出來的請求對象找到對應的實現類,完成真正的方法調用,然后把執行結果序列化后,回寫到對應的TCP誦道里面。調用方獲取到應答的數據包后,冉反序列化成應答對象,這樣調用方就完成了一次RPC調用。

AOP代理
通過springAOP動態代理的技術,對方法進行攔截增強,以便於增加需要的額外處理邏輯。
由服務提供者給出業務接口聲明,在調用方的程序里面,RPC框架根據調用的服務接口提前生成動
態代理實現類,並通過依賴注入等技術注入到聲明了該接口的相關業務邏輯里面。該代理實現類會
攔截所有的方法調用,在提供的方法處理邏輯里面完成一整套的遠程調用,並把遠程調用結果返回
給調用方,這樣調用方在調用遠程方法的時候就獲得了像調用本地接口一樣的體驗。

 

 

RPC為什么要序列化?

例子幫助理解:

比如發快遞:

我們要發一個需要自行組裝的物件,發件人發之前,會把物件拆開裝箱,這就好比序列化;
這時候快遞員來了,不能磕碰呀,那就要打包,這就好比將序列化后的數據進行編碼,封裝成一個固定格式的協議;
過了兩天,收件人收到包裹了,就會拆箱將物件拼接好,這就好比是協議解碼和反序列化。
因為網絡傳輸的數據必須是二進制數據,

所以在調用中,對入參對象與返回值對象進行序列化與反序列化是一個必須的過程。

序列化:
當A機器上的應用發起一個RPC調用時,調用方法和其入參等信息需要通過底層的網絡協議如TCP傳輸到B機器,由於網絡協議是基於二進制的,所有傳輸的參數數據都需要先進行序列化(Serialize)或者編組(marshal)成二進制的形式才能在網絡中進行傳輸。然后通過尋址操作和網絡傳輸將序列化或者編組之后的二進制數據發送給B機器。

反序列化:
當B機器接收到A機器的應用發來的請求之后,又需要對接收到的參數等信息進行反序列化操作(序列化的逆操作),

即將二進制信息恢復為內存中的表達方式,然后再找到對應的方法(尋址的一部分)進行本地調用(一般是通過生成代理Proxy去調用,通常會有JDK動態代理、CGLIB動態代理、Javassist生成字節碼技術等),之后得到調用的返回值

 有哪些常用的序列化方式

1:JDK原生序列化
2:JSON序列化
3:Hessian序列化
4:Protobuf序列化
5:Kero序列化

RPC框架如何選擇序列化?
1:性能和效率(性能越好,序列化及反序列化就越快)
2:空間開銷(體積越小,網絡傳輸數據量就小,傳輸數據就越快)
3:協議的通用性及兼容性(優先級最高,直接關系到服務的可用率和穩定性)
4:協議的安全性(JDK原生序列化存在漏洞)

 RPC框架在使用時注意的問題

1:對象構造過於復雜:屬性很多,嵌套很多,聚合很多其他對象,依賴過於復雜,
序列化及反序列化的時候越浪費性能,消耗CPU嚴重影響整體性能,出現問題概率就越高
2:對象過於龐大:如入參對象為大list或大Map,序列化之后字節長度達到上兆字節,嚴重浪費服務器性能,CPU及帶寬
3:使用序列化框架不支持的類作為入參類:hessian不支持linkhashmap,linkhashset,使用第三方集合類,盡量選擇原生常用的集合類hashMap,ArrayList

4:對象有復雜的繼承關系:大多數序列化框架在序列化對象的時候,都會將對象屬性進行序列化,當有繼承關系時,會不停地尋找父類遍歷屬性,越復雜就越浪費性能,出錯概率就越高


總結:
1:對象要盡量簡單,沒有太多依賴關系,屬性不要太多,盡量高聚合
2:入參對象與返回值對象不要太大,更不要傳太大的集合
3:盡量使用簡單的常用的開發語言原生的對象,尤其是集合類
4:對象不要有復雜的繼承關系,最好不要有父子類的情況

網絡通信模型的選擇
RPC是解決進程間通信的一種方式。
一次RPC調用,本質就是服務消費者與服務提供者間的一次網絡信息交換的過程。
服務調用者通過網絡發送一條請求消息,服務提供者接收並解析,處理完相關的業務邏輯之后,再發送一條響應消息給服務調用者,服務調用者接收並解析響應消息,處理完相關的響應邏輯,一次RPC調便結束了,網絡通信是整個RPC調用流程的基礎。

下圖屬於通信方式屬於那種通信IO模型呢?

 

 

 常見的網絡IO模型分為四種:

1:同步阻塞IO(BIO)
2:同步非阻塞IO(NIO)
3:IO多路復用和異步
4:非阻塞IO(AIO)

同步阻塞IO(BIO)

同步阻塞IO是最簡單最常見的IO模型,
默認情況下所有的socket都是阻塞的.

操作流程。
1: 應用進程發起IO系統調用后,應用進程被阻塞,轉到內核空間處理。
2: 內核開始等待數據,等待到數據之后,再將內核中的數據拷貝到用戶內存中,整個處理完畢后返回進程。
3: 應用的進程解除阻塞狀態,運行業務邏輯。

這里我們可以看到,系統內核處理IO操作分為兩個階段等待數據和拷貝數據。
而在這2階段中,應用進程中IO操作的線程會一直都處於阻塞狀態,
也就是說,內核准備數據和數據從內核拷貝到用戶空間這兩個過程都是阻塞的

如果是基於java多線程開發,那么每一個IO操作都要占用線程,直至IO操作結束。

這個流程就好比我們去餐廳吃飯,我們到達餐廳,向服務員點餐,之后要一直在餐廳等待后廚將菜做好,然后服務員會將菜端給我們,我們才能享用。

 

優點:

能夠及時返回數據,無延遲;
調用代碼邏輯簡單;

缺點:
等待浪費很多時間,影響程序性能

同步非阻塞IO(NIO)
同步非阻塞IO即在IO系統調用的過程中,進程不必阻塞,
而是采用定時輪詢(polling)的方式數據是否准備就緒;
在此期間,進程可以處理其他的任務。

1:進程發起read,進行recvfrom系統調用,如果內核中的數據還沒有准備好,就立刻返回一個error;
2:調用返回后進程可以進行其他操作,然后再次發起recvfrom系統調用,不斷重復;(這個過程稱為輪詢polling)
3:內核中的數據准備好以后,再次收到recvfrom調用,就將數據拷貝到了用戶內存,然后返回;
注意:在數據從內核拷貝到用戶內存的過程中,進程仍然是屬於阻塞的狀態

 

 優點:

能夠在IO操作過程中,處理其他的任務。
缺點:
任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,
而任務可能在兩次輪詢之間的任意時間完成,這會導致整體數據吞吐量的降低。

IO多路復用(IO multiplexing)
I/O多路復用就是通過一種機制,可以監視多個文件描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
常見的select, poll, epoll 都是IO多路復用。
需要注意的是select,poll,epoll本質上都是同步I/O,
因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的。
Java底層的NIO,redis,nginx底層IO模型就是IO多路復用模型

 

異步IO(asynchronous IO)

異步IO是事件驅動IO。
用戶進程發起IO操作之后,會立即返回,然后可以處理其他任務。
內核會等待數據准備完成,然后將數據拷貝到用戶內存。
當這一切都完成之后,內核會給用戶進程發送一個信號,通知IO操作完成。
在IO兩個階段,進程都是非阻塞的。 目前有很多開源的異步IO庫,例如libevent、libev、libuv。

 

 

IO多路復用更適合高並發的場景,可以使用較少的進程處理較多的socket的IO請求,如Netty

阻塞IO每處理一個socket的IO請求都會阻塞進程,並發量低,業務邏輯同步進行IO操作,阻塞IO可滿足,開銷比IO多路復用低

RPC調用在大多數的情況下,是高並發的調用場景 ,故網絡通信模型會選擇IO多路復用的方式,

最優的選擇是基於Reactor模式實現的框架,Java環境.首選Netty框架

Reactor介紹:https://www.cnblogs.com/winner-0715/p/8733787.html

RPC中的動態代理

RPC 框架解決的問題是:像調用本地接口一樣調用遠程的接口.

那么可使用動態代理去解決:
1:如何組裝數據報文
2:經過網絡傳輸發送至服務提供方
3:屏蔽遠程接口調用的細節

不需要改動原有代碼的前提下,實現非業務邏輯跟業務邏輯的解耦。

通過對字節碼進行增強,在方法調用的時候進行攔截,以便於在方法調用前后,增加需要的額外處理邏輯,如屏蔽網絡通信過程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM