ActiveJ学习——RPC(1)

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有了大体的了解之后,下面我们就继续进入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官方网站

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值