前言
本文只是簡單實現了一次RPC調用示例,以理解其調用原理。一些主流RPC框架的其他功能并沒有實現。(如服務自動注冊與發現,流控,動態配置等)
PRC調用核心
像調用本地代碼一樣調用遠程服務。
調用方只需調用服務方所提供的接口,通過Java動態代理,代理方法內,與服務方進行網絡交互,得到服務方返回結果。基于上述,調用方只需依賴服務方所提供的接口。在使用時的感覺就像是,調用了本地代碼一樣。其實是用代理模式屏蔽了底層的網絡交互。
簡單的RPC調用所涉及的底層技術
Java動態代理
Java反射
Java序列化
NIO網絡模型(Netty)
Netty是什么
- Netty 是 JBoss 公司用 Java 寫的一個 Jar 包(庫),目的是快速開發高性能、高可靠性的網絡服務器和客戶端程序
- Netty 提供異步、無阻塞、事件驅動的網絡應用程序框架和工具
- Netty 是目前公認的網絡編程最好的框架,官網地址:http://netty.io/
- GitHub 托管地址:https://github.com/netty/netty
- Netty 底層封裝的也是 Java 的NIO,所以也叫NIO框架,常用于開發分布式系統
推薦該博主的關于netty的文章:
https://blog.csdn.net/wangmx1993328/article/details/83035760
動手實現
調用時序圖
我們先來看一次PRC調用的時序圖:
- Client:服務調用方
- Proxy : 調用方動態代理組件
- Netty_C:調用方Netty客戶端
- 注冊中心:服務自動注冊與發現,可以是ZooKeeper
- Netty_S : 提供方Netty服務端
- Server: 服務提供方
注:上圖紅色虛線框中的功能,本示例沒有涵蓋。本示例通過API方式配置,直連服務節點。
類圖
- MyRpcServiceContainer : 調用方入口,主要用于獲取代理服務,存儲服務節點的信息。
- MyRpcServiceGroup : 服務節點的集合,并提供負載均衡策略(未實現)。
- MyRpcServiceNode : 單個服務節點的信息。
- MyRpcClientProxy : 調用方動態代理實現類。
- MyRpcClient : Netty客戶端,對連接信息進行配置,如序列化反序列化Handler和異步處理返回結果的Handler。
- MyRpcClientHandler : Netty客戶端異步處理的Handler。主要用于發送請求信息等。
- MyRpcRequest : 請求對象封裝,包含請求接口,請求方法,請求參數等。
- MyRpcResponse : 請求結果封裝,包含方法返回結果。
- MyRpcServer : 提供方入口,主要用于暴露服務。
- MyRpcServerConfig : 提供方服務的集合,以及一些配置信息。
- MyRpcServiceImplProxy : 提供方服務代理,代理服務的方法具體實現,并提供流控等功能。
- MyRpcFlowControl : 流控計數器,針對接口、方法維度,提供流控計數功能。
- MyRpcServerHandler : Netty服務端異步處理的Handler。主要用于發送執行結果等。
代碼講解
TODO
測試
服務提供方
public class MyRpcServerTest {
@Test
public void testName() {
MyRpcServer myRpcServer = new MyRpcServer();
MyRpcServerConfig config = new MyRpcServerConfig();
config.setPort(8888);
//注冊服務,此處為api方式
MyRpcServiceImplProxy implProxy = new MyRpcServiceImplProxy(DemoRpcService.class);
config.addService(DemoPrcInterface.class, implProxy);
myRpcServer.config(config).start();
}
}
十一月 13, 2019 5:24:09 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0xea1be79c] REGISTERED
十一月 13, 2019 5:24:09 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0xea1be79c] BIND: 0.0.0.0/0.0.0.0:8888
十一月 13, 2019 5:24:09 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
main,服務器開始監聽端口,等待客戶端連接.........
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0xac188c6d, L:/127.0.0.1:8888 - R:/127.0.0.1:61210]
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x46558bc0, L:/127.0.0.1:8888 - R:/127.0.0.1:61208]
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x7b6f1d71, L:/127.0.0.1:8888 - R:/127.0.0.1:61209]
十一月 13, 2019 5:24:25 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xea1be79c, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
服務調用方
public class MyRpcServiceContainerTest {
private CountDownLatch countDownLatch = new CountDownLatch(3);
@Test
public void testName() throws InterruptedException {
Map<Class, MyRpcServiceGroup> serviceInfoMap = new HashMap<Class, MyRpcServiceGroup>();
MyRpcServiceGroup serviceInfo = new MyRpcServiceGroup();
serviceInfo.addNode(new MyRpcServiceNode("127.0.0.1",8888));
serviceInfoMap.put(DemoPrcInterface.class, serviceInfo);
MyRpcServiceContainer container = new MyRpcServiceContainer(serviceInfoMap);
//上面為服務發現,此處為api方式
final DemoPrcInterface intf = container.getService(DemoPrcInterface.class);
//System.out.println(intf.helloWithName("zxm"));
final DemoReq req = new DemoReq();
req.setUuid("123456");
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
try {
System.out.println(intf.callRemoteService(req,"yyxl").getUuid());
}catch (Exception e){
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
}).start();
}
countDownLatch.await();
}
}
Thread-2,客戶端發起異步連接..........
Thread-1,客戶端發起異步連接..........
Thread-0,客戶端發起異步連接..........
nioEventLoopGroup-3-1,Server return Message:com.yyxl.myrpc.service.dto.MyRpcResponse@6585879b
nioEventLoopGroup-4-1,Server return Message:com.yyxl.myrpc.service.dto.MyRpcResponse@74997d68
java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy3.callRemoteService(Unknown Source)
at com.yyxl.myrpc.service.consumer.MyRpcServiceContainerTest$1.run(MyRpcServiceContainerTest.java:32)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.Exception: 流控
at com.yyxl.myrpc.service.provider.MyRpcServiceImplProxy.call(MyRpcServiceImplProxy.java:57)
at com.yyxl.myrpc.service.provider.MyRpcServerHandler.channelRead(MyRpcServerHandler.java:26)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:656)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:591)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:508)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:470)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:909)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
... 1 more
java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy3.callRemoteService(Unknown Source)
at com.yyxl.myrpc.service.consumer.MyRpcServiceContainerTest$1.run(MyRpcServiceContainerTest.java:32)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.Exception: 流控
at com.yyxl.myrpc.service.provider.MyRpcServiceImplProxy.call(MyRpcServiceImplProxy.java:57)
at com.yyxl.myrpc.service.provider.MyRpcServerHandler.channelRead(MyRpcServerHandler.java:26)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:656)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:591)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:508)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:470)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:909)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
... 1 more
nioEventLoopGroup-2-1,Server return Message:com.yyxl.myrpc.service.dto.MyRpcResponse@686c50af
123456##yyxl
我們可以看到,默認流控閥值為1,前2個請求直接返回流控異常,第3個返回正常調用結果。
參考
徹底理解Netty,這一篇文章就夠了
Netty 入門示例詳解
Netty之傳輸POJO(使用Java自帶的序列化方式)
動態代理
【Java 筆記】Java 反射相關整理