上一节中介绍了ratis的主要结构和模块,详情见下方的链接:Ratis介绍
本篇主要是从零开始学习客户端(Client)的源码,学习内部的逻辑。
Client是一个发起请求到服务端的模块,ratis是嵌入在系统中运行的,要想使用ratis中的功能,就需要继承ratis的类文件,实现其中的方法。ratis的源码RATIS中给出了几个使用的例子,如Counter, Arithmetic, Filestore等,本文中我们可以从简单的Counter例子入手来观察和使用ratis。
客户端的使用方式是采用了建造者模式,如果想要获取一个客户端的实例,那就需要使用建造者模式创建一个客户端。代码如下:
private final RaftClient client = RaftClient.newBuilder()
.setProperties(new RaftProperties())
.setRaftGroup(Constants.RAFT_GROUP)
.setClientId(客户端ID可以不传)
.setLeaderId(指定leaderid)
.setClientRpc(设置客户端到服务端的rpc协议框架,默认是使用Grpc)
.setRetryPolicy(设置客户端重试的策略,默认是不睡眠一直重试)
.build();
RetryPolicy还有一个实现类RequestTypeDependentRetryPolicy 可以根据不同的操作类型,设置不同的重试策略,使用的时候类似上述的使用方法
final RequestTypeDependentRetryPolicy.Builder b RequestTypeDependentRetryPolicy.newBuilder();
final RetryPolicies.RetryLimited writePolicy = RetryPolicies.retryUpToMaximumCountWithFixedSleep(n, writeSleep);
b.setRetryPolicy(RaftClientRequestProto.TypeCase.WRITE, writePolicy);
创建客户端
private final RaftClient client = RaftClient.newBuilder()
.setProperties(new RaftProperties())
.setRaftGroup(Constants.RAFT_GROUP)
.setClientId(客户端ID可以不传)
.setLeaderId(指定leaderid)
.setClientRpc(设置客户端到服务端的rpc协议框架,默认是使用Grpc)
.setRetryPolicy(b.builder())
.build();
客户端的请求发送有两种方式,一种是同步阻塞等待,一种是异步的发送,下面分别介绍两种实现方法。
1)客户端调用client.io().send()方法调用的是同步阻塞式的等待返回结果,客户端侧创建一个RaftClientRequest,然后将请求发送给服务端,代码如下:
// 发送请求
private RaftClientReply send(RaftClientRequest.Type type, Message message, RaftPeerId server)
throws IOException {
if (!type.is(TypeCase.WATCH)) {
Objects.requireNonNull(message, "message == null");
}
final long callId = CallId.getAndIncrement();
return sendRequestWithRetry(() -> client.newRaftClientRequest(server, callId, message, type, null));
}
// 创建客户端请求
RaftClientRequest newRaftClientRequest(
RaftPeerId server, long callId, Message message, RaftClientRequest.Type type,
SlidingWindowEntry slidingWindowEntry) {
final RaftClientRequest.Builder b = RaftClientRequest.newBuilder();
if (server != null) {
b.setServerId(server);
} else {
b.setLeaderId(getLeaderId());
}
return b.setClientId(clientId)
.setGroupId(groupId)
.setCallId(callId)
.setMessage(message)
.setType(type)
.setSlidingWindowEntry(slidingWindowEntry)
.build();
}
发送过程中会根据指定的重试策略,如果请求失败了会进行重试。
2)客户端调用client.async().send()方法发送客户端请求,在客户端侧维护了一个滑动窗口,每次默认最大允许100个并发提交请求到服务端
CompletableFuture send(RaftClientRequest.Type type, Message message, RaftPeerId server) {
if (!type.is(TypeCase.WATCH) && !type.is(TypeCase.MESSAGESTREAM)) {
Objects.requireNonNull(message, "message == null");
}
try {
requestSemaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return JavaUtils.completeExceptionally(IOUtils.toInterruptedIOException(
"Interrupted when sending " + type + ", message=" + message, e));
}
final long callId = CallId.getAndIncrement();
final LongFunction constructor = seqNum -> new PendingOrderedRequest(callId, seqNum,
slidingWindowEntry -> client.newRaftClientRequest(server, callId, message, type, slidingWindowEntry));
return getSlidingWindow(server).submitNewRequest(constructor, this::sendRequestWithRetry
).getReplyFuture(
).thenApply(reply -> RaftClientImpl.handleRaftException(reply, CompletionException::new)
).whenComplete((r, e) -> {
if (e != null) {
LOG.error("Failed to send request, message=" + message, e);
}
requestSemaphore.release();
});
}