RPC
RPC(Remote Procedure Call,远程过程调用)一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地资源一样,通过网络传输去访问远端系统资源。(此段百度的,不管来自哪儿,弄明白就是自己的)
RPC框架实现的架构原理:
下面来展示下本人拙劣的画图技术
Client:客户端,服务消费者,负责发起RPC调用,通过远程代理对象调用远程服务。 Serialization/Deserilization:负责对RPC调用通过网络传输的内容进行序列化与反序列化。 Stub Proxy:代理对象,屏蔽RPC调用过程中复杂的网络处理逻辑,使得RPC调用透明化,能够保持与本地调用一样的代码风格。 TransPort:作为RPC框架底层的通信传输模块,一般通过Socket在客户端与服务端之间传递请求与应答信息。
我所理解的原理过程(不对的地方求指教):
- 服务消费方(client)调用以本地调用方式调用服务;
- stub Proxy接收到调用后负责将方法、参数等组装成可以序列化的Invocation对象;
- stub Proxy找到服务地址,建立socket连接并将消息发送到服务端;
- server端将接收到的数据反序列化,交给stub Proxy处理;
- server端将stub收集到的调用信息分发给具体的方法去执行,这里并不是真正的client去执行该方法,而是在server端通过反射生成的client动态对象去执行;
- RPC接口执行完毕,返回执行结果,思路类似。
RPC的简易实现
技术方案
采用的是Socket通信、动态代理与反射与Java原生的序列化,由于能力有限,只会基于BIO来实现,以后会尝试用zookeeper、netty、redis等来实现!
具体实现
1、服务提供者提供的服务实现
package com.ooliuyue.simplRpc.test;
/**
* @Auther: ly
* @Date: 2019/3/14 11:45
*/
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "hello " + name;
}
}
复制代码
2、客户端获取远程服务,服务端暴露自己的服务,Socket来完成通信工作(注释比较详细,一目了然)
package com.ooliuyue.simplRpc.rpc;
import com.ooliuyue.simplRpc.utils.StringUtils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: ly
* @Date: 2019/3/13 17:53
*/
public class SimpleRpc {
/**
*将指定服务暴露出来,供客户端远程调用
* @param service 需要暴露的服务
* @param port 暴露的端口号
* @throws Exception
*/
public static void export(final Object service,int port) throws Exception{
if (service == null)
throw new IllegalArgumentException("service not null");
if (port < 0 || port > 65535)
throw new IllegalArgumentException("port in [0,65535]");
System.out.println("Export Service:" + service.getClass().getName() + " port:" + port );
/**
* Socket编程步骤
* 服务器端创建ServerSocket对象,调用accept方法返回Socket对象
* 客户端创建Socket对象,通过端口连接到服务器
* 客户端、服务器端都使用Socket中的getInputStream方法和getOutputStream方法获得输入流和输出流,
* 进一步进行数据读写操作
*/
/**
* socket在服务器端上的使用
* 1.getInputStream方法得到的是一个输入流,服务端的Socket对象上的getInputStream方法得到的输入流其实就是从客户端发送给服务器端的数据流。
* 2.getOutputStream方法得到的是一个输出流,服务端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给客户端的数据。
*/
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
final Socket socket = serverSocket.accept();
new Thread(() -> {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
try {
//将客户端发送给服务端的数据流反序列化成对象
input = new ObjectInputStream(socket.getInputStream());
String methodName = input.readUTF();
Class>[] paramTypes = (Class>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Method method = service.getClass().getMethod(methodName, paramTypes);
//反射调用服务实现,获取执行结果
Object result = method.invoke(service, arguments);
//将执行结果反序列化,然后发送给客户端
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
if (output != null)
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
/**
* 获取远程服务
* @param interfaceClass 服务接口class
* @param host 远程IP地址
* @param port 远程端口号
* @param 指定接口的实例
* @return
* @throws Exception
*/
/**
*
* socket在客户端上的使用
* 1.getInputStream方法可以得到一个输入流,客户端的Socket对象上的getInputStream方法得到输入流其实就是从服务器端返回的数据。
* 2.getOutputStream方法得到的是一个输出流,客户端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给服务器端的数据。
*/
public static T getRemoteService(final Class interfaceClass,final String host,final int port)
throws Exception {
verifyGetRemoteService(interfaceClass,host,port);
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class>[]{interfaceClass},(Object proxy, Method method, Object[] args) ->{
Socket socket = new Socket(host,port);
System.out.println("get remote service :" + interfaceClass.getName() + " from " + host + ":" + port);
//将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//result就是从服务器返回的数据
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
Object result = input.readObject();
return result;
});
}
private static void verifyGetRemoteService(final Class interfaceClass, final String host, final int port) {
if (interfaceClass == null)
throw new IllegalArgumentException("interfaceClass not null");
if (!interfaceClass.isInterface())
throw new IllegalArgumentException("interfaceClass not a interface");
if (!StringUtils.isNotBlank(host))
throw new IllegalArgumentException("host not blank");
if (port < 0 || port > 65535)
throw new IllegalArgumentException("port in [0,65535]");
}
}
复制代码
3、测试 首先服务端暴露服务,运行main
package com.ooliuyue.simplRpc.test;
/**
* @Auther: ly
* @Date: 2019/3/14 11:43
*/
import com.ooliuyue.simplRpc.rpc.SimpleRpc;
/**
* 服务提供者
*/
public class Server {
public static void main(String[] args) throws Exception {
HelloService helloService = new HelloServiceImpl();
SimpleRpc.export(helloService,1000);
}
}
复制代码
运行结果: Export Service:com.ooliuyue.simplRpc.test.HelloServiceImpl port:1000
然后客户端进行调用,运行main
package com.ooliuyue.simplRpc.test;
import com.ooliuyue.simplRpc.rpc.SimpleRpc;
/**
* @Auther: ly
* @Date: 2019/3/14 11:43
*/
public class Client {
public static void main(String[] args) throws Exception {
HelloService remoteService = SimpleRpc.getRemoteService(HelloService.class, "127.0.0.1", 1000);
String str = remoteService.hello("王大锤");
System.out.println(str);
}
}
复制代码
运行结果 get remote service :com.ooliuyue.simplRpc.test.HelloService from 127.0.0.1:1000 hello 王大锤