MyRPC版本三
上一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/119153197
Netty高性能网络框架的使用
Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
针对之前提出的问题,主要就是提高这个性能,从两个方面入手,网络传输从BIO到NIO,序列化要减少字节流长度,提高序列化反序列化效率,而知名的RPC框架,dubbo和grpc都是用netty底层进行通信的
pom.xml 导入netty
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
重构客户端代码
是因为客户端代码太乱了,先进行代码重构,才有利于后面使用netty的方式实现客户端,使之不同方式网络连接的客户端有相同的结构,同样的api
假设有了两个客户端,SimpleRPCClient (使用Java BIO的方式)和NettyRPCClient(使用netty) ,他们的共性是发送请求与得到response,而建立连接与发送请求的方式是不同的
RPC接口以及一个实现
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
/**
* 代替原来的RPCClient
*/
//直接把客户端作为一个接口
//把共性给抽取出来
public interface RPCClient {
//共性就是 发送请求和得到response
RPCResponse sendRequest(RPCRequest request);
}
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import lombok.AllArgsConstructor;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 代替原来的SimpleRPCClient
*/
//使用BIO方式实现这个接口
@AllArgsConstructor
public class SimpleRPCClient implements RPCClient {
//携带的参数
private String host;
private int port;
//这里客户端发起一次请求调用,Socket建立连接,发起请求request,得到相应response
//这里request 是封装好的,不同的service需要进行不同的封装
//客户只需要知道service接口,需要一层动态代理来根据反射封装不同的service
public RPCResponse sendRequest(RPCRequest request) {
try{
//发起一次Socket连接请求
Socket socket=new Socket(host,port);
//之后会被SockServer的
ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
System.out.println(request);
//写入request 因为写入之后要输出出去 所有是output这边
objectOutputStream.writeObject(request);
//然后刷新一下
objectOutputStream.flush();
//再通过读取来获取对应的response
RPCResponse response = (RPCResponse) objectInputStream.readObject();
return response;
} catch (UnknownHostException e) {
e.printStackTrace();
System.out.println("UnKnowHostException====");
} catch (IOException e) {
e.printStackTrace();
System.out.println("IOException====");
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("ClassNotFound====");
}
return null;
}
}
重构PRCClientProxy
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//这个类动态代理Service类,封装不同的service请求为request对象
//并且持有一个RPCClient对象,负责与客户端的通信
public class PRCClientProxy implements InvocationHandler {
//创建一个RPCClient对象
private RPCClient rpcClient;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里才是真正创建的方法 构建一个request
//interfaceName是服务类的名字 这个名字怎么获取呢
//基本上是不用去管 proxy都是method的问题
//getDeclaringClass是返回 声明这个method对象表示的方法的类的class对象
//就相当于是 A这个类有 a 和b这两个方法,通过方法的getDeclaringClass的getName就可以知道 A这个名字了
//相当于就是知道类 getParameterTypes就是返回方法里面的参数的类型 那params就表示的是参数吧
RPCRequest rpcRequest=RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.params(args).paramsTypes(method.getParameterTypes()).build();
//通过生成的request来进行传输得到response
RPCResponse rpcResponse=rpcClient.sendRequest(rpcRequest);
return rpcResponse.getData();
}
//再写一个getProxy的方法
<T>T getProxy(Class<T> clazz){
Object o= Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},this);
return (T) o;
}
}
使用netty方式进行数据的传输,实现NettyRPCServer,NettyRPCClient
NettyRPCClient
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
/**
* 实现RPCClient接口
*/
public class NettyRPCClient implements RPCClient{
private static final Bootstrap bootstrap;
private static final EventLoopGroup eventLoopGroup;
private String host;
private int port;
public NettyRPCClient(String host,int port){
this.host=host;
this.port=port;
}
//这里就是这些static的初始化
static {
eventLoopGroup=new NioEventLoopGroup();
bootstrap=new Bootstrap();
//然后就是那个创建过程
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
}
/**
* 这里需要操作一下,因为netty的传输都是异步的,你发送request,会立刻返回
* 而不是想要的相应的response
* @param request
* @return
*/
//经过测试 发现不是这部分的错误
@Override
public RPCResponse sendRequest(RPCRequest request) {
try{
ChannelFuture channelFuture=bootstrap.connect(host,port).sync();
Channel channel=channelFuture.channel();
//发送数据
channel.writeAndFlush(request);
channel.closeFuture().sync();
//阻塞的获得结果 通过给channel设计别名 获取特定名字下的channel中的内容(这个在hanlder中设置)
//attributeKey 是线程隔离的,不会有线程安全问题
//实际上不应通过阻塞,可以通过回调函数
AttributeKey<RPCResponse> key= AttributeKey.valueOf("RPCResponse");
//这里就是通过别名获取这个
RPCResponse rpcResponse=channel.attr(key).get();
//这个地方就是null了
System.out.println("NettyRPCClient===="+rpcResponse);
return rpcResponse;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
NettyClientInitializer
package RPCVersion01.MyRPCVersion03.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//要写这个初始化的方法
ChannelPipeline pipeline=socketChannel.pipeline();
//老规矩 为了防止粘包和拆包
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
//这里忘记写了
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(new ClassResolver() {
@Override
public Class<?> resolve(String className) throws ClassNotFoundException {
return Class.forName(className);
}
}));
pipeline.addLast(new NettyClientHandler());
}
}
NettyClientHandler
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.EventExecutorGroup;
public class NettyClientHandler extends SimpleChannelInboundHandler<RPCResponse> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RPCResponse rpcResponse) throws Exception {
//接收到response,给channel设计别名,让sendRequest 里读取response
//这里应该是设计别名
AttributeKey<RPCResponse> key= AttributeKey.valueOf("RPCResponse");
//再把别名放进去 这里是set方法 对应之后的get方法
channelHandlerContext.channel().attr(key).set(rpcResponse);
//再把通道关闭
channelHandlerContext.channel().close();
}
//另一个override的还没写
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Netty服务端
NettyRPCServer 服务端的实现
package RPCVersion01.MyRPCVersion03.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class NettyRPCServer implements RPCServer{
//实现这两个方法
private ServiceProvider serviceProvider;
@Override
public void start(int port) {
//netty服务线程组boss负责建立连接,work负责具体的请求
//NioEventLoopGroup直接就是 EventLoopGroup的具体实现
//boss word就相当于是 parent和childGroup
// boss 或者说是parent是对port进行监听的
//word 或者说是 ChildGroup是用来和Channel绑定的
NioEventLoopGroup bossGroup=new NioEventLoopGroup();
NioEventLoopGroup workGroup=new NioEventLoopGroup();
System.out.println("Netty服务端启动了....");
try{
//启动服务器 是Netty的启动程序 引导类
ServerBootstrap serverBootstrap=new ServerBootstrap();
//初始化
//这里没用到Handler 因为这个调用的是父类的,但childHandler则是自己的,是负责和客户端进行连接的IO交互
serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)
.childHandler(new NettyServerInitializer(serviceProvider));
//上面这些都是初始化,都是为了后面的操作做准备
//同步阻塞 ChannelFuture是异步操作的"代言人”
//.bind是为了通过端口创建,.sync是为了让主线程间接调用wait(),进而实现阻塞的效果
//当调用bind(port)时,是异步的,因此为了保证在初始化完成后才进行操作,避免调用一个初始化未完成的句柄,sync方法是等待异步操作执行完毕.
ChannelFuture channelFuture=serverBootstrap.bind(port).sync();
//死循环监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//应该是关闭吧
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
@Override
public void stop() {
}
}
NettyServerInitializer 初始化
package RPCVersion01.MyRPCVersion03.server;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.AllArgsConstructor;
//他的作用就是生成一个ChannelHandler
/**
* 初始化,主要负责序列化的编码和解码,需要解决netty的粘包问题
*/
@AllArgsConstructor
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
private ServiceProvider serviceProvider;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//意思是 初始化Channel
//创建的这个pipeline是可以把channel依次进去的
ChannelPipeline pipeline=socketChannel.pipeline();
/**
* 后面这两个相当于是Netty自带的格式功能,总的来说就是为了解决粘包和拆包的问题
*/
//消息格式[长度][消息体],解决粘包问题
//LengthFieldBasedFrameDecoder是通用拆包器,几乎所有和长度相关的二进制协议都可以用他来实现
//第一个参数是表示包的最大长度,第二个参数是 长度域的偏移量,0表示无偏移。第三个参数是 长度域的长度
//第五个参数 是4的话,表示获取完一个完整的数据包之后,忽略前面的四个字节
//第四个参数 lengthAdjustment是对包体长度调整的大小
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
//计算过当前待发送消息的长度 写入到前四个字节中
pipeline.addLast(new LengthFieldPrepender(4));
//这里还是使用的Java的序列化方法,netty的自带的解码编码支持传输
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(new ClassResolver() {
@Override
public Class<?> resolve(String className) throws ClassNotFoundException {
return Class.forName(className);
}
}));
pipeline.addLast(new NettyPRCServerHandler(serviceProvider));
}
}
NettyRPCServerHandler netty server具体的handler
package RPCVersion01.MyRPCVersion03.server;
import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.AllArgsConstructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@AllArgsConstructor
public class NettyPRCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
private ServiceProvider serviceProvider;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RPCRequest rpcRequest) throws Exception {
System.out.println("channelRead0===="+rpcRequest);
RPCResponse rpcResponse=getResponse(rpcRequest);
channelHandlerContext.writeAndFlush(rpcResponse);
channelHandlerContext.close();
}
private RPCResponse getResponse(RPCRequest rpcRequest) {
//得到服务名
String interfaceName=rpcRequest.getInterfaceName();
//得到服务端相应服务实现类
Object service = serviceProvider.getService(interfaceName);
//反射调用方法
Method method=null;
try{
method=service.getClass().getMethod(rpcRequest.getMethodName(),rpcRequest.getParamsTypes());
Object invoke = method.invoke(service, rpcRequest.getParams());
return RPCResponse.success(invoke);
} catch (NoSuchMethodException |IllegalAccessException |InvocationTargetException e) {
System.out.println("方法执行错误");
e.printStackTrace();
return RPCResponse.fail();
}
}
//一个overrider
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端和服务端的测试代码
服务端的测试代码 TestServe
package RPCVersion01.MyRPCVersion03.server;
import RPCVersion01.MyRPCVersion03.service.BlogService;
import RPCVersion01.MyRPCVersion03.service.BlogServiceImpl;
import RPCVersion01.MyRPCVersion03.service.UserService;
import RPCVersion01.MyRPCVersion03.service.UserServiceImpl;
public class TestServer {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
// BlogService blogService=new BlogServiceImpl();
//再使用Map把服务添加进去
/* Map<String,Object> serviceProvide=new HashMap<>();
//暴露两个服务接口 在RPCServer中加入一个HashMap
serviceProvide.put("PRCVersion01.UserService",userService);
serviceProvide.put("PRCVersion01.BlogService",blogService);*/
//因为直接写了一个ServiceProvider所以可以进行修改
ServiceProvider serviceProvider = new ServiceProvider();
serviceProvider.provideServiceInterface(userService);
// serviceProvider.provideServiceInterface(blogService);
//MyRPC 版本2的 RPCServer RPCServer = new SimpleRPCRPCServer(serviceProvider) 会报错呀
//这里修改了一下 不知道有没有用
// RPCServerVersion2 rpcServerVersion2=new SimpleRPCServerVersion2(serviceProvider.getInterfaceProvider());
//线程池版
RPCServer rpcServerVersion2=new NettyRPCServer(serviceProvider);
rpcServerVersion2.start(8899);
}
}
服务端的测试代码 TestClient
package RPCVersion01.MyRPCVersion03.client;
import RPCVersion01.MyRPCVersion03.common.User;
import RPCVersion01.MyRPCVersion03.service.UserService;
public class TestClient {
public static void main(String[] args) {
//创建一个client
System.out.println("TestClient开始了====");
RPCClient nettyRPCClient=new NettyRPCClient("127.0.0.1",8899);
// System.out.println("nettyRPCClient===="+nettyRPCClient);
//把客户端传入到代理客户端中
RPCClientProxy rpcClientProxy=new RPCClientProxy(nettyRPCClient);
// System.out.println("rpcClientProxy===="+rpcClientProxy);
//代理客户端根据不同的服务,获得一个代理类
//经过测试 是这个部分出现了问题
UserService proxy = rpcClientProxy.getProxy(UserService.class);
//问题就处在这 proxy不能先调用的
// System.out.println("proxy===="+proxy);
//调用方法
User userByUserId = proxy.getUserByUserId(10);
System.out.println("userByUserId===="+userByUserId);
}
}
运行结果
代码问题
这里关于动态代理,出现了一个问题,让我代码一直运行不起来
经过debug之后,发现错误出现这个地方
正确的代码部分:
错误的代码:
也就是多写了一个
System.out.println("proxy===="+proxy);
在debug的情况下看看到了这部分会出现什么情况
首先
RPCClientProxy rpcClientProxy=new RPCClientProxy(nettyRPCClient);
这一步只是进行了 RPCClientProxy部分的注解@AllArgsConstructor,也就是进行了创建构造函数
这个时候下面的方法都还没开始调用
然后到这一步
调用了Proxy下的 getproxy方法
这个时候才创建好一个proxy
然后是这一步
首先他会报出一个异常:
Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate com.sun.proxy.$Proxy0.toString()
https://blog.csdn.net/weixin_43979923/article/details/107151410
但这个异常是可以暂时忽略不计的
进入这个之后 就先进入到了RPCReuqest的build方法,也就是request的初始化
随即进入到invoke当中
问题就在于 这个时候的proxy还没有指定对应的方法
这样子生成的request是不带任何参数的
然后调用了 RPCClient的sendRequest
所以导致这个时候输出的是null了
而正常情况下
到这一步的时候
首先进行RPCRequest的builder
然后进入到invoke当中
注意,这个时候的method就是有方法的
这样子生成的request就是有值的
RPCRequest(interfaceName=RPCVersion01.MyRPCVersion03.service.UserService, methodName=getUserByUserId, params=[10], paramsTypes=[class java.lang.Integer])
并且这样子输出的response也不是null 了
所以问题还是出在了 这个输出上面
总结
这个版本完成了客户端的重构,使之能够支持多种版本客户端的扩展,实现了RPCClient接口
并使用了Netty实现了客户端和服务端的通信
痛点
使用了Java自带的序列化方式,Java序列化写入不仅是完整的类名,也包含整个类的定义,包含所有被引用的类,不够通用,不够高效