RPC是Remote Procedure Call(远程过程调用)的简写,即实现调用远程计算机上的方法,就像调用本地方法一样。
分布式环境下各个服务之间的协作,必然会用到RPC的思想。
一般来讲,RPC框架会包含3部分:
- 服务提供者(ServiceProvider)
- 注册中心(RegistryCentre)
- 服务消费者(ServiceComsumer)
RPC整个过程可以概括如下:
- 定义好统一的请求体(RpcRequest)和返回体(RpcResponse);
- 定义好服务接口;
- 服务提供者完成接口的实现,并将接口通过TCP或HTTP的方式暴露出去,同时将服务的元信息提交到注册中心;
- 服务消费者从注册中心拿到服务提供者的信息,然后提交请求,并获得返回。
考虑到文章篇幅大小,本文仅介绍如何开发服务提供者和服务消费者,服务消费者采用直连服务提供者的方式实现RPC。
请求体(RpcRequest)和返回体(RpcResponse)
请求体(RpcRequest)
public class RpcRequest implements Serializable {
// 方法名称
private String methodName;
// 参数列表
private Object[] parameters;
// 参数类型
private Class<?>[] parameterTypes;
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;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
@Override
public String toString() {
return "RpcRequest{" +
"methodName='" + methodName + '\'' +
", parameters=" + Arrays.toString(parameters) +
", parameterTypes=" + Arrays.toString(parameterTypes) +
'}';
}
}
返回体(RpcResponse)
public class RpcResponse implements Serializable {
public static String SUCCEED = "succeed";
public static String FAILED = "failed";
// 响应状态
private String status = SUCCEED;
// 响应信息,如异常信息
private String message;
// 响应数据
private Object data;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "RpcResponse{" +
"status='" + status + '\'' +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
服务接口
public interface DemoService {
String add(Integer a, Integer b);
}
RpcProvider(服务提供者)
RpcProvider本质上就是一个代理类,它持有服务接口的实现类,并根据接收到的RpcRequest,反射调用服务接口实现类的对应方法,并将执行结果封装为RpcResponse返回给RpcConsumer。
public class RpcProvider<T> {
// 服务提供者持有接口的实现类
private T ref;
// 服务接口的Class
private Class<?> interfaceClass;
public void setRef(T ref) {
this.ref = ref;
}
public RpcProvider<T> setInterfaceClass(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
return this;
}
public void export(int port){
try {
ServerSocket listener = new ServerSocket(port);
while(true){
Socket socket = listener.accept();
// 接收数据并进行反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
// 获取请求对象
Object object = objectInputStream.readObject();
if (object instanceof RpcRequest){
RpcRequest request = (RpcRequest) object;
System.out.println("服务端收到请求: " + request);
// 处理传入的请求
RpcResponse response = handleRequest(request);
// 将结果写回客户端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
System.out.println("服务端返回结果: " + response);
objectOutputStream.writeObject(response);
}
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public RpcResponse handleRequest(RpcRequest request){
RpcResponse response = new RpcResponse();
try{
// 解析请求体,获取请求中调用方法的参数
// 基于反射获取到接口的对应方法
Method method = interfaceClass.getMethod(request.getMethodName(), request.getParameterTypes());
// 反射调用接口实现类的对应方法,并拿到方法结果
Object data = method.invoke(ref, request.getParameters());
// 将方法结果set到返回体中
response.setData(data);
}catch (Exception e){
response.setStatus(RpcResponse.FAILED);
response.setMessage(e.getMessage());
}
return response;
}
}
准备好服务接口的实现类:
public class DemoServiceImpl implements DemoService {
@Override
public String add(Integer a, Integer b) {
return "This is a Simple RPC Service, result: " + (a + b);
}
}
启动RpcProvider:
public class DemoServiceProvider {
public static void main(String[] args) {
// 创建服务接口实现实例
DemoServiceImpl demoService = new DemoServiceImpl();
RpcProvider<DemoService> provider = new RpcProvider<>();
// 将服务接口实现实例传给RpcProvider
provider.setInterfaceClass(DemoService.class)
.setRef(demoService);
provider.export(9092);
}
}
RpcConsumer
服务消费端仅持有服务的接口,并没有服务各个接口方法的实现,那如何完成方法的调用呢?
基本原理就是,服务消费端会动态创建1个接口的代理类给到用户,用户调用接口的各个方法时,本质上执行的是代理类的对应方法。
代理类根据用户调用的方法名称和入参等构造好RpcRequest,然后提交给RpcProvider,RpcProvider解析RpcRequest并执行完相应方法,将执行结果封装为RpcResponse返回给代理类,代理类解析完RpcResponse并将结果作为接口方法返回给到用户。整个过程中,用户感知不到代理类做的这些事情,就好像接口方法在本地执行一样。
首先我们先定义1个连接RpcProvider的客户端,用于向RpcProvider发送RpcRequest,并接收RpcResponse:
public class RpcClient {
// 服务接口
private String address;
// 服务端口
private int port;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public RpcResponse send(RpcRequest request) throws Exception {
Socket socket = new Socket(address, port);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(request);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
if(object instanceof RpcResponse){
return (RpcResponse) object;
}
throw new RuntimeException("Return error");
}
}
基于Java的Proxy.newProxyInstance动态创建代理类时,需要传入1个InvocationHandler,以定义代理方法的执行过程,即InvocationHandler的invoke方法。
我们定义1个RpcInvocationHandler,在invoke方法中,我们首先根据调用的方法参数构建RpcRequest,然后再通过RpcClient发送RpcRequest,并接收RpcResponse,最后解析RpcResponse拿到远程方法的执行结果。
public class RpcInvocationHandler implements InvocationHandler {
// 客户端,用于发送RpcRequest
private RpcClient client;
public RpcInvocationHandler(RpcClient client) {
this.client = client;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 构建发送请求
RpcRequest request = new RpcRequest();
request.setMethodName(method.getName());
request.setParameters(args);
request.setParameterTypes(method.getParameterTypes());
// 发送请求,并得到返回
RpcResponse response = client.send(request);
if(RpcResponse.SUCCEED.equals(response.getStatus())){
// 解析返回,拿到结果data
return response.getData();
}
throw new RuntimeException(response.getMessage());
}
}
最后,我们定义一下RpcConsumer:
public class RpcConsumer {
private String address;
private int port;
private Class<?> interfaceClass;
public RpcConsumer setAddress(String address) {
this.address = address;
return this;
}
public RpcConsumer setPort(int port) {
this.port = port;
return this;
}
public RpcConsumer setInterfaceClass(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
return this;
}
public <T> T get(){
RpcClient client = new RpcClient();
client.setAddress(address);
client.setPort(port);
RpcInvocationHandler handler = new RpcInvocationHandler(client);
// 创建代理类
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, handler);
}
}
启动RpcConsumer:
public class DemoServiceConsumer {
public static void main(String[] args) {
RpcConsumer consumer = new RpcConsumer();
consumer.setAddress("127.0.0.1");
consumer.setPort(9092);
consumer.setInterfaceClass(DemoService.class);
DemoService service = consumer.get();
System.out.println(service.add(1, 2));
}
}
代码执行结果:
This is a Simple RPC Service, result: 3
同时可以观察到RpcProvider端的返回:
服务端收到请求: RpcRequest{methodName='add', parameters=[1, 2], parameterTypes=[class java.lang.Integer, class java.lang.Integer]}
服务端返回结果: RpcResponse{status='succeed', message='null', data=This is a Simple RPC Service, result: 3}
可以发现,RpcConsumer端调用的方法最终是在RpcProvider端执行的,即实现了所谓的远程过程调用。
总结
本文实现了1个简易版的RPC框架,旨在带读者过一遍RPC框架的实现过程,但存在以下不足:
- 缺少注册中心,无法实现服务的注册和发现等
- 底层TCP通信基于的是Java的BIO,无法应对高并发场景。
后面,我们会引入Zookeeper作为注册中心,使用Netty高性能网络框架来实现底层通信,对现有的RPC实现进行增强。
本文到此结束,感谢阅读!