2021SC@SDUSC
何为RPC
首先,在分析ActiveJ的RPC源码之前,我们需要先知道,什么是RPC?
RPC全称为Remote Procedure Call即远程过程调用,是一个节点请求另一个远端节点提供的服务。即两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
下面来看一看RPC的大体流程是怎么样的:
一、要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
二、要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
三、当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
四、B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
五、返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用
具体地可以看一看下面的图帮助理解
以上即为RPC的一些基础知识了解,在脑海中对于RPC有了大体的了解之后,下面我们就继续进入RPC进行探究,通过这一个框架,去探究ActiveJ中的RPC,实现了什么。
ActiveJ RPC
ActiveJ RPC由世界上最快的JVM序列化程序ActiveJ Serializer提供支持,在TCP上运行,支持流水线请求,并具有自定义的高性能二进制流协议。它引入了一种完全替代的微服务实现方法,并克服了利用具有JSON或XML编码的HTTP协议的开销。
这是ActiveJ对于自身的RPC的评价,下面就从入门,RPC的第一个实例来看一看RPC的具体过程在ActiveJ中是怎么实现的。
RpcExample
public class RpcExample extends Launcher {
private static final int SERVICE_PORT = 34765;
@Inject
private RpcClient client;
@Inject
private RpcServer server;
@Inject
private Eventloop eventloop;
@Provides
Eventloop eventloop() {
System.out.println("running init eventloop");
return Eventloop.create();
}
@Provides
RpcServer rpcServer(Eventloop eventloop) {
System.out.println("running init rpcserver");
return RpcServer.create(eventloop)
.withMessageTypes(String.class)
.withHandler(String.class,
request -> Promise.of("Hello " + request))
.withListenPort(SERVICE_PORT);
}
@Provides
RpcClient rpcClient(Eventloop eventloop) {
System.out.println("running init rpcclient");
StackTraceElement stack[] = (new Throwable()).getStackTrace();
for (int i = 0; i < stack.length; i++) {
StackTraceElement ste = stack[i];
System.out.println(ste.getClassName() + "." + ste.getMethodName() + "(...)");
System.out.println(i+"--"+ste.getMethodName()+"--"+ste.getFileName() + "--" + ste.getLineNumber());
}
return RpcClient.create(eventloop)
.withMessageTypes(String.class)
.withStrategy(server(new InetSocketAddress(SERVICE_PORT)));
}
@ProvidesIntoSet
Initializer<ServiceGraphModuleSettings> configureServiceGraph() {
// add logical dependency so that service graph starts client only after it started the server
return settings -> settings.addDependency(Key.of(RpcClient.class), Key.of(RpcServer.class));
}
@Override
protected Module getModule() {
return ServiceGraphModule.create();
}
@Override
protected void run() throws ExecutionException, InterruptedException {
CompletableFuture<Object> future = eventloop.submit(() ->
client.sendRequest("World", 1000)
);
System.out.printf("%nRPC result: %s %n%n", future.get());
}
public static void main(String[] args) throws Exception {
RpcExample example = new RpcExample();
example.launch(args);
}
}
由于现阶段我们还什么都不知道,我们先来看看main函数结合着输出都干了些什么?由于输出的同时会附带log日志一同输出,让我们来看一下实际上这个例子想要的输出。
RPC result: Hello World
从分析代码可以知道,显然,这和RpcExample类里的方法run()有关。我们可以看到RpcExample是继承自Launcher类的类,我们来看一看实际上launer到底都干了些什么:
前面的一些初始化我们先行跳过,看一看关于其中具体的流程步骤
1.将client和server连接起来,下面代码将客户端和服务器连接起来,保证其能进行通信。如果连接失败会报启动错误。
logger.info("=== STARTING APPLICATION");
try {
instantOfStart = Instant.now();
logger0.info("Starting Root Services: {}", services);
startServices(services, startedServices);
onStart();
onStartFuture.complete(null);
} catch (Exception e) {
applicationError = e;
logger.error("Start error", e);
onStartFuture.completeExceptionally(e);
}
2.client和server确认建立连接成功了之后,调用RpcExample类重写的run()方法
if (applicationError == null) {
logger.info("=== RUNNING APPLICATION");
try {
instantOfRun = Instant.now();
run();
onRunFuture.complete(null);
} catch (Exception e) {
applicationError = e;
logger.error("Error", e);
onRunFuture.completeExceptionally(e);
}
} else {
onRunFuture.completeExceptionally(applicationError);
}
3.到这里我们大概能了解到,RpcExample重写的getModel()和run()方法本质上还是会进入到launcher里面再对子类重载进行调用。但此时,我们还尚不知道,在example类中,除了两个重载的方法之外,其他的方法有什么作用。
于是我试着将eventloop、rpcServer、rpcClient均注释掉,再次运行RpcExample可以得到以下的结果:
Exception in thread "main" io.activej.inject.binding.DIException: Unsatisfied dependencies detected:
key RpcClient required to make:
- InstanceInjector<RpcExample> at <unknown binding location>
key RpcServer required to make:
- InstanceInjector<RpcExample> at <unknown binding location>
key Eventloop required to make:
- InstanceInjector<RpcExample> at <unknown binding location>
at io.activej.inject.impl.Preprocessor.checkUnsatisfied(Preprocessor.java:188)
at io.activej.inject.impl.Preprocessor.check(Preprocessor.java:162)
at io.activej.inject.Injector.compile(Injector.java:207)
at io.activej.inject.Injector.compile(Injector.java:171)
at io.activej.inject.Injector.of(Injector.java:147)
at io.activej.launcher.Launcher.createInjector(Launcher.java:290)
at io.activej.launcher.Launcher.createInjector(Launcher.java:286)
at io.activej.launcher.Launcher.launch(Launcher.java:147)
at RpcExample.main(RpcExample.java:86)
我从报错的根源一步步走入,在launcher启动的过程中,设计到injector的初始化,而injector的初始化会一步步递归调用到module,且module会调用绑定的对应的eventloop和rpcserver、rpccilent,而当他找回原来的类中时,却会发现没有这个,所以就会报绑定错误。
小结
至此我们能了解到rpc在ActiveJ中大体是怎样运行起来的,并且也明白了rpc本身的机理。
在ActiveJ中每一次使用到Rpc时都需要使用到eventloop和相关的绑定服务器和客户端,下一次的源码分析就会进入到RPC的具体包中进行分析,明白其内在到底是如何使用所谓的“引入了一种完全替代的微服务实现方法,并克服了利用具有JSON或XML编码的HTTP协议的开销。”
参考
【1】简书:什么是RPC
【2】知乎:RPC框架解释
【3】Activej官方网站