一文了解RPC框架原理
點(diǎn)擊上方“開源Linux”,選擇“設(shè)為星標(biāo)”
回復(fù)“學(xué)習(xí)”獲取獨(dú)家整理的學(xué)習(xí)資料!
1.RPC框架的概念
RPC(Remote Procedure Call)–遠(yuǎn)程過程調(diào)用,通過網(wǎng)絡(luò)通信調(diào)用不同的服務(wù),共同支撐一個(gè)軟件系統(tǒng),微服務(wù)實(shí)現(xiàn)的基石技術(shù)。使用RPC可以解耦系統(tǒng),方便維護(hù),同時(shí)增加系統(tǒng)處理請求的能力。

上面是一個(gè)簡單的軟件系統(tǒng)結(jié)構(gòu),我們拆分出來用戶系統(tǒng)和訂單系統(tǒng)做為服務(wù)存在,讓不同的站點(diǎn)去調(diào)用。
只需要引入各個(gè)服務(wù)的接口包,在代碼中調(diào)用RPC服務(wù)就跟調(diào)用本地方法一樣,我剛接觸到這種調(diào)用方式的時(shí)候頗為驚奇,我明明調(diào)用的就是java語言方法?。ㄒ詊ava為例,現(xiàn)在RPC框架一般都支持多語言),怎么就調(diào)用了遠(yuǎn)程的服務(wù)了呢??
2.RPC框架的原理解析
2.1 流程縱覽

如上圖所示,我將一個(gè)RPC調(diào)用流程概括為上圖中5個(gè)流程,左邊3個(gè)為客戶端流程,右邊兩個(gè)為服務(wù)端流程。下面就各流程進(jìn)行解析
2.2 客戶端調(diào)用
服務(wù)調(diào)用方在調(diào)用服務(wù)時(shí),一般進(jìn)行相關(guān)初始化,通過配置文件/配置中心 獲取服務(wù)端地址 用戶調(diào)用:
//?用戶服務(wù)接口
public?interface?UserService?{
?
?public?User?genericUser(Integer?id,String?name,Long?phone);
?
}
//調(diào)用方
//服務(wù)初始化
KRPC.init("D:\\krpc\\service\\demo\\conf\\client.xml");
UserService?service?=?ProxyFactory.create(UserService.class,?"demo","demoService");
User?user?=?service.genericUser(1,?"yasin",?1888888888L);
一開始接觸RPC調(diào)用方法肯定就有疑惑,它不是一個(gè)接口嗎,直接調(diào)用應(yīng)該沒啥效果啊,我也沒有引入實(shí)現(xiàn)包。
帶著這個(gè)疑惑,我們就進(jìn)入下一個(gè)知識點(diǎn),動(dòng)態(tài)代理
2.3動(dòng)態(tài)代理
動(dòng)態(tài)代理這東西意如其名,它代理你幫你做事情。上面我們不說直接調(diào)用一個(gè)接口中的方法,并且沒有用該接口的實(shí)現(xiàn)類調(diào)用,那么方法是怎么生效的呢?
可以看到這個(gè)用戶服務(wù)這個(gè)service是由ProxyFactory代理工程創(chuàng)造的,在該P(yáng)roxyFactory#create()方法中就跟一個(gè)代理處理器綁定在一起了
@Override
public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????//構(gòu)造請求request
????Request?request?=?new?Request();
????....
???return?RequestHandler.request(serviceName,?request,returnClass);
}
這個(gè)類實(shí)現(xiàn)了InvocationHandler接口(JDK提供的動(dòng)態(tài)代理技術(shù)),每次去調(diào)用接口方法,最終都交由該handler進(jìn)行處理。這個(gè)環(huán)節(jié)一般會獲取方法的一些信息,例如方法名,方法參數(shù)類型,方法參數(shù)值,返回對象類型。
同時(shí)這個(gè)環(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,可以快速一個(gè)高性能、高可靠的一個(gè)服務(wù)端。
public?class?ServerHandler?extends?ChannelInboundHandlerAdapter?{
????@Override
????public?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);
????}
????@Override
????public?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ù)端的動(dòng)態(tài)加載
通過2.2到2.6的說明,一次RPC請求過程大致如此,但是一個(gè)RPC框架會有很多細(xì)節(jié)需要處理。
其實(shí)在一次請求調(diào)用前,服務(wù)端肯定要先啟動(dòng)。
服務(wù)端作為一個(gè)容器,跟我們熟知的tomcat一樣,它可以動(dòng)態(tài)的加載任何項(xiàng)目。所以在服務(wù)端啟動(dòng)的時(shí)候,必須要進(jìn)行一個(gè)動(dòng)態(tài)加載的過程。在KRPC中,我使用了URLClassLoader動(dòng)態(tài)加載一個(gè)指定路徑的jar包,任何業(yè)務(wù)服務(wù)的實(shí)現(xiàn)所依賴的jar包都可以放入該路徑中。
3.總結(jié)
一個(gè)RPC框架大致需要?jiǎng)討B(tài)代理、序列化、網(wǎng)絡(luò)請求、網(wǎng)絡(luò)請求接受(netty實(shí)現(xiàn))、動(dòng)態(tài)加載、反射這些知識點(diǎn)?,F(xiàn)在開源及各公司自己造的RPC框架層出不窮,唯有掌握原理是一勞永逸的。掌握原理最好的方法莫不是閱讀源碼,自己動(dòng)手寫是最快的。
作者:_Yasin
出處:http://002ii.cn/rxIivD
關(guān)注「開源Linux」加星標(biāo),提升IT技能
