当前位置: 首页 > 工具软件 > jgroups-raft > 使用案例 >

raft协议_将Raft集成到JGroups中

仇龙光
2023-12-01

raft协议

几个月前,我在阅读有关Raft共识算法的有启发性的Diego Ongaro论文,并认为在Red Hat开源产品组合中拥有这样的选择将是很棒的。 过去,我多次与Paxos纠缠在一起,并认为它真的太复杂而无法正确实施,至少对我而言。 实话说,我们已经支持Apache ZooKeeper (目前是JBoss Fuse的一部分)和JGroups (这是JBoss家族中所有其他可以集群化的基础),从WildFly应用程序服务器本身到Infinispan都可以解决类似的问题。

ZooKeeper是一个非常成熟的项目,实现了共识算法(已经详细发布),并且在Raft论文中也有引用。 JGroups可以说甚至更成熟,可以追溯到2002年,并且提供了许多低级消息传递功能,但是没有实现分布式共识算法。 JGroups更像是一种用于编写这类算法的工具集,而不仅仅是其中任何一种的实现。

与JGroups仁慈的独裁者Bela Ban聊天时,我们认为在JGroups之上实现Raft算法将很有趣,因为JGroups已经具有许多对健壮的Raft实现有用的功能,另一方面因为在许多不同的用例中,基于一致性的一致算法实际上是一个很好的补充。 看到Bela在短短几天内实现了99%的代码,我想这是一个有效的选择!

等等,什么是木筏?

Diego OngaroJohn Ousterhout在他们的论文“ 寻找 可理解的 共识算法(扩展版本) ”中详细描述了Raft。 用他们自己的话说,“这是一种易于理解的共识算法,在容错和性能上与Paxos等效”。 Raft的目标是使共识可用于更广泛的受众,并且该更广泛的受众将能够开发比当今可用的更高质量的基于共识的系统。 我是广大观众的一部分,所以这绝对是一个很好的开始!

关于Raft的另一个非常重要的事实是,它已经通过使用形式化方法得到了证明 ,特别是在TLA +的帮助下:尽管显然实现中可能存在错误,但是您不会在算法中找到隐藏的极端情况!

共识是分布式计算中的一个众所周知的问题。 它基本上由使不同系统在某件事上达成一致的策略组成。 当您使用不同的(联网的)系统时,必须同意一个简单的命令,例如“ SET MEANING_OF_LIFE to 42”,以确保状态可以高度可用。 共识方法是让大多数节点达成共识,并且Raft算法依赖于领导者选举和每个节点的持久日志来实现。

领导者是通过协商一致选举产生的,所有更改都通过协商进行,然后将其复制到所有节点并将其添加到其持久日志中。 Raft保证在任何时候最多有一个领导者,因此所有状态机都接收相同的有序更新流,因此具有完全相同的状态。

Raft赞成一致性而不是可用性 :就Cap定理而言 ,jgroups-raft是一个CP系统,这意味着,如果不能让大多数节点达成共识,它将无法使用,但它将保持其一致性。 例如,如果我们有5个节点的群集,则3个是大多数,因此即使2个节点发生故障,也可以在系统上进行读取/写入。 如果发生两次以上的故障,就不可能获得多数故障,因此该系统将不可用(尽管在这种情况下可能具有一些只读功能)。

总之,在非常高的级别上,Raft包含一个领导者选举(需要多数),以及由领导者协调的节点,每个节点都有一个永久日志,详细说明了他们的工作。

这里提供了有关Raft算法如何工作的出色图形说明。

为什么选择jgroups-raft

已经有许多可用许多不同语言提供的Raft实现,但是也有许多理由在JGroups中实现Raft:JGroups具有许多现成的构建基块,这些构建基块已经在15年的广泛使用中经过了实战测试,并且需要一个健壮的实现比从头开始一切并因此减少错误少得多。 除此之外,JGroups在扩展基本Raft功能方面也非常有用,例如,通过UDP添加可靠性,大型集群的多播功能和更智能的集群成员资格更改(在JGroups看来,“视图更改”,我们将在后面看到示例)

在JGroups内部拥有可靠的共识算法对于JGroups本身也很重要,因为这意味着Wildfly和/或Infinispan将来将能够开箱即用地提供不同的集群功能,例如,支持集群的Singleton即使在存在网络分区的情况下也能够保持其唯一性(前提是仍然可以达成共识):实际上,具有“正常” Wildfly群集感知能力的单例会在网络分区的情况下破坏其唯一性,最终每个分区(“ partitionton”?)。

测试jgroups-raft

要测试jgroups-raft ,我们将从GIT下载它(见下文),并使用随附的CounterServiceDemo。 这是一个分布式计数器,非常简单,可以在“筏”级别进行分析。 尽管本教程仅在OSX和Linux上进行了测试,但您仍需要在首选OS上安装Java 8。

对于此示例,我假设您正在使用回送接口在笔记本电脑上测试jgroups-raft。

要开始打开终端并在磁盘上的某处克隆git repo:

> git clone git@github.com:belaban/jgroups-raft.git
> export JGROUPS_RAFT_HOME=jgroups-raft
> cd jgroups-raft

我们将在本教程的其余部分中将此目录称为$ JGROUPS_RAFT_HOME。

> ./bin/counter.sh -name A

您应该看到JGroups的一些输出,类似于:

-------------------------------------------------------------------
GMS: address=A, raft-id=A, cluster=cntrs, physical address=127.0.0.1:62162
-------------------------------------------------------------------
-- view: [A, raft-id=A|0] (1) [A, raft-id=A]
[1] Increment [2] Decrement [3] Compare and set [4] Dump log
[8] Snapshot [9] Increment N times [x] Exit
first-appended=0, last-appended=4, commit-index=4, log size=40b

-- changed role to Candidate

最后一行表示此节点现在是成为领导者的候选人,但它仍然不是领导者。 在Raft中,在任何给定时间,每个节点都可以恰好处于以下三种状态之一: Leaderfollower候选人 。 成为候选者意味着该节点正试图成为领导者 ,而成为跟随者意味着该节点将仅遵循领导者候选 者的请求。 默认的raft.xml(可以在$ JGROUPS_RAFT_HOME / conf中找到)配置了三个Raft节点,因此多数是2,这意味着要完成选举,我们需要添加第二个节点。

打开第二个终端:

> ./bin/counter.sh -name B

现在我们启动了第二个节点,两个节点中的一个将能够成为领导者 ,而另一个节点将成为跟随者 。 在Raft中,领导者选举基于心跳机制:如果追随者在称为选举超时的时间内未收到心跳,他们将假设没有领导者,他们将开始选举。 选举大致包括成为候选人 (每个人都是跟随者 )为自己投票,并向集群中的其他节点广播投票请求以获取多数。 选举超时是随机的,因此每个节点的选举超时略有不同,因此分割票很少,并且可以通过算法快速解决。

现在,您应该看到节点之间正在进行投票,并且在两个控制台中看到类似于以下内容的输出:

-- changed role to Follower
-- changed role to Leader

如果这没有发生,那是因为第二个Raft实例在网络级别上看不到另一个:默认的JGroups配置是基于UDP的,因此这很可能意味着您的UDP网络配置未正确配置为回送接口,这通常发生在基于BSD的操作系统(如OSX)上:您将在输出中看到第二个Raft实例创建了一个新的JGroups视图,仅包含其自身。

-- view: [B, raft-id=B|0] (1) [B, raft-id=B]

如果是这种情况,要解决此错误配置,请在路由表中添加以下行,以使UDP在环回接口上起作用:

> sudo route add -net 224.0.0.0/5 127.0.0.1

重新启动两个Raft实例,您最终应该看到选举开始,两个节点更改了角色,并且JGroups视图正确地包含了两个节点:

-- view: [A, raft-id=A|1] (2) [A, raft-id=A, B, raft-id=B]

如果一切正常,请打开第三个终端以启动第三个Raft节点:

> ./bin/counter.sh -name C

现在,您将拥有三个不同的Raft节点,并且可以开始使用该示例。

如果仍然无法正常工作,则可能是由于某种原因您绑定到错误的网络接口而导致UDP流量丢失。 建议是查看《 JGroups手册》的“ 故障排除”部分 或者,如果您急忙,只需通过IRC(#jgroups-raft)或Google网上论坛与我们联系。

分布式计数器具有非常简单的命令行界面,可用于以不同方式递增/递减分布式计数器并查看节点的持久日志中包含的信息。

[1] Increment [2] Decrement [3] Compare and set [4] Dump log
[8] Snapshot [9] Increment N times [x] Exit
first-appended=0, last-appended=7, commit-index=7, log size=70b

例如,您可以在不同的节点控制台中修改计数器,并确保每个节点都具有一致的计数器视图。

现在看一下杀死一个节点会发生什么事情很有趣:您仍然拥有多数,因此系统将继续运行,但是如果杀死两个节点,系统将不可用:其余节点将为自己投票,但它会赢得胜利无法获得获得多数票所需的剩余选票。 请记住,Raft是一个CP系统,如果它检测到无法保证一致性,则会牺牲可用性,在三个群集中只有一个节点处于活动状态时就是这种情况。 重新启动两个死节点之一将使整个系统重新工作。

您将看到最后添加的值和提交索引的值一起增长:要了解它们的含义,您将需要有关Raft持久日志的结构以及在无法使用Raft时如何精确管理集群可用性的更多信息。达成共识。

请注意,当您杀死并重新启动节点时,将从其持久日志中初始化其初始状态。

持久日志中的内容

在Raft中,一旦领导者启动并运行,它将开始回答所有客户端请求,其中包含要由复制状态机执行的命令,在这种情况下,这只是对分布式计数器的一个操作(inc / dec)。 领导者将命令附加到自己的日志中,然后要求其他集群成员执行相同的操作:领导者只有在能够将命令复制到大多数跟随者的情况下,才将控制权返回给客户端。

如果您查看永久日志,将得到类似于以下内容的输出:

index (term): command
---------------------
1 (200): incrementAndGet(counter)
2 (200): incrementAndGet(counter)
3 (200): incrementAndGet(counter)
4 (200): incrementAndGet(counter)
5 (200): decrementAndGet(counter)
6 (200): incrementAndGet(counter)
7 (200): compareAndSet(counter, 4, 10)

日志包含已应用于复制状态机的操作。 该术语是Raft中的一个重要概念,因为它基本上是时间单位。 该任期始于选举,当候选人获胜时,它将成为该任期剩余时间的领导者 (可能永远存在:该任期具有任意长度)。 Raft确保在给定任期内最多有一位领导者:在此示例中,我们的任期为200年,要看到这一价值增长,您只需要使Raft重新开始选举即可。

您可以在日志中看到最后添加的值和commit-index值:第一个是附加到持久性日志的最后一个命令的索引,而后者是最后一个提交的索引:它们通常是相同的,但是对于例如,在领导者上,您最后添加的值为100, 提交索引为90,这意味着领导者无法复制多数命令中的10条命令,因此这些命令尚未应用于状态机。

为了展示这种行为,让我们杀死C节点,并拥有一个只有三个活着的节点A和B的群集。例如,假设A是领导者:如果杀死它,则B(跟随者)会获胜不能成为领导者(绝不成为多数人)并且不接受任何命令:如果您尝试在柜台上做某事,则会收到Java异常,说领导者不再是集群视图的一部分,因此该命令无法应用。 但是,如果不是杀死领导者A而是杀死跟随者B,您将得到一个仅包含领导者的视图。

在这种情况下,如果您尝试对其应用命令,则会发现行为略有不同。 您可能希望领导者下台并成为候选人,但实际上您只会在客户端上遇到超时(准确地说是ConcurrentTimeOutException)。 然后,该命令将被追加到领导者的持久日志中,而无需提交(因为没有人回答可怜的领导者),因此您将看到领导者的最后添加的值和提交索引值不同。 如果然后重新启动B节点,最终将看到commit-index赶上来。

这是Raft的默认行为,这很好,但是在JGroups的帮助下,我们可以在集群视图中进行更改,观察到该视图的大小小于所需的多数,然后使领导者下台:目前尚未实现,但将来将成为可配置的选项 。 这是可能的Raft改进的一个示例,这得益于JGroups的强大功能,可以轻松地应用于基本算法。

Jgroups-raft还实现了Raft论文中建议的快照系统:领导者可以对其持久日志进行快照,因此它可以将快照传输给可能不同步的成员,例如,在大量宕机之后时间,然后重新开始。 请注意,这只是一种优化,因为即使没有快照,该算法也最终能够赶上。

jgroups-raft路线图

jgroups-raft的实现当前为0.2版本,因此尚未真正准备就绪,但几乎已完成所有功能。 它的代码库目前位于主要JGroups项目的“外部”,因为它的发展速度更快,但将来会被合并回去。 尽管如此,它绝对准备好进行测试,并且像开放源代码项目中一样,任何反馈都值得欢迎。 请在IRC组#jgroups-raft或Google组中提交反馈。

结论

在本教程的第一部分中,我们只是从头开始介绍Raft,并通过提供的DistributedCounter示例研究了jgroups-raft实现。 在本教程的第二部分中,我们将(最终)编写一些Java代码,以查看实现自己的启用了jgroups-raft的分区证明集群所需的内容。

翻译自: https://www.infoq.com/articles/JGroups-raft-Primer/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

raft协议

 类似资料: