Rpc框架深入实现
前期提要
RPC即远程过程调用,我们不管用什么语言,比如c,java,都有本地过程或者本地方法,也就是一般来说,都是自己机器上的代码在使用自己机器上的代码,但是如果自己机器受不了,也可以交由远程服务去计算,将我们的远程服务发布出来,然后自己本机调用那个服务即可了。在分布式环境中经常使用,其实也可以将分布式应用看成一台机器,一台机器内本地调用,只是真实的本地机器是通过CPU,内存,磁盘,总线等去加载数据计算的,而分布式应用这种“一台机器”内部是使用网络去加载数据通信计算的。当然你像Redis也可以不使用RPC而是直接轻量的去使用,这里想熟悉熟悉研究研究RPC主要是因为像Hadoop,Spark,Flink,Kafka,Zookeeper等大量的分布式应用底层通信都有用到RPC,所以从这里就开始就先自己一步一步实现一个RPC框架,再去研究一些通信应用,像Netty,Dubbo等等
在了解RPC之前,必须掌握几个基础:
动态代理
反射
序列化与反序列化
网络传输
Demo1:简易版RPC框架-无注册中心
Demo1模型
我们的RPC先简化成传统的客户端和服务端,客户端就是分布式应用中我们常用的调用服务,而服务端就是我们常常发布了一个服务,等待被调用然后执行。
- 第一步:我们服务端首先需要发布一个服务,等待客户端的调用。其实我们平常开发SpringBoot,不也是发布一个服务,通过端口转发或者访问,然后等待返回结果,模型道理是一样的,只是实现不一样。
- 第二步:有了服务端,我们客户端就可以去调用了,在我们客户端,不用知道服务端的那个服务的具体实现逻辑,只需要知道服务端是干什么的就可以了,这样你就可以想到java里面的接口了,接口不就是定义了一系列行为吗,让我们知道是干什么的。所以我们只需要根据这个接口去生成一个代理对象即可了。为什么要生成一个代理对象,这里是动态代理,毕竟如果不使用,客户端每次去调用一个服务,比如sayHello,我就要吧相应的sayHello,参数名,所实现的接口去告诉服务端,这样虽然是我们的代码直接和服务器通信,但是如果又想调用sayInt,那么就会造成我们又要写代码将这个名字传过去,就会出现大量的代码冗余。你有可能会想到,可以将公共的访问服务提取出来,就是统一的利用反射,将method,参数啥的传给服务端,这样服务端就可以根据这个东西去使用了。这样想没错,这个就是动态代理的思想,利用java中的Proxy自动实现一个代理类来实现我们的服务接口,然后调用的时候就将方法和参数发送过去就行了。
- 第三步:就像第二步说的那样,有了动态代理,将我们用户代码的实现接口,调用方法和参数,送给代理对象,代理对象可能经过包装或者序列化传输给服务端。
- 第四步:服务端受到了客户端的请求,经过反序列化,将我们的接口,方法名,和参数提取出来,然后通过反射调用那个方法
- 第五步:服务端将调用结果返回给客户端,客户端操作调用结果
整个逻辑不是很复杂,我们这一节可以根据这个模型实现一个简单的RPC,但是呢,我们后期还需要考虑协议,网络,序列化和反序列化,负载均衡等等,到时候再去研究,先简单看看这个怎么做
Demo1:服务接口
这个就是我们远程服务的接口,就是我们客户端也会存一份,来了解这个接口可以干嘛。
public interface IHelloServie {
public String sayHello(String name);
public String sayInt(String info, Integer port);
}
Demo1:服务接口实现
这个保存在服务端,用来发布服务
public class HelloServiceImpl implements IHelloServie {
@Override
public String sayHello(String info) {
return "你好:"+info;
}
@Override
public String sayInt(String info, Integer port) {
return "info:"+info+"port:"+port;
}
}
Demo1:RpcRequest
这个是用来在服务器和客户端传输数据的对象,包装了类名,方法名和参数,注意这里简单的实现了序列化接口
/**
* 用来序列化,告诉服务器端需要做什么
* 统一传输对象
* */
public class RpcRequest implements Serializable {
/**
* 类名,方法名,和参数
* */
private String className;
private String methodName;
private Object[] parameters;
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 Object[] getParameters() {
return parameters;
}
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
}
Demo1:RpcServer和ProcessHandler
RpcServer用来统一管理,实现的是一个线程池,用来发布服务,当然这里线程池使用不合理,只是随便想的,不用太在意,生产环境肯定不会这样用
public class RpcServer {
/**线程池*/
private static final ExecutorService executor = Executors.newCachedThreadPool();
public void publisher(final Object service,int port){
/**启动服务监听*/
try {
ServerSocket serverSocket = new ServerSocket(port);
while(true){
Socket socket = serverSocket.accept();
executor.execute(new ProcessHandler(socket,service));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ProcessHandler用来真正的服务调用,这里很简单,不就是一个线程嘛,我们的RpcServer在监听,如果来了一个,就启动一个线程,这里异步的等待监听了。我们线程里来接受那个RpcRequest,它是序列化过的。我们这里读出来就会反序列化。然后解析这个类,将其中的类名,方法名,和参数解析出来了,然后去找当前这个服务的方法和参数对应的方法即可。通过反射调用返回去。
public class ProcessHandler implements Runnable {
private Socket socket;
/***
* 服务端发布的服务
* */
private Object service;
/**
* 构造方法,顺便保存socket和服务service
* */
public ProcessHandler(Socket socket,Object service){
this.socket = socket;
this.service = service;
}
@Override
public void run() {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(socket.getInputStream());
/**反序列化,将网络输入流的序列化对象反序列化成RpcRequest*/
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
Object result = invoke(rpcRequest);
/**将结果返回给客户端*/
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(result);
objectOutputStream.flush();
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} finally {
if(objectInputStream!=null){
try {
/**关闭输入流*/
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/***
* 反射调用
* */
private Object invoke(RpcRequest rpcRequest) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
System.out.println("服务端开始调用了......");
System.out.println("调用的为:"+rpcRequest.getClassName()+" "+rpcRequest.getMethodName()+" ");
Object[] parameters = rpcRequest.getParameters();
/**
* 两种参数类型的选择方式,可以随便选择,第二种要使用jdk1.8以上
* */
Class[] parameterTypes = new Class[parameters.length];
Class<?>[] parameTypes = Arrays.stream(parameters).map(Object::getClass).toArray(Class<?>[]::new);
for(int i=0,length = parameters.length;i<length;i++){
parameterTypes[i] = parameters[i].getClass();
System.out.println(parameterTypes[i].getName());
}
// for(int i=0;i<parameTypes.length;i++) System.out.println(parameTypes[i]);
/**开始调用方法*/
Method method = service.getClass().getMethod(rpcRequest.getMethodName(),parameterTypes);
return method.invoke(service,parameters);
}
}
Demo1:RpcClientProxy客户端动态代理生成代理对象
这里使用了Proxy的newProxyInstance方法来动态生成一个服务接口的代理实现类,用来传输数据。
/**
* 客户端获取代理对象
* 客户端代理
* */
public class RpcClientProxy {
public <T> T clientProxy(final Class<T> interfaceClass,final String host,final int port){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new RemoteInvocationHandler(host,port));
}
}
上面使用的RemoteInvocationHandler在这里,主要是来统一管理调用,当我们的用户代码使用代理对象时,就会统一使用这个invoke方法。
public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port;
/**发起客户端和服务器的远程调用,调用客户端的信息进行传输*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//构建一个Rpc传输对象RpcRequest,里面包括了类名,方法名和参数
System.out.println("proxy:"+proxy.getClass().getName());
System.out.println("method:"+method.getName());
System.out.println(args[0]);
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setClassName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParameters(args);
System.out.println(args[0].getClass());
/**将这个Rpc调用对象通过tcp传输过去*/
TcpTransport tcpTransport = new TcpTransport(host,port);
return tcpTransport.send(rpcRequest);
}
/**构造方法*/
public RemoteInvocationHandler(String host,int port){
this.host = host;
this.port = port;
}
}
Demo1:TcpTransport
这里简单模拟了Socket传输而已,没多大意义
/**
* Socket传输
* */
public class TcpTransport {
private String host;
private int port;
public TcpTransport(String host,int port){
this.host = host;
this.port = port;
}
private Socket newSocket(){
DatePrint.printdate();
System.out.println("准备创建Socket连接,host:"+host+" Port:"+port);
Socket socket = null;
try {
socket = new Socket(host,port);
} catch (IOException e) {
e.printStackTrace();
}
return socket;
}
public Object send(RpcRequest rpcRequest){
/**建立Socket连接*/
Socket socket = null;
socket = newSocket();
/**向这个socket的输出流写入序列化的Rpc请求对象*/
ObjectOutputStream outputStream = null;
try {
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(rpcRequest);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
/**输入流,接受socket返回的结果*/
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(socket.getInputStream());
Object result = inputStream.readObject();
inputStream.close();
outputStream.close();
return result;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
Demo1测试:服务器发布服务
public class ServerDemo {
public static void main(String[] args) {
RpcServer rpcServer = new RpcServer();
rpcServer.publisher(new HelloServiceImpl(),12345);
}
}
Demo1测试:客户端发送数据
public class ClientDemo {
public static void main(String[] args) {
RpcClientProxy proxy = new RpcClientProxy();
IHelloServie helloServie = proxy.clientProxy(IHelloServie.class,"localhost",12345);
String name = helloServie.sayHello("ongbo");
System.out.println(name);
helloServie.sayInt("ongboinfo",1234);
}
}