一篇文章了解RPC框架原理
轉(zhuǎn)載自? ?一篇文章了解RPC框架原理
1.RPC框架的概念
RPC(Remote Procedure Call)–遠(yuǎn)程過程調(diào)用,通過網(wǎng)絡(luò)通信調(diào)用不同的服務(wù),共同支撐一個軟件系統(tǒng),微服務(wù)實(shí)現(xiàn)的基石技術(shù)。使用RPC可以解耦系統(tǒng),方便維護(hù),同時增加系統(tǒng)處理請求的能力。
?
上面是一個簡單的軟件系統(tǒng)結(jié)構(gòu),我們拆分出來用戶系統(tǒng)和訂單系統(tǒng)做為服務(wù)存在,讓不同的站點(diǎn)去調(diào)用。
只需要引入各個服務(wù)的接口包,在代碼中調(diào)用RPC服務(wù)就跟調(diào)用本地方法一樣,我剛接觸到這種調(diào)用方式的時候頗為驚奇,我明明調(diào)用的就是java語言方法啊(已java為例,現(xiàn)在RPC框架一般都支持多語言),怎么就調(diào)用了遠(yuǎn)程的服務(wù)了呢??
2.RPC框架的原理解析
最近自己寫了一個簡單的RPC框架KRPC,本文原理分析結(jié)合中代碼,均為該框架源碼?https://github.com/yangzhenkun/krpc
2.1 流程縱覽
?
如上圖所示,我將一個RPC調(diào)用流程概括為上圖中5個流程,左邊3個為客戶端流程,右邊兩個為服務(wù)端流程。?
下面就各流程進(jìn)行解析
2.2 客戶端調(diào)用
服務(wù)調(diào)用方在調(diào)用服務(wù)時,一般進(jìn)行相關(guān)初始化,通過配置文件/配置中心 獲取服務(wù)端地址?
用戶調(diào)用:
一開始接觸RPC調(diào)用方法肯定就有疑惑,它不是一個接口嗎,直接調(diào)用應(yīng)該沒啥效果啊,我也沒有引入實(shí)現(xiàn)包。
帶著這個疑惑,我們就進(jìn)入下一個知識點(diǎn),動態(tài)代理
2.3動態(tài)代理
動態(tài)代理這東西意如其名,它代理你幫你做事情。?
上面我們不說道直接調(diào)用一個接口中的方法,并且沒有用該接口的實(shí)現(xiàn)類調(diào)用,那么方法是怎么生效的呢?
可以看到這個用戶服務(wù)這個service是由ProxyFactory代理工程創(chuàng)造的,在該P(yáng)roxyFactory#create()方法中就跟一個代理處理器綁定在一起了
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//構(gòu)造請求requestRequest request = new Request();....return RequestHandler.request(serviceName, request,returnClass); }這個類實(shí)現(xiàn)了InvocationHandler接口(JDK提供的動態(tài)代理技術(shù)),每次去調(diào)用接口方法,最終都交由該handler進(jìn)行處理。?
這個環(huán)節(jié)一般會獲取方法的一些信息,例如方法名,方法參數(shù)類型,方法參數(shù)值,返回對象類型。
同時這個環(huán)節(jié)會提供序列化功能,一般的RPC網(wǎng)絡(luò)傳輸使用TCP(哪怕使用HTTP)傳輸,這里也要將這些參數(shù)進(jìn)行封裝成我們定義的數(shù)據(jù)接口進(jìn)行傳輸。
2.4網(wǎng)絡(luò)傳輸
我們通過將方法參數(shù)進(jìn)行處理后,就要使用發(fā)起網(wǎng)絡(luò)請求,使用tcp傳輸?shù)木屠胹ocket通信進(jìn)行傳輸,這一塊我開源項(xiàng)目中使用的同步堵塞的方案進(jìn)行請求,也可以使用一些非堵塞方案進(jìn)行請求,效率會更高一些。
2.5服務(wù)端數(shù)據(jù)接受
這一塊使用netty,可以快速一個高性能、高可靠的一個服務(wù)端。
public class ServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf)msg; ?byte[] bytes = new byte[buf.readableBytes()]; ?buf.readBytes(bytes); ?byte[] responseBytes = RequestHandler.handler(bytes);ByteBuf resbuf = ctx.alloc().buffer(responseBytes.length);resbuf.writeBytes(responseBytes);ctx.writeAndFlush(resbuf);} ? ?@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}}上面代碼是我項(xiàng)目中使用的服務(wù)端代碼。關(guān)于netty網(wǎng)上學(xué)習(xí)的資料很多,這里也只是宏觀的講解RPC原理,就不展開。
2.6真實(shí)調(diào)用
服務(wù)端獲取客戶端請求的數(shù)據(jù)后, 調(diào)用請求中的方法,方法參數(shù)值,通過反射調(diào)用真實(shí)的方法,獲取其返回值,將其序列化封裝,通過netty進(jìn)行數(shù)據(jù)返回,客戶端在接受數(shù)據(jù)并解析,這就完成了一次rpc請求調(diào)用的全過程。
method = clazz.getMethod(request.getMethodName(),requestParamTypes); method.setAccessible(true); result = method.invoke(service, requestParmsValues)上面代碼片段為通過反射調(diào)用真實(shí)方法
2.7 服務(wù)端的動態(tài)加載
通過2.2到2.6的說明,一次RPC請求過程大致如此,但是一個RPC框架會有很多細(xì)節(jié)需要處理。
其實(shí)在一次請求調(diào)用前,服務(wù)端肯定要先啟動。
服務(wù)端作為一個容器,跟我們熟知的tomcat一樣,它可以動態(tài)的加載任何項(xiàng)目。所以在服務(wù)端啟動的時候,必須要進(jìn)行一個動態(tài)加載的過程。在KRPC中,我使用了URLClassLoader動態(tài)加載一個指定路徑的jar包,任何業(yè)務(wù)服務(wù)的實(shí)現(xiàn)所依賴的jar包都可以放入該路徑中。
?
3.總結(jié)
一個RPC框架大致需要動態(tài)代理、序列化、網(wǎng)絡(luò)請求、網(wǎng)絡(luò)請求接受(netty實(shí)現(xiàn))、動態(tài)加載、反射這些知識點(diǎn)。現(xiàn)在開源及各公司自己造的RPC框架層出不窮,唯有掌握原理是一勞永逸的。掌握原理最好的方法莫不是閱讀源碼,自己動手寫是最快的。
https://github.com/yangzhenkun/krpc
這是我最近開始寫的一個RPC框架,麻雀雖小五臟俱全,雖然這個框架我還在開發(fā)中(還有一些功能沒有完成),歡迎一起討論,star,這個框架中有很多地方可以改進(jìn)的點(diǎn)。
總結(jié)
以上是生活随笔為你收集整理的一篇文章了解RPC框架原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1799 元新低:大疆 Osmo Act
- 下一篇: 分布式架构--基本思想汇总