RPC 實(shí)現(xiàn)以及相關(guān)學(xué)習(xí) ~
點(diǎn)擊上方藍(lán)色“小哈學(xué)Java”,選擇“設(shè)為星標(biāo)”
回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!
作者:xavior
yuque.com/xavior.wx/point/rpc-practice
我們即希望能夠敏捷開發(fā),不做重復(fù)的勞動(dòng),用別人的勢(shì)能賦能自己;又要成為一名能夠賦能別人的人,擁有自身的勢(shì)能。
在一個(gè)擁有成千上萬(wàn)大大小小的服務(wù)的公司里,每個(gè)團(tuán)隊(duì)在不同的機(jī)器上部署它們自己的服務(wù),所以真實(shí)開發(fā)一個(gè)新服務(wù)的場(chǎng)景一定需要考慮兩個(gè)問(wèn)題:
我的團(tuán)隊(duì)開發(fā)一個(gè)新服務(wù),可能需要調(diào)用別人的服務(wù)。
我的團(tuán)隊(duì)開發(fā)一個(gè)新服務(wù),別的團(tuán)隊(duì)可能會(huì)調(diào)用。
RPC調(diào)用的變與不變
由于服務(wù)部署在不同機(jī)器,想要進(jìn)行服務(wù)間的調(diào)用必須進(jìn)行網(wǎng)絡(luò)通信,那服務(wù)消費(fèi)方每調(diào)用一個(gè)服務(wù)都要寫一大堆網(wǎng)絡(luò)通信的東西,不僅復(fù)雜而且極易出錯(cuò)。
我們知道此時(shí)我們的技術(shù)選型時(shí)很豐富的,關(guān)于各種技術(shù)的優(yōu)缺點(diǎn)網(wǎng)上很多文章,可以去編乎的相關(guān)問(wèn)題去看看,我覺(jué)得概括的比較好一句話是良好的RPC調(diào)用是面向服務(wù)的封裝,針對(duì)的是服務(wù)的可用性和效率,減輕網(wǎng)絡(luò)服務(wù)開發(fā)和調(diào)用的復(fù)雜性。
但我們不管選擇何種進(jìn)程間通信手段,http,TCP通信或是消息中間件、RPC通信,調(diào)用本身很多東西是不可能變的:
角色的定義(發(fā)起調(diào)用的是客戶端,接受調(diào)用的是服務(wù)端)
通信的機(jī)制(網(wǎng)絡(luò)IO,序列化,傳輸協(xié)議,同步異步)
那真正變的是什么?這些都是你遇到的場(chǎng)景以及你的目標(biāo)導(dǎo)致的,對(duì)于RPC來(lái)說(shuō),主要來(lái)說(shuō)和其他相比比較大的變化在于下面兩條吧。
調(diào)用的目標(biāo)。服務(wù)透明化,目標(biāo)是讓用戶像以本地調(diào)用方式調(diào)用遠(yuǎn)程服務(wù)。
調(diào)用的方式。服務(wù)端的服務(wù)要被調(diào)用,客戶端在本地直接調(diào)用服務(wù)端提供的接口即可,而不需要調(diào)用真實(shí)的接口實(shí)現(xiàn)。于是服務(wù)端就是需要利用一些很多反射操作去完成。
RPC需要什么
想要實(shí)現(xiàn)一個(gè)基本的RPC框架,其實(shí)需要什么?
網(wǎng)絡(luò)IO,BIO\NIO\AIO,Socket編程,HTTP通信,一個(gè)就行。
序列化,JDK序列化,JSON、Hessian、Kryo、ProtoBuffer、ProtoStuff、Fst知道一個(gè)就行。
反射,JDK或者Cglib的動(dòng)態(tài)代理。
那一個(gè)優(yōu)秀的RPC框架,還需要考慮什么問(wèn)題?
一個(gè)服務(wù)可能有多個(gè)實(shí)例,你在調(diào)用時(shí),要如何獲取這些實(shí)例的地址?服務(wù)注冊(cè)中心
多個(gè)實(shí)例,選哪個(gè)調(diào)用好?負(fù)載均衡
服務(wù)注冊(cè)中心每次都查?緩存相關(guān)
客戶端每次要等服務(wù)器返回結(jié)果?異步調(diào)用
服務(wù)是要升級(jí)的?版本控制
多個(gè)服務(wù)依賴,某個(gè)有問(wèn)題?熔斷器
某個(gè)服務(wù)出了問(wèn)題怎么辦?監(jiān)控 ...
Dubbo
其實(shí)要考慮的問(wèn)題是非常多的,瞻仰一下Dubbo的流程圖和Dubbo團(tuán)隊(duì)對(duì)未來(lái)的規(guī)劃圖:

自己實(shí)現(xiàn)的一個(gè)簡(jiǎn)單RPC框架
看了很多網(wǎng)上的博客實(shí)現(xiàn)的,最后自己實(shí)現(xiàn)的地址:https://github.com/1000-7/xinrpc 在看阿里技術(shù)大學(xué)的HSF視頻課的時(shí)候,視頻的講師說(shuō)想要理解RPC就把下面這張圖理解清楚就夠了,這張圖也是HSF官方文檔中介紹一次調(diào)用流程使用的圖。

借此實(shí)現(xiàn)的機(jī)會(huì),自己又學(xué)習(xí)實(shí)踐了包括Netty、Java反射、序列化、java注解、SpringBoot等很多方面的知識(shí)。
整體調(diào)用流程
由于采用了etcd做服務(wù)注冊(cè)中心,所以整體調(diào)用流程可以被概括為下面這樣:
Server端啟動(dòng)進(jìn)行服務(wù)注冊(cè)到etcd;
Client端啟動(dòng)獲取etcd的服務(wù)注冊(cè)信息,定期更新;
Client以本地調(diào)用方式調(diào)用服務(wù)(使用接口,例如helloService.sayHi("world"));
Client通過(guò)RpcProxy會(huì)使用對(duì)應(yīng)的服務(wù)名生成動(dòng)態(tài)代理相關(guān)類,而動(dòng)態(tài)代理類會(huì)將請(qǐng)求的對(duì)象中的方法、參數(shù)等組裝成能夠進(jìn)行網(wǎng)絡(luò)傳輸?shù)南ⅢwRpcRequest;
Client通過(guò)一些的負(fù)載均衡方式確定向某臺(tái)Server發(fā)送編碼(RpcEncoder)過(guò)后的請(qǐng)求(netty實(shí)現(xiàn))
Server收到請(qǐng)求進(jìn)行解碼(RpcDecoder),通過(guò)反射(cglib的FastMethod實(shí)現(xiàn))會(huì)進(jìn)行本地的服務(wù)執(zhí)行
Server端writeAndFlush()將RpcResponse返回;
Clinet將返回的結(jié)果會(huì)進(jìn)行解碼,得到最終結(jié)果。
Netty學(xué)習(xí)
Netty是一款異步的事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,支持快速地開發(fā)可維護(hù)的高性能的面向協(xié)議的服務(wù)器和客戶端。
Netty重要的幾個(gè)概念
Channel:這并不是Netty專有的概念,Java NIO里也有??梢钥醋魇侨胝净蛘叱鰬?zhàn)數(shù)據(jù)的載體,有各種基本的read、write、connect、bind等方法,相當(dāng)于傳統(tǒng)IO的Socket,需要關(guān)注一下ServerChannel,ServerChannel負(fù)責(zé)創(chuàng)建子Channel,子Channel具體去執(zhí)行一些具體accept之后的讀寫操作。項(xiàng)目中用的NioSocketChannel和NioServerSocketChannel。
EventLoop和EventLoopGroup:Netty的核心抽象,channel的整個(gè)生命周期都是通過(guò)EventLoop去處理。EventLoop相當(dāng)于對(duì)Thread的封裝,一個(gè)EventLoop里面擁有一個(gè)永遠(yuǎn)都不會(huì)改變的Thread,同時(shí)任務(wù)的提交只需要通過(guò)EventLoop就可執(zhí)行;而EventLoopGroup負(fù)責(zé)為每個(gè)Channel分配一個(gè)EventLoop/
ChannelFuture:Netty所有的IO操作都是異步的原因。
ChannelHandler和ChannelPipeline:開發(fā)人員主要關(guān)注的也可能是唯一需要關(guān)注的兩個(gè)組件,用來(lái)管理數(shù)據(jù)流以及執(zhí)行應(yīng)用程序處理邏輯。
ChannelInboundHandler和ChannelOutboundHandler:兩個(gè)常見的ChannelHandler適配器,前者管理入站的數(shù)據(jù)和操作,后者管理出站的數(shù)據(jù)和操作,謹(jǐn)記:入站順序執(zhí)行,出站逆序執(zhí)行。
ChannelPipeline:一個(gè)攔截流經(jīng)某個(gè)channel的入站和出站時(shí)間的ChannelHandle實(shí)例鏈,每一個(gè)Channel剛被創(chuàng)建就會(huì)被分配一個(gè)ChannelPipeline,永久不可更改。
ChannelHandlerContext:ChannelHandle和ChannelPipeline中間管理的紐帶,每一個(gè)ChannelHandler分配一個(gè)ChannelHandlerContext用來(lái)跟其他Handler作交互。
ByteBuf:網(wǎng)絡(luò)數(shù)據(jù)的基本單位是字節(jié),Java NIO使用的ByteBuffer作為字節(jié)容器,而Netty使用ByteBuf替代ByteBuffer作為數(shù)據(jù)容器進(jìn)行讀寫。
BootStrap:將各種組件拼圖進(jìn)行組裝,ServerBootstrap用來(lái)引導(dǎo)服務(wù)端,Bootstrap用來(lái)引導(dǎo)客戶端。ServerBootstrap的Group一般會(huì)放入兩個(gè)EventLoopGroup,需要結(jié)合Channel去理解,ServerChannel會(huì)有子Channel,那為了處理這個(gè)Channel,你需要為每一個(gè)子Channel分配一個(gè)EventLoop,第二個(gè)EventLoopGroup是為了讓子Channel去共享一個(gè)EventLoop,避免額外的線程創(chuàng)建以及上下文切換。
ByteToMessageDecoder和MessageToByteEncoder:編解碼器的解碼器和編碼器,MessageToByteEncoder繼承了ChannelOutboundHandlerAdapter接口,ByteToMessageDecoder繼承了ChannelInboundHandlerAdapter接口。解碼器是將字節(jié)解碼為消息;編碼器是將消息編碼成字節(jié)。
Netty學(xué)習(xí)的其他問(wèn)題
1.序列化和編碼都是把 Java 對(duì)象封裝成二進(jìn)制數(shù)據(jù)的過(guò)程,這兩者有什么區(qū)別和聯(lián)系?序列化是把內(nèi)容變成計(jì)算機(jī)可傳輸?shù)馁Y源,而編碼則是讓程序認(rèn)識(shí)這份資源。
2.與服務(wù)端啟動(dòng)相比,客戶端啟動(dòng)的引導(dǎo)類少了哪些方法,為什么不需要這些方法?服務(wù)端:需要兩個(gè)線程組,NioServerSocketChannel線程模型,可以設(shè)置childHandle 客戶端:一個(gè)線程組,NioSocketChannel線程模型,只可以設(shè)置handler
3.ChannelPipeline執(zhí)行順序?
(1)InboundHandler順序執(zhí)行,OutboundHandler逆序執(zhí)行
(2)InboundHandler之間傳遞數(shù)據(jù),通過(guò)ctx.fireChannelRead(msg)
(3)InboundHandler通過(guò)ctx.write(msg),則會(huì)傳遞到outboundHandler (4) ?使用ctx.write(msg)傳遞消息,Inbound需要放在結(jié)尾,在Outbound之后,不然outboundhandler會(huì)不執(zhí)行;但是使用channel.write(msg)、pipline.write(msg)情況會(huì)不一致,都會(huì)執(zhí)行,那是因?yàn)閏hannel和pipline會(huì)貫穿整個(gè)流。
(5) ?outBound和Inbound誰(shuí)先執(zhí)行,針對(duì)客戶端和服務(wù)端而言,客戶端是發(fā)起請(qǐng)求再接受數(shù)據(jù),先outbound(寫)再inbound(讀),服務(wù)端則相反。
(6)outBound可以理解為數(shù)據(jù)“出航”,inBound可以理解為“歸航”,所以請(qǐng)求從客戶端到服務(wù)端就意味著,請(qǐng)求數(shù)據(jù)從客戶端出航,在服務(wù)端歸航,服務(wù)端響應(yīng)請(qǐng)求是從服務(wù)端到客戶端的,所以就是響應(yīng)數(shù)據(jù)從服務(wù)端出航,在客戶端歸航。
4.三種最常見的ChannelHandle的子類型?
a. 基于 ByteToMessageDecoder,我們可以實(shí)現(xiàn)自定義解碼,而不用關(guān)心 ByteBuf 的強(qiáng)轉(zhuǎn)和 解碼結(jié)果的傳遞。
b. 基于 SimpleChannelInboundHandler,這主要針對(duì)的最常見的一種情況,你去接收一種(泛型)解碼信息,然后對(duì)數(shù)據(jù)應(yīng)用業(yè)務(wù)邏輯然后繼續(xù)傳下去。我們可以實(shí)現(xiàn)每一種指令的處理,通過(guò)泛型不再需要強(qiáng)轉(zhuǎn),不再有冗長(zhǎng)乏味的 if else 邏輯,不需要手動(dòng)傳遞對(duì)象。
c. 基于 MessageToByteEncoder,我們可以實(shí)現(xiàn)自定義編碼,而不用關(guān)心 ByteBuf 的創(chuàng)建,不用每次向?qū)Χ藢?Java 對(duì)象都進(jìn)行一次編碼。
5.Netty關(guān)于拆包粘包理論與解決方案?
本次使用的是LengthFieldBasedFrameDecoder。
a.固定長(zhǎng)度的拆包器 FixedLengthFrameDecoder 如果你的應(yīng)用層協(xié)議非常簡(jiǎn)單,每個(gè)數(shù)據(jù)包的長(zhǎng)度都是固定的,比如 100,那么只需要把這個(gè)拆包器加到 pipeline 中,Netty 會(huì)把一個(gè)個(gè)長(zhǎng)度為 100 的數(shù)據(jù)包 (ByteBuf) 傳遞到下一個(gè) channelHandler。
b.行拆包器 LineBasedFrameDecoder 從字面意思來(lái)看,發(fā)送端發(fā)送數(shù)據(jù)包的時(shí)候,每個(gè)數(shù)據(jù)包之間以換行符作為分隔,接收端通過(guò) LineBasedFrameDecoder 將粘過(guò)的 ByteBuf 拆分成一個(gè)個(gè)完整的應(yīng)用層數(shù)據(jù)包。
c.分隔符拆包器 DelimiterBasedFrameDecoder DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不過(guò)我們可以自定義分隔符。
d.基于長(zhǎng)度域拆包器 LengthFieldBasedFrameDecoder 最后一種拆包器是最通用的一種拆包器,只要你的自定義協(xié)議中包含長(zhǎng)度域字段,均可以使用這個(gè)拆包器來(lái)實(shí)現(xiàn)應(yīng)用層拆包。由于上面三種拆包器比較簡(jiǎn)單,讀者可以自行寫出 demo,接下來(lái),我們就結(jié)合我們小冊(cè)的自定義協(xié)議,來(lái)學(xué)習(xí)一下如何使用基于長(zhǎng)度域的拆包器來(lái)拆解我們的數(shù)據(jù)包。
CGLib學(xué)習(xí)
反射和動(dòng)態(tài)代理
反射機(jī)制是Java語(yǔ)言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí) 自省 (introspect,官方用語(yǔ))的能力。通過(guò)反射我們可以直接操作類或者對(duì)象,比如獲取某個(gè)對(duì)象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對(duì)象,甚至可以運(yùn)行時(shí)修改類定義。
動(dòng)態(tài)代理是一種方便運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建代理、動(dòng)態(tài)處理代理方法調(diào)用的機(jī)制,很多場(chǎng)景都是利用類似機(jī)制做到的,比如用來(lái)包裝 RPC 調(diào)用、面向切面的編程(AOP)。實(shí)現(xiàn)動(dòng)態(tài)代理的方式很多,比如 JDK 自身提供的動(dòng)態(tài)代理,就是主要利用了上面提到的反射機(jī)制。還有其他的實(shí)現(xiàn)方式,比如利用傳說(shuō)中更高性能的字節(jié)碼操作機(jī)制,類似 ASM、cglib(基于 ASM)等。
總結(jié):反射是java的一種能力,而動(dòng)態(tài)代理是一種解決問(wèn)題的方案。動(dòng)態(tài)代理是一種代理模式。代理可以看作是對(duì)調(diào)用目標(biāo)的一個(gè)包裝,這樣我們對(duì)目標(biāo)代碼的調(diào)用不是直接發(fā)生的,而是通過(guò)代理完成。通過(guò)代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦 。
CGLib實(shí)現(xiàn)反射
????FastClass?fastClass?=?FastClass.create(serviceClass);??
????FastMethod?fastMethod?=?fastClass.getMethod(methodName,?parameterTypes);??
????return?fastMethod.invoke(serviceBean,?parameters);??
CGLib實(shí)現(xiàn)動(dòng)態(tài)代理
實(shí)現(xiàn)MethodInterceptor接口,然后使用Enhancer構(gòu)建
????public?static??T?createByCglib(Class?clazz) ?{??
????????Enhancer?enhancer?=?new?Enhancer();??
????????enhancer.setSuperclass(clazz);??
????????enhancer.setCallback(new?RpcMethodInterceptor(clazz));??
????????return?(T)?enhancer.create();??
????}??
JDK實(shí)現(xiàn)動(dòng)態(tài)代理
實(shí)現(xiàn)InvocationHandler接口,然后使用Proxy創(chuàng)建
????public?static??T?create(Class?interfaceClass) ?{??
????????return?(T)?Proxy.newProxyInstance(??
????????????????interfaceClass.getClassLoader(),??
????????????????new?Class>[]{interfaceClass},??
????????????????new?RpcInvocationHandler<>(interfaceClass)??
????????);??
????}??
序列化實(shí)現(xiàn)
序列化有多種實(shí)現(xiàn)方式,不同序列化優(yōu)缺點(diǎn)不同,網(wǎng)上有很多比較天梯圖。我實(shí)現(xiàn)了五種,JSON,F(xiàn)ST,HESSIAN2,PROTO_STUFF,KRYO。
END
有熱門推薦??
1.?太牛逼了!項(xiàng)目中用了Disruptor之后,性能提升了2.5倍
2.?科普 | 關(guān)于加解密、加簽驗(yàn)簽的那些事
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)



