上一篇简单介绍了一下Dubbo,Dubbo首先是一个RPC框架,那么这篇我们先从RPC聊起 。
Dubbo是一个基于Java的RPC框架,底层基于高性能通信框架Netty实现。RPC(Remote Procedure Call)翻译过来就是远程过程调用,那么什么是远程过程调用呢?我们应该知道本地过程调用,就像我们本地的A方法调用B方法就是本地过程调用。那么对应的远程过程调用,就是通过网络去远程计算机服务上请求服务,至于 RPC 要如何调用远程计算机上的方法可以走 HTTP协议、也可以是基于 TCP 或者UDP自定义协议。
最开始接触RPC的时候我想当然的把RPC和HTTP来进行对比,现在看来这两个都不是一个层级的东西,不能瞎比。
而 RPC 框架的作用,就是让我们使用远程调用像本地调用一样简单方便,并且能解决一些远程调用中会发生的问题。让用户像调用本地方法一样无感知、放心的使用,不需要了解底层网络的通信机制。
那么如何设计一个RPC框架
通过上面我们了解到什么是RPC以及RPC框架的作用是什么,那么接下来刁钻的面试官肯定得问你怎么设计一个RPC框架,没想到吧,我做了准备。
RPC框架有以下三个核心角色:
1.服务提供者
服务提供者需要提供接口的实现,然后把接口暴露出去,将自己的服务信息注册到注册中心。并定期像注册中心发送心跳,证明自己还活着。
这样服务消费者请求过来之后,要实现反序列化,序列化完成之后的请求放入到线程池,某个线程拿到这个请求去执行对应代码的逻辑,然后返回结果给服务消费者。
2.服务消费者
服务消费者启动时向注册中心订阅所需要的服务,并缓存到实例列表中。
服务消费者要去消费服务提供者提供的服务,双方就要约定一个协议,比如用http协议来进行通信。调用时,从实例列表中选择一个服务实例去调用。
因为是网络传输,所以序列化是必须的,比如代码中的类信息需要经过序列化之后才能通过网络传输,因此要约定序列化格式。
因为服务消费者是直接调用服务提供者,就算注册中心宕机了也不会影响已经正常运行的消费者和服务者。
3.注册中心
注册中心就像一个平台,用于服务提供者暴露服务,服务消费者在上面发现服务。服务实例列表发生变化时,动态通知消费者,消费者更新本地的实例列表。
这个平台还可以用作配置中心,将所需配置集中存放到此,配置变更之后可以动态通知消费者。
市面上常用的注册中心有:zookeeper,nacos,eureka,consul
总结起来就是这么回事:
1.服务调用者以本地调用方式调用方法;
2.client stub接收到调用后负责将方法名、参数、参数类型等组装成能够进行网络传输的消息体;其实这一步在Java中就是序列化过程。然后找到对应的服务地址,并将消息通过网络发送到服务端;
3.client stub将打包后的消息通过网络发送至服务提供者;
4.server stub收到消息后进行解码,对应在Java中就是反序列化过程;
5.server stub根据解码结果调用本地的方法;
6.本地服务执行处理逻辑并将结果返回给server stub;
7.server stub将返回结果打包成消息,对应Java里的序列化过程;
8.server stub将打包后的消息通过网络发送至服务消费者;
9.client stub接收到消息并解码,对应 Java中的反序列化过程;
10.服务调用者得到最终返回结果;
服务提供者相关代码:
private static void runRPCServer(Object service, int port) {
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("服务提供者启动!!");
while (true) {
final Socket accept = server.accept();
System.out.println("收到请求并放入线程池执行");
pool.submit(new Runnable() {
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
//1.反序列化
input = new ObjectInputStream(accept.getInputStream());
//2.获得方法名
String methodName = input.readUTF();
//3.获得参数类型
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
//4.获得参数
Object[] args = (Object[]) input.readObject();
//5.根据方法名找到方法
Method method = service.getClass().getMethod(methodName, parameterTypes);
//6.执行对应的方法
Object result = method.invoke(service, args);
//7.从监听的socket中获得输出流
output = new ObjectOutputStream(accept.getOutputStream());
//8.返回执行结果
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != server) {
server.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务消费者相关代码:
public static Object getClientService(Class interfaceClazz) {
return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz},
(proxy, method, args) -> callRPCServer(method,args));
}
private static Object callRPCServer(Method method, Object[] args){
Object back = null;
//默认ip:127.0.0.1
String ip = "127.0.0.1";
//端口号默认设80
int port = 8090;
ObjectOutputStream objOutStream = null;
Socket client;
try {
//指定服务提供者ip和端口
client = new Socket(ip,port);
objOutStream = new ObjectOutputStream(client.getOutputStream());
//方法名
objOutStream.writeUTF(method.getName());
//参数类型
objOutStream.writeObject(method.getParameterTypes());
//参数值
objOutStream.writeObject(args);
try (ObjectInputStream objInStream = new ObjectInputStream(client.getInputStream())) {
//读取远程方法返回的结果
back = objInStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if(null != objOutStream)
objOutStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return back;
}
以上便是一个非常简陋的rpc框架demon,就是服务消费者传递了方法名、参数类型和参数值,服务提供者接收到这样参数之后调用对应的方法并返回结果。