一、RPC是什么
RPC,全称为Remote Procedure Call,即远程过程调用。它允许像调用本地服务一样调用远程服务。
个人感觉,与http类似,都需要本地给远程服务器发报文,获取返回信息,因此记录下两者的区别。
RPC与http区别:
http://www.ccutu.com/244407.html
RPC可以基于TCP协议,也可以基于HTTP协议;
RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。
个人理解,RPC在公司内部的分布式系统中比直接用http方式具有优势,网络传输效率高,具有额外的适合分布式的一些功能(如包含负载均衡策略等),所以分布式系统内部会使用RPC。
二、Netty是什么
Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
Netty用到了NIO(同步非阻塞)。
Netty、Servlet都属于Web框架:https://blog.csdn.net/dengkane/article/details/84720822
因此记录下Netty与Tomcat(Servlet)的区别:https://www.cnblogs.com/pangguoming/p/9353536.html
Servlet是基于Http协议的;Netty可以通过编程自定义各种协议,因为netty能够通过编程自己来编码/解码字节流。
个人感觉,Netty既用到了异步非阻塞,也用到了同步非阻塞。为了避免搞混,在下方简要记录一下。
NIO基本概念:
1、缓冲区Buffer
Buffer是一个对象,它包含了一些要写入的或者要读出的数据。
缓冲区Buffer本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
数据是从通道读入缓冲区,从缓冲区写入到通道中的。
2、通道Channel
Channel是一个通道,可以通过它进行数据的读取和写入。
数据在channel中可以进行双向的流通,通道可以用于读、写、或同时进行读写。
3、多路复用器 Selector
selector会不断轮询注册在其上的Channel,如果某个Channel上有新的Tcp接入,或者有发生读写事件,这个Channel就会处于就绪状态,可以被Selector轮询出来,然后通过selectedKey获取就绪的Channel集合,以便进行后续的IO操作。
一个多路复用器可以同时轮询多个Channel。
Netty用到了NIO,个人理解:
首先,NIO模型中,每个客户端Socket请求服务器时,不是直接与服务器建立一个链接,而是与Selector建立一个链接,这样就减小了服务器的压力。(Netty中实现了Selector)
然后,Selector会轮询与客户端的链接,如果有请求,就转发给服务器,并将服务器返回的结果转发给客户端。
Netty可以有多个Selector,每个Selector与服务器建立一个链接即可,减小了服务器的压力。
所以Netty用到了NIO。
Netty的所有IO操作都是异步非阻塞的,个人理解:
首先,IO操作异步是说,客户端或Netty会对将数据写入Channel,或从Channel读取数据,这是异步的,进行IO后不会立刻得到结果,而是实际进行IO的部件在完成后,通过状态、通知和回调来通知调用者。
异步的好处是不会造成阻塞,在高并发情形下会更稳定和更高的吞吐量。
三、Netty实现RPC的代码
通过上方的分析发现,RPC协议适合用于分布式系统;Netty是NIO网络应用程序框架,可以开发客户端程序与服务器程序。
所以这两个组合起来,就能开发分布式系统的客户端程序与服务器程序了。
代码样例分3部分:
*使用下方的代码时,import对应的目录结构需要自己调整下。
rpc-common:其中定义了一些客户端与服务器端都会用到的JavaBean类与接口类。
要实现RPC,最重要的就是要定义一个客户端与服务器公用的接口类;
客户端调用这个接口类中的方法,看起来是直接获得了处理结果,实际上是给服务器发送请求、服务器处理后返回的结果;
服务器则需要实现这个接口类,编写实际执行的方法,等待客户端请求。
rpc-consumer:这个是rpc客户端的代码。
rpc-provider:这个是rpc服务器的代码。
1.rpc-common部分
(1)IUserService.java,这个是客户端与服务器都会用到的接口类。
public interface IUserService { public String sayHello(String smg);}
(2)RpcRequest.java,这个是自定义了一个Request对象,这个对象中包含客户端要调用的方法的名称、参数等信息,客户端发送这个对象给服务器,服务器解析这个对象。
public class RpcRequest{ //本次请求的ID,可以自定义一个 private String requestId; //客户端准备调用服务端的类名 private String className; //对应类中的方法名 private String methodName; //调用这个方法时,要传递的参数类型的数组,按顺序 private Class>[] parameterTypes; //调用这个方法时,要传递的参数数组,按顺序 private Object[] parameters; public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Class>[] getParameterTypes() { return parameterTypes; } public void setParameterTypes(Class>[] parameterTypes) { this.parameterTypes = parameterTypes; } public Object[] getParameters() { return parameters; } public void setParameters(Object[] parameters) { this.parameters = parameters; }}
(3)Serializer.java,这个类是自定义的序列化/反序列化类,用来将java对象(RpcRequest对象)序列化和反序列化,然后就能在网络上传输了(也就是转为字节数组,用对象流传输)。
import java.io.IOException;public interface Serializer { //将java对象转换为二进制,接口方法 //传入对象,传出字节数组 byte[] serialize(Object object) throws IOException; //将二进制转换成java对象,接口方法 //传入字节数组与类的类型,返回该类型的对象 T deserialize(Class clazz, byte[] bytes) throws IOException;}
(4)JSONSerializer.java,这个类是Serializer.java的实现类,采用了json方式进行序列化/反序列化。
import com.alibaba.fastjson.JSON;public class JSONSerializer implements Serializer{ public byte[] serialize(Object object) { return JSON.toJSONBytes(object); } public T deserialize(Class clazz, byte[] bytes) { return JSON.parseObject(bytes, clazz); }}
(5)pom.xml,这个是rpc-common用到的依赖。同时要注意下groupId与artifactId,这个在后续的rpc-consumer与rpc-provider中会用到。
<groupId>com.testgroupId> <artifactId>rpc-commonartifactId> <version>1.0-SNAPSHOTversion> <dependencies> <dependency> <groupId>io.nettygroupId> <artifactId>netty-allartifactId> <version>4.1.16.Finalversion> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.41version> dependency> dependencies>
2.rpc-consumer部分(rpc客户端)
(1)pom.xml,用到的依赖,其中用到了rpc-common的jar包。
<groupId>org.examplegroupId> <artifactId>rpc-consumerartifactId> <version>1.0-SNAPSHOTversion> <dependencies> <dependency> <groupId>com.testgroupId> <artifactId>rpc-commonartifactId> <version>1.0-SNAPSHOTversion> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.41version> dependency> dependencies>
(2)ConsumerBoot.java,客户端的启动类。
import com.test.client.RPCConsumer;import com.test.service.IUserService;public class ConsumerBoot { //这个字段的意思是,要调用UserService类中的sayHello方法 private static final String PROVIDER_NAME = "UserService#sayHello#"; public static void main(String[] args) throws InterruptedException { //1.创建代理对象 IUserService service = (IUserService) RPCConsumer.createProxy(IUserService.class, PROVIDER_NAME); //2.循环给服务器写数据 while (true){ //调用这个方法后,实际上就给服务器发请求并获取返回结果了,不过看起来就像调用本地方法一样。这就是rpc。 String result = service.sayHello("are you ok !!"); System.out.println(result); Thread.sleep(2000); } }}
(3)RPCConsumer.java,客户端消费者类,有创建代理对象等一系列功能。
import com.test.bean.RpcRequest;import com.test.encoder.RpcEncoder;import com.test.handler.UserClientHandler;import com.test.serializer.JSONSerializer;import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.ChannelPipeline;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import io.netty.handler.codec.string.StringDecoder;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 客户端消费者类 */public class RPCConsumer { //自定义的请求id,从0开始自增 private static int requestId = 0; //1.创建一个线程池对象 -- 它要处理我们自定义事件 private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //2.声明一个自定义事件处理器 UserClientHandler private static UserClientHandler userClientHandler; //3.编写方法,初始化客户端 ( 创建连接池 创建bootStrap 设置bootstrap 连接服务器) public static void initClient() throws InterruptedException { //1) 初始化UserClientHandler userClientHandler = new UserClientHandler(); //2)创建连接池对象 EventLoopGroup group = new NioEventLoopGroup(); //3)创建客户端的引导对象 Bootstrap bootstrap = new Bootstrap(); //4)配置启动引导对象 bootstrap.group(group) //设置通道为NIO .channel(NioSocketChannel.class) //设置请求协议为TCP .option(ChannelOption.TCP_NODELAY,true) //监听channel 并初始化 .handler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel socketChannel) throws Exception { //获取ChannelPipeline ChannelPipeline pipeline = socketChannel.pipeline(); //设置如何编码,使用自定义json序列化类编码 pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer())); //设置如何解码 pipeline.addLast(new StringDecoder()); //添加自定义事件处理器 pipeline.addLast(userClientHandler); } }); //5)连接服务端 bootstrap.connect("127.0.0.1",8999).sync(); } //4.编写一个方法,使用JDK的动态代理创建对象 // serviceClass 接口类型,根据哪个接口生成子类代理对象; providerParam : "UserService#sayHello#" public static Object createProxy(Class> serviceClass, final String providerParam){ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, new InvocationHandler() { public Object invoke(Object o, Method method, Object[] objects) throws Throwable { //封装好rpcRequest RpcRequest rpcRequest = getRpcRequest(providerParam, objects[0]); //1)初始化客户端client if(userClientHandler == null){ initClient(); } //2)给UserClientHandler 设置param参数 userClientHandler.setParam(rpcRequest); //3).使用线程池,开启一个线程处理处理call() 写操作,并返回结果 Object result = executorService.submit(userClientHandler).get(); //4)return 结果 return result; } }); } //封装rpcRequest对象的方法 public static RpcRequest getRpcRequest(String providerParam, Object object){ RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setRequestId(String.valueOf(requestId)); requestId++; rpcRequest.setClassName(providerParam.split("#")[0]); rpcRequest.setMethodName(providerParam.split("#")[1]); rpcRequest.setParameterTypes(new Class[]{String.class}); //are you ok !! rpcRequest.setParameters(new Object[]{object}); return rpcRequest; }}
(4)RpcEncoder.java,上方设置pipeline如何编码时用到了。
import com.test.serializer.Serializer;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;//注意继承的是MessageToByteEncoder类public class RpcEncoder extends MessageToByteEncoder { private Class> clazz; private Serializer serializer; public RpcEncoder(Class> clazz, Serializer serializer) { this.clazz = clazz; this.serializer = serializer; } @Override protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception { if (clazz != null && clazz.isInstance(msg)) { byte[] bytes = serializer.serialize(msg); byteBuf.writeBytes(bytes); } }}
(5)UserClientHandler.java,自定义事件处理器。
import com.test.bean.RpcRequest;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import java.util.concurrent.Callable;/** * 自定义事件处理器 */public class UserClientHandler extends ChannelInboundHandlerAdapter implements Callable { //1.定义成员变量 private ChannelHandlerContext context; //事件处理器上下文对象 (存储handler信息,写操作) private String result; // 记录服务器返回的数据 private RpcRequest rpcRequest; //将要发送给服务器的数据(自定义的类对象) //2.实现channelActive 客户端和服务器连接时,该方法就自动执行 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //初始化ChannelHandlerContext this.context = ctx; } //3.实现channelRead 当我们读到服务器数据,该方法自动执行 @Override public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //将读到的服务器的数据msg ,设置为成员变量的值 result = msg.toString(); //唤醒线程,线程才会继续执行下方的return result; notify(); } //4.将客户端的数写到服务器 public synchronized Object call() throws Exception { //context给服务器写数据 context.writeAndFlush(rpcRequest); //使用wait阻塞自己,等待服务器返回数据 wait(); //在上方notify()后,才继续执行,return服务器返回的信息 return result; } //5.设置参数的方法 public void setParam(RpcRequest param){ this.rpcRequest = param; }}
3.rpc-provider部分(rpc服务器)
(1)pom.xml,依赖信息,其中用到了rpc-common的jar包;服务器写成了springboot项目,因此还有springboot相关依赖。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.3.3.RELEASEversion> <relativePath/> parent> <groupId>com.testgroupId> <artifactId>rpc-providerartifactId> <version>1.0-SNAPSHOTversion> <name>myspringbootname> <description>Demo project for Spring Bootdescription> <properties> <java.version>8java.version> <mybatis-springboot-starter.version>1.3.0mybatis-springboot-starter.version> properties> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> <dependency> <groupId>com.testgroupId> <artifactId>rpc-commonartifactId> <version>1.0-SNAPSHOTversion> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.41version> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build>project>
(2)ServerBootstrap.java,rpc服务端的启动类。
import com.test.service.IUserService;import com.test.service.UserServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class ServerBootstrap { @Autowired @Qualifier("UserService") IUserService userServiceImpl; public static void main(String[] args) throws InterruptedException { //启动spring容器 SpringApplication.run(ServerBootstrap.class, args); //启动服务器 UserServiceImpl.startServer("127.0.0.1",8999); }}
(3)UserServiceImpl.java,实现类,客户端相当于实际调用了这个类中的方法。(客户端给服务器发请求,服务器调用方法,然后将结果返回给客户端。)
import com.test.bean.RpcRequest;import com.test.decoder.RpcDecoder;import com.test.handler.UserServiceHandler;import com.test.serializer.JSONSerializer;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelPipeline;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Service;@Service("UserService")public class UserServiceImpl implements IUserService,ApplicationContextAware { //spring容器对象,通过实现ApplicationContextAware获取到 private static ApplicationContext applicationContext; //将来客户端要远程调用的方法 public String sayHello(String msg) { System.out.println("客户端发来数据:"+msg); return "服务器返回数据 : "+msg+"[success]"; } //创建一个方法启动服务器 public static void startServer(String ip , int port) throws InterruptedException { //1.创建两个线程池对象 NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workGroup = new NioEventLoopGroup(); //2.创建服务端的启动引导对象 ServerBootstrap serverBootstrap = new ServerBootstrap(); //3.配置启动引导对象 serverBootstrap.group(bossGroup,workGroup) //设置通道为NIO .channel(NioServerSocketChannel.class) //创建监听channel .childHandler(new ChannelInitializer() { protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception { //获取管道对象 ChannelPipeline pipeline = nioSocketChannel.pipeline(); //给管道对象pipeLine 设置编码方式 pipeline.addLast(new StringEncoder()); //给管道对象pipeLine 设置解码方式,使用自定义json方式解码 pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); //把自定义的一个ChannelHander添加到通道中 pipeline.addLast(new UserServiceHandler(applicationContext)); } }); //4.绑定端口 serverBootstrap.bind(8999).sync(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
(4)UserServiceHandler.java,自定义业务处理器,包含处理从客户端收到的信息的方法,以及返回给客户端信息的方法。
import com.test.bean.RpcRequest;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import org.springframework.context.ApplicationContext;import java.lang.reflect.Method;/** * 自定义的业务处理器 */public class UserServiceHandler extends ChannelInboundHandlerAdapter { //spring容器对象 private ApplicationContext applicationContext; //构造方法,给spring容器对象赋值 public UserServiceHandler(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } //当客户端发来数据时,该方法会被调用 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //将客户端发来的对象强转为RpcRequest。(约定好的,本来就是RpcRequest) RpcRequest rpcRequest = (RpcRequest)msg; //从spring容器中获得要调用的类,名字是UserService,实际的类型是UserServiceImpl Object bean = applicationContext.getBean(rpcRequest.getClassName()); //用反射获得UserServiceImpl对象 //com.test.UserServiceImpl //要注意这个类的路径 Class c = Class.forName(bean.getClass().getPackage().getName()+ "." + rpcRequest.getClassName() +"Impl"); //然后用反射获得这个类的方法 //sayHello Method method = c.getDeclaredMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes()); System.out.println("当前请求号:"+rpcRequest.getRequestId()); //注意method中传入的是spring容器中取出的bean,而不是c.newInstance() //are you ok!! Object invoke = method.invoke(bean, rpcRequest.getParameters()); //把调用实现类的方法获得的结果写到客户端 ctx.writeAndFlush(invoke); }}
(5)RpcDecoder.java,自定义json格式解码用到的类。
import com.test.serializer.Serializer;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ByteToMessageDecoder;import java.util.List;//注意解码继承的是ByteToMessageDecoderpublic class RpcDecoder extends ByteToMessageDecoder { private Class> clazz; private Serializer serializer; public RpcDecoder(Class> clazz, Serializer serializer) { this.clazz = clazz; this.serializer = serializer; } @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { if (clazz != null && serializer != null) { byte[] bytes = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(bytes); //如果不想改变byteBuf的读写位置,可以用getBytes //int readerIndex = byteBuf.readerIndex(); //byteBuf.getBytes(readerIndex, bytes); //这个就是RpcRequest对象 Object obj = serializer.deserialize(clazz, bytes); //注意这里只添加了一个RpcRequest对象 //如果list中添加了多个元素,则handler中的channelRead(ChannelHandlerContext ctx, Object msg)方法会被多次调用,按顺序一次给msg传入一个元素 list.add(obj); } }}
以上,Netty实现RPC的代码样例就完成了。