ratis学习(一):Ratis客户端

上一节中介绍了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();
  • 其中的客户端使用RPC框架,如果构建客户端时没指定使用的框架,则默认使用grpc,还支持netty。
  • 客户端的失败重试策略有四种:
  1. NO_Retry :不重试,失败直接抛异常给用户,不自动重试
  2. RETRY_FOREVER_NO_SLEEP: 失败直接重试,不主动失败,没有睡眠时间,失败立即重试
  3. retryForeverWithSleep: 失败重试,但是每次失败后会睡眠指定的时间再进行重试,直到成功。
  4. retryLimited: 失败重试,但是是重试指定的次数并且每次失败后会睡眠指定的时间再进行重试。 

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();
    });
  }

你可能感兴趣的:(ratis,java,大数据)