ActiveJ学习 RPC(2)——RpcClient

2021SC@SDUSC

前言

上一次的博客我们根据activej官方提供的案例RpcExample,初步了解了rpc的大概运行机制,在有了对于整个模型的大致框架之后,下面正式开始对于源代码的解析。
未来的关于源代码的分析,将主要根据以下思路为重点进行:
1.RPCclient
2.RPCserver
3.RPCclient与server沟通相关类:如connection、sender、requeshandler等类

下面的包结构中红框部分为之后的重点研究所在。
在这里插入图片描述

RpcClient

接下来我们正式开始本次的源代码分析,本次以RPCclient类作为起始。
首先,RpcClient在整一个rpc框架中起到的作用是什么?

Sends requests to the specified servers according to defined {@code RpcStrategy} strategy. Strategies, represented in {@link RpcStrategies} satisfy most cases.
通过类RpcStrategy定义的策略,向指定的服务器RpcServer发送请求。

其与RpcServer一般的连接过程为:
1.为客户端创建请求-响应类
2.为服务端创建请求处理类
3.创建一个客户端并根据策略等进行调整

明确clinent要做的事之后,我们来看一下client的具体代码。跳过定义变量环节,直接查看具体的方法。

创建一个使用提供的socket(套接字)的client

	public RpcClient withSocketSettings(SocketSettings socketSettings) {
		this.socketSettings = socketSettings;
		return this;
	}

创建具有指定消息类型处理能力的client

	public RpcClient withMessageTypes(Class<?>... messageTypes) {
		return withMessageTypes(Arrays.asList(messageTypes));
	}
	public RpcClient withMessageTypes(List<Class<?>> messageTypes) {
		Checks.checkArgument(new HashSet<>(messageTypes).size() == messageTypes.size(), "Message types must be unique");
		this.messageTypes = messageTypes;
		return this;
	}

使用序列化器(Serializer)构建器创建客户端。序列化生成器用于在运行时创建快速序列化器。

	public RpcClient withSerializerBuilder(SerializerBuilder serializerBuilder) {
		this.serializerBuilder = serializerBuilder;
		return this;
	}

用一些策略创建一个客户端。考虑一些来自RpcStrategies类的现成策略。

	public RpcClient withStrategy(RpcStrategy requestSendingStrategy) {
		this.strategy = requestSendingStrategy;
		return this;
	}

设置client在连接前等待指定的时间。指定最大连接时间,返回一个具有最大连接时间的client。

	public RpcClient withConnectTimeout(Duration connectTimeout) {
		this.connectTimeoutMillis = connectTimeout.toMillis();
		return this;
	}
	public RpcClient withReconnectInterval(Duration reconnectInterval) {
		this.reconnectIntervalMillis = reconnectInterval.toMillis();
		return this;
	}

在无连接的情况下启动客户端,返回一个无论连接是否可用的client

	public RpcClient withForcedStart() {
		this.forcedStart = true;
		return this;
	}

以上是客户端的前期准备工作的方法
下面是客户端在具体实现时调用的方法
首先是start和stop方法,他实现了RpcClient实现的接口EventloopService,其主要功能就是为了实现异步处理。

public @NotNull Promise<Void> start() {
		if (CHECK) Checks.checkState(eventloop.inEventloopThread(), "Not in eventloop thread");
		Checks.checkNotNull(messageTypes, "Message types must be specified");

		Checks.checkState(stopPromise == null);

		serializer = serializerBuilder.withSubclasses(RpcMessage.MESSAGE_TYPES, messageTypes).build(RpcMessage.class);

		return Promise.<Map<Object, InetSocketAddress>>ofCallback(cb -> strategy.getDiscoveryService().discover(null, cb))
				.map(result -> {
					this.previouslyDiscovered = result;
					Collection<InetSocketAddress> addresses = result.values();

					this.addresses = new ArrayList<>(addresses);

					for (InetSocketAddress address : addresses) {
						if (!connectsStatsPerAddress.containsKey(address)) {
							connectsStatsPerAddress.put(address, new RpcConnectStats(eventloop));
						}
					}

					return addresses;
				})
				.then(addresses -> Promises.all(
						addresses.stream()
								.map(address -> {
									logger.info("Connecting: {}", address);
									return connect(address)
											.map(($, e) -> null);
								}))
						.then(() -> !forcedStart && requestSender instanceof NoSenderAvailable ?
								Promise.ofException(START_EXCEPTION) :
								Promise.complete()))
				.whenResult(this::rediscover);
	}
	public @NotNull Promise<Void> stop() {
		if (CHECK) Checks.checkState(eventloop.inEventloopThread(), "Not in eventloop thread");
		if (stopPromise != null) return stopPromise;

		stopPromise = new SettablePromise<>();
		if (connections.size() == 0) {
			stopPromise.set(null);
			return stopPromise;
		}
		for (RpcClientConnection connection : connections.values()) {
			connection.shutdown();
		}
		return stopPromise;
	}

下面的三个方法分别为client连接,移除连接和关闭连接。

	private Promise<Void> connect(InetSocketAddress address) {
		return AsyncTcpSocketNio.connect(address, connectTimeoutMillis, socketSettings)
				.whenResult(asyncTcpSocketImpl -> {
					if (stopPromise != null || !addresses.contains(address)) {
						asyncTcpSocketImpl.close();
						return;
					}
					statsSocket.onConnect(asyncTcpSocketImpl);
					asyncTcpSocketImpl.setInspector(statsSocket);
					AsyncTcpSocket socket = sslContext == null ?
							asyncTcpSocketImpl :
							wrapClientSocket(asyncTcpSocketImpl, sslContext, sslExecutor);
					RpcStream stream = new RpcStream(socket, serializer, defaultPacketSize,
							autoFlushInterval, frameFormat, false); // , statsSerializer, statsDeserializer, statsCompressor, statsDecompressor);
					RpcClientConnection connection = new RpcClientConnection(eventloop, this, address, stream, keepAliveInterval.toMillis());
					stream.setListener(connection);

					// jmx
					if (isMonitoring()) {
						connection.startMonitoring();
					}
					connections.put(address, connection);
					requestSender = nonNullElseGet(strategy.createSender(pool), NoSenderAvailable::new);

					// jmx
					connectsStatsPerAddress.get(address).recordSuccessfulConnect();

					logger.info("Connection to {} established", address);
				})
				.whenException(e -> {
					logger.warn("Connection {} failed: {}", address, e);
					if (stopPromise == null) {
						processClosedConnection(address);
					}
				})
				.toVoid();
	}

	void removeConnection(InetSocketAddress address) {
		if (connections.remove(address) == null) return;
		requestSender = nonNullElseGet(strategy.createSender(pool), NoSenderAvailable::new);
		logger.info("Connection closed: {}", address);
		processClosedConnection(address);
	}

	private void processClosedConnection(InetSocketAddress address) {
		if (stopPromise == null) {
			if (!addresses.contains(address)) return;
			//jmx
			connectsStatsPerAddress.get(address).recordFailedConnect();
			eventloop.delayBackground(reconnectIntervalMillis, () -> {
				if (stopPromise == null) {
					logger.info("Reconnecting: {}", address);
					connect(address);
				}
			});
		} else {
			if (connections.size() == 0) {
				stopPromise.set(null);
			}
		}
	}

完成连接之后,下一步就是发送请求到服务器,等待结果超时并处理结果与回调。此处是实现了接口IRpcCilent中的方法sendRequest()。
变量说明:
I:请求类
O:响应类
request:向server发送的请求
timeout:超时时间,以毫秒为单位。
cb:在收到响应或遇到错误后完成的回调

	public <I, O> void sendRequest(I request, int timeout, Callback<O> cb) {
		if (CHECK) Checks.checkState(eventloop.inEventloopThread(), "Not in eventloop thread");
		if (timeout > 0) {
			requestSender.sendRequest(request, timeout, cb);
		} else {
			cb.accept(null, new AsyncTimeoutException("RPC request has timed out"));
		}
	}
	public <I, O> void sendRequest(I request, Callback<O> cb) {
		if (CHECK) Checks.checkState(eventloop.inEventloopThread(), "Not in eventloop thread");
		requestSender.sendRequest(request, cb);
	}

其中注意,如果超时后服务器没有响应,请求将以AsyncTimeoutException异常结束。且设置超时时间会将任务调度到Eventloop事件循环中,所以很多有较大timeouts的请求可能会降低性能。
更新socket地址

	private void updateAddresses(Map<Object, InetSocketAddress> newAddresses) {
		this.previouslyDiscovered = newAddresses;
		List<InetSocketAddress> previousAddresses = this.addresses;
		this.addresses = new ArrayList<>(newAddresses.values());

		boolean changed = false;
		for (InetSocketAddress address : previousAddresses) {
			if (!this.addresses.contains(address)) {
				connections.remove(address).shutdown();
				connectsStatsPerAddress.remove(address);
				changed = true;
			}
		}
		if (changed) {
			requestSender = nonNullElseGet(strategy.createSender(pool), NoSenderAvailable::new);
		}
		for (InetSocketAddress address : this.addresses) {
			if (!previousAddresses.contains(address)) {
				connectsStatsPerAddress.put(address, new RpcConnectStats(eventloop));
				connect(address);
			}
		}
	}

再下面的是一些关于ActiveJ的jmx管理生命周期的部分,在这里不再过多详解。

总结

本次的RpcClient的具体内容部分就到这里,从每一个方法中我们其实都能看到,ActiveJ在做的是使用自己的方式,去重写java中的一些特定的接口或功能,以此来达到加快运行速度等期望的效果,例如其中的promise就可以和java的future作类比去进行理解,其与future相同,表示的是一个可能还没有完成的异步结果。这一种类比能让我们更快地理解好ActiveJ的具体实现。而在下一次的代码分析中,我们将解析RpcServer部分以及相关类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Avro是一个轻量级的数据序列化框架,同时也提供了RPC功能。Avro提供了一个基于JSON的schema定义文件来描述数据结构,使得Avro能够支持动态的数据类型。Avro还提供了一个名为avro-rpc的模块,用于实现基于Avro的RPC。 下面我们来对avro-rpc进行性能测试。我们将使用Python 3.7作为客户端和服务端编程语言,并使用Apache Bench来进行压力测试。 首先,我们需要安装avro和avro-rpc模块: ``` pip install avro pip install avro-rpc ``` 接下来,我们编写一个简单的RPC服务端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcServer(object): def __init__(self, host, port): self.server = avro.ipc.HTTPServer(self.handle_request) self.server.add_listener((host, port)) def handle_request(self, request, protocol): message_name = request.message_name request_params = request.request_params print("Received request: {} {}".format(message_name, request_params)) if message_name == "ping": return "pong" elif message_name == "echo": return request_params else: raise avro.AvroRemoteException("Unknown message: {}".format(message_name)) def serve_forever(self): self.server.start() self.server.join() if __name__ == "__main__": server = RpcServer("localhost", 8080) server.serve_forever() ``` 这个RPC服务端程序会监听localhost的8080端口,并实现了两个RPC方法:ping和echo。当客户端调用ping方法时,服务端会返回字符串“pong”;当客户端调用echo方法时,服务端会返回客户端传递的参数。 接下来,我们编写一个简单的RPC客户端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcClient(object): def __init__(self, host, port): self.transceiver = avro.ipc.HTTPTransceiver((host, port)) self.requestor = avro.ipc.Requestor(PROTOCOL, self.transceiver) def ping(self): return self.requestor.request("ping", []) def echo(self, message): return self.requestor.request("echo", [message]) if __name__ == "__main__": client = RpcClient("localhost", 8080) print(client.ping()) print(client.echo("Hello, world!")) ``` 这个RPC客户端程序会连接到localhost的8080端口,并调用服务端实现的ping和echo方法。 接下来,我们使用Apache Bench来进行压力测试: ``` ab -n 10000 -c 10 http://localhost:8080/ ``` 这个命令会模拟10个并发连接,总共发送10000个请求。我们可以通过修改-n和-c参数来改变测试规模。 测试结果如下: ``` Server Software: Server Hostname: localhost Server Port: 8080 Document Path: / Document Length: 4 bytes Concurrency Level: 10 Time taken for tests: 7.194 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1830000 bytes HTML transferred: 40000 bytes Requests per second: 1390.36 [#/sec] (mean) Time per request: 7.194 [ms] (mean) Time per request: 0.719 [ms] (mean, across all concurrent requests) Transfer rate: 248.18 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 14 Processing: 1 7 2.3 7 24 Waiting: 1 7 2.3 7 24 Total: 1 7 2.3 7 24 Percentage of the requests served within a certain time (ms) 50% 7 66% 8 75% 8 80% 8 90% 10 95% 12 98% 15 99% 17 100% 24 (longest request) ``` 从测试结果中可以看出,avro-rpc在处理10000个请求时,平均每个请求处理时间为7.194毫秒。每秒处理请求数为1390.36,处理速度较快,而且没有出现失败的请求。因此,我们可以认为avro-rpc是一个性能良好的轻量级RPC框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值