我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用Raft组件

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用Raft组件

这篇文章主要讲解了“如何使用Raft组件”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用Raft组件”吧!

一、编译

github下载 Ratis 直接 mvn clean package 即可,如果编译过程中出错,可以先clean install  ratis-proto

二、示例

Ratis 自带的示例有三个:

  • arithmetic

  • counter

  • filestore

在 ratis-examples 模块中,对于 arithmetic 和 filestore比较方便,可以通过main/bin目录下的 shell  脚本快速启动 Server 和 Client 来进行测试。

对于Raft,咱们都知道是需要多实例组成集群才能测试,你启动一个实例没啥用,连选主都成问题。Bin 目录下的 start-all 支持 example  的名称以及对应的命令。比如 filestore server 代表是启动 filestore 这个应用的server。对应的命令参数会在相应example里的  cli 中解析。同时会一次性启动三个server,组成一个集群并在周期内完成选举。

而对于 counter 这个示例,并没有相应的脚本来快速启动三个server,这个我们可以通过命令行或者在IDE里以参数的形式启动。

三、分析

下面我们来示例里看下 Raft Server 是怎样工作的。

对于 counter 示例来说,我们启动的时候,需要传入一个参数,代表当前的server是第几个,目的在于,要从 peers 列表中得知该用哪个IP +  端口去启动它。这里我们能发现,这个 peers 列表,是在代码内提前设置好的。当然你说动态配置啥的,也没啥问题,另外两个示例是通过shell 脚本里common  中的配置传入的。

所以,第一步我们看到, Raft Server 在启动的时候,会通过「配置」的形式,来知道 peer  之间的存在,这样才能彼此通信,让别人给自己投票或者给别人投票,完成 Term 内的选举。另外,才能接收到 Leader 传过来的 Log  ,并且应用到本地。

第二步,我们来看下 Client 和 集群之间是如何通信的。整个 Raft 集群可能有多个实例,我们知道必须通过 Leader  来完成写操作。那怎样知道谁是Leader?有什么办法?

一般常见的思路有:

  • 在写之前,先去集群内查一下,谁是 Leader,然后再写

  • 随机拿一个写,不行再换一个,不停的试,总会有一个成功。

当然方式二这样试下去效率不太高。所以会在这个随机试一次之后,集群会将当前的 Leader 信息返回给 Client,然后 Client  直接通过这个建立连接进行通信即可。

在 Ratis 里, Client 调用非 Leader 节点会收到 Server 抛出的一个异常,异常中会包含一个称为 suggestLeader  的信息,表示当前正确的 Leader,按这个连上去就行。当然,如果如果在此过程中发生的 Leader 的变更,那就会有一个新的suggestLeader  返回来,再次重试。

我们来看 Counter 这个示例中的实现。

Server 和 Client 的共用的Common 代码中,包含 peers 的声明

public final class CounterCommon {   public static final List<RaftPeer> PEERS = new ArrayList<>(3);    static {     PEERS.add(new RaftPeer(RaftPeerId.getRaftPeerId("n1"), "127.0.0.1:6000"));     PEERS.add(new RaftPeer(RaftPeerId.getRaftPeerId("n2"), "127.0.0.1:6001"));     PEERS.add(new RaftPeer(RaftPeerId.getRaftPeerId("n3"), "127.0.0.1:6002"));   }

这里声明了三个节点。

通过命令行启动时,会直接把index 传进来, index 取值1-3。

java -cp *.jar org.apache.ratis.examples.counter.server.CounterServer {serverIndex}

然后在Server 启动的时候,拿到对应的配置信息。

//find current peer object based on application parameter     RaftPeer currentPeer =         CounterCommon.PEERS.get(Integer.parseInt(args[0]) - 1);

再设置存储目录

//set the storage directory (different for each peer) in RaftProperty object     File raftStorageDir = new File("./" + currentPeer.getId().toString());     RaftServerConfigKeys.setStorageDir(properties,         Collections.singletonList(raftStorageDir))

重点看这里,每个 Server 都会有一个状态机「CounterStateMachine」,平时我们的「业务逻辑」都放到这里

//create the counter state machine which hold the counter value     CounterStateMachine counterStateMachine = new CounterStateMachine();

客户端发送的命令,会在这个状态机中被执行,同时这些命令又以Log 的形式复制给其它节点,各个节点的Log  又会在它自己的状态机里执行,从而保证各个节点状态的一致。

如何使用Raft组件

如何使用Raft组件

最后根据这些配置,生成 Raft Server 实例并启动。

//create and start the Raft server     RaftServer server = RaftServer.newBuilder()         .setGroup(CounterCommon.RAFT_GROUP)         .setProperties(properties)         .setServerId(currentPeer.getId())         .setStateMachine(counterStateMachine)         .build();     server.start();

CounterStateMachine 里,应用计数的这一小段代码,我们看先检查了命令是否合法,然后执行命令

//check if the command is valid     String logData = entry.getStateMachineLogEntry().getLogData()         .toString(Charset.defaultCharset());     if (!logData.equals("INCREMENT")) {       return CompletableFuture.completedFuture(           Message.valueOf("Invalid Command"));     }     //update the last applied term and index     final long index = entry.getIndex();     updateLastAppliedTermIndex(entry.getTerm(), index);      //actual execution of the command: increment the counter     counter.incrementAndGet();      //return the new value of the counter to the client     final CompletableFuture<Message> f =         CompletableFuture.completedFuture(Message.valueOf(counter.toString()));      //if leader, log the incremented value and it's log index     if (trx.getServerRole() == RaftProtos.RaftPeerRole.LEADER) {       LOG.info("{}: Increment to {}", index, counter.toString());     }

我们再来看 Client 的实现。

和 Server 类似,通过配置属性,创建一个实例

private static RaftClient buildClient() {     RaftProperties raftProperties = new RaftProperties();     RaftClient.Builder builder = RaftClient.newBuilder()         .setProperties(raftProperties)         .setRaftGroup(CounterCommon.RAFT_GROUP)         .setClientRpc(             new GrpcFactory(new Parameters())                 .newRaftClientRpc(ClientId.randomId(), raftProperties));     return builder.build();   }

然后就可以向Server发送命令开工了。

raftClient.send(Message.valueOf("INCREMENT"));

Counter 的状态机支持INCREMENT 和 GET 两个命令。所以example 最后执行了一个 GET 的命令来获取最终的计数结果

RaftClientReply count = raftClient.sendReadOnly(Message.valueOf("GET"));

四、内部部分实现

RaftClientImpl 里,初期会从peers列表中选一个,当成leader 去请求。

RaftClientImpl(ClientId clientId, RaftGroup group, RaftPeerId leaderId,       RaftClientRpc clientRpc, RaftProperties properties, RetryPolicy retryPolicy) {     this.clientId = clientId;     this.clientRpc = clientRpc;     this.peers = new ConcurrentLinkedQueue<>(group.getPeers());     this.groupId = group.getGroupId();     this.leaderId = leaderId != null? leaderId         : !peers.isEmpty()? peers.iterator().next().getId(): null;     ...   }

之后,会根据server 返回的不同异常分别处理。

private RaftClientReply sendRequest(RaftClientRequest request) throws IOException {     RaftClientReply reply;     try {       reply = clientRpc.sendRequest(request);     } catch (GroupMismatchException gme) {       throw gme;     } catch (IOException ioe) {       handleIOException(request, ioe);     }     reply = handleLeaderException(request, reply, null);     reply = handleRaftException(reply, Function.identity());     return reply;   }

比如在 handleLeaderException 中,又分几种情况,因为通过Client 来和 Server  进行通讯的时候,会随机从peers里选择一个,做为leader去请求,如果 Server  返回异常,说它不是leader,就用下面的代码,随机从另外的peer里选择一个再去请求。

final RaftPeerId oldLeader = request.getServerId();     final RaftPeerId curLeader = leaderId;     final boolean stillLeader = oldLeader.equals(curLeader);     if (newLeader == null && stillLeader) {       newLeader = CollectionUtils.random(oldLeader,           CollectionUtils.as(peers, RaftPeer::getId));     }   static <T> T random(final T given, Iterable<T> iteration) {     Objects.requireNonNull(given, "given == null");     Objects.requireNonNull(iteration, "iteration == null");      final List<T> list = StreamSupport.stream(iteration.spliterator(), false)         .filter(e -> !given.equals(e))         .collect(Collectors.toList());     final int size = list.size();     return size == 0? null: list.get(ThreadLocalRandom.current().nextInt(size));   }

是不是感觉很低效。如果这个时候,server 返回的信息里,告诉client 谁是 leader,那client 直接连上去就可以了是吧。

   RaftClientReply handleLeaderException(RaftClientRequest request, RaftClientReply reply,                                         Consumer<RaftClientRequest> handler) {     if (reply == null || reply.getException() instanceof LeaderNotReadyException) {       return null;     }     final NotLeaderException nle = reply.getNotLeaderException();     if (nle == null) {       return reply;     }     return handleNotLeaderException(request, nle, handler);   }
RaftClientReply handleNotLeaderException(RaftClientRequest request, NotLeaderException nle,       Consumer<RaftClientRequest> handler) {     refreshPeers(nle.getPeers());     final RaftPeerId newLeader = nle.getSuggestedLeader() == null ? null         : nle.getSuggestedLeader().getId();     handleIOException(request, nle, newLeader, handler);     return null;   }

我们会看到,在异常的信息中,如果能够提取出一个 suggestedLeader,这时候就会做为新的leaderId来使用,下次直接连接了。

感谢各位的阅读,以上就是“如何使用Raft组件”的内容了,经过本文的学习后,相信大家对如何使用Raft组件这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何使用Raft组件

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何用Nacos实现Raft算法

这篇文章主要介绍“如何用Nacos实现Raft算法”,在日常操作中,相信很多人在如何用Nacos实现Raft算法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何用Nacos实现Raft算法”的疑惑有所帮助!
2023-06-02

React组件如何使用

本篇内容主要讲解“React组件如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“React组件如何使用”吧!组件的定义:理解:用来实现局部功能效果的代码和资源的集合(html/css/js
2023-07-05

如何使用vue组件封装共用的组件

这篇文章主要介绍了如何使用vue组件封装共用的组件的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何使用vue组件封装共用的组件文章都会有所收获,下面我们一起来看看吧。这里提供两种vue封装共用组件的方法方法一
2023-06-30

SimpleFramework中如何使用组件

这篇文章主要讲解了“SimpleFramework中如何使用组件”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SimpleFramework中如何使用组件”吧!描述文件的位置XML描述文件的
2023-06-17

Android ProgressBar组件如何使用

Android ProgressBar是一个显示进度的组件,可以用于展示任务的进度,比如下载文件的进度、上传文件的进度等。使用ProgressBar组件的步骤如下:1. 在XML布局文件中添加ProgressBar组件:```xmlandr
2023-08-12

Unity3D AudioSource组件如何使用

这篇文章主要介绍了Unity3D AudioSource组件如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Unity3D AudioSource组件如何使用文章都会有所收获,下面我们一起来看看吧。1 简
2023-07-05

SpringCloud Bus组件如何使用

本文小编为大家详细介绍“SpringCloud Bus组件如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“SpringCloud Bus组件如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是Bu
2023-06-29

android navigation组件如何使用

Android Navigation组件是一套可以帮助开发者轻松实现应用程序导航功能的框架。下面是使用Android Navigation组件的一般步骤:1. 添加依赖:在项目的build.gradle文件中添加以下依赖:```implem
2023-10-09

JetpackCompose Scaffold组件如何使用

本文小编为大家详细介绍“JetpackCompose Scaffold组件如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“JetpackCompose Scaffold组件如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入
2023-07-04

vue3 table组件如何使用

今天小编给大家分享一下vue3 table组件如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。基础表格首先开发tabl
2023-07-06

Android中如何使用Spinner组件

今天就跟大家聊聊有关Android中如何使用Spinner组件,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Android Spinner 组件Spinner: 下拉组件使用事项:布
2023-05-30

如何使用Flex组件开发

这篇文章主要为大家展示了“如何使用Flex组件开发”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用Flex组件开发”这篇文章吧。使用Flex组件开发在Flex应用程序中ActionScri
2023-06-17

VB.NET中 TextBox组件如何使用

VB.NET中 TextBox组件如何使用,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1. 启动Visual Studio .Net。2. 选择菜单【文件】|【新建】|【
2023-06-17

C#中COM组件如何使用

C#中COM组件如何使用,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一.本文程序设计和运行的软件环境1 微软公司视窗2000服务器版2 .Net FrameWork SDK
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录