Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

背景

raft算法原理,建议参考raft官网:https://raft.github.io/

强烈推荐观看raft的流程动画,方便直观理解算法过程

动画地址:http://thesecretlivesofdata.com/raft/

本系列通过阅读SofaJRaft源码,并在本地运行SofaJRaft自带的Counter演示程序,学习了解raft算法在工程中的具体实现。

SofaJRaft源码地址:https://github.com/sofastack/sofa-jraft

对应的文档地址:https://www.sofastack.tech/projects/sofa-jraft/jraft-user-guide/

本项目阅读和演示的代码版本:

<dependencies>

        <!-- jraft -->
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>jraft-core</artifactId>
            <version>1.3.9</version>
        </dependency>
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>jraft-rheakv-core</artifactId>
            <version>1.3.9</version>
        </dependency>
        <!-- jsr305 -->
        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>3.0.2</version>
        </dependency>
        <!-- bolt -->
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>bolt</artifactId>
            <version>1.6.4</version>
        </dependency>
        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>hessian</artifactId>
            <version>3.3.6</version>
        </dependency>
        <!-- log -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>
        <!-- disruptor -->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.7</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- protobuf -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- protostuff -->
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.6.0</version>
        </dependency>
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.6.0</version>
        </dependency>
        <!-- rocksdb -->
        <dependency>
            <groupId>org.rocksdb</groupId>
            <artifactId>rocksdbjni</artifactId>
            <version>6.22.1.1</version>
        </dependency>
        <!-- java thread affinity -->
        <dependency>
            <groupId>net.openhft</groupId>
            <artifactId>affinity</artifactId>
            <version>3.1.7</version>
        </dependency>
        <!-- metrics -->
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-core</artifactId>
            <version>4.0.2</version>
        </dependency>
</dependencies>

选主

通过raft算法的原理可以知道,raft集群是一主(learder)多从(follower)的结构,leader负责接收客户端(client)的请求,处理并记录日志,然后leader将日志复制到所有的follower,达到数据冗余备份的目的。

当集群中有follower宕机,在不超过半数的情况下,集群仍然可以正常提供服务,follower恢复后,可以从leader复制快照(snapshot),以及快照点之后的所有日志记录,用于恢复内存中的数据,以追赶上集群状态。

当集群中的leader宕机后,所有的follower将选举出一名新的leader替代原有的leader,此过程即“选主”。

Counter演示程序

在源码分析之前,先演示一遍raft集群的启动及选主程序,直观的感受一下流程。

该程序来源于SofaJRaft源码,业务功能很简单,就是一个计数器,从零开始,每次客户端调用,都可以自增服务端内存中的整数值。

服务器启动

服务器执行入口是源码中的CounterServer的main方法 

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

我这里会在idea中启动3个本地服务host,模拟一主两从的集群结构。

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

CounterServer1、 CounterServer2、CounterServer3 是3个服务器,执行入口都是上面说的同一个main方法,但是启动参数有细微差别:

CounterServer1的启动参数:

/tmp/server1 counter 127.0.0.1:8181 127.0.0.1:8181,127.0.0.1:8182,127.0.0.1:8183

CounterServer2的启动参数:

/tmp/server2 counter 127.0.0.1:8182 127.0.0.1:8181,127.0.0.1:8182,127.0.0.1:8183

CounterServer3的启动参数:

/tmp/server3 counter 127.0.0.1:8183 127.0.0.1:8181,127.0.0.1:8182,127.0.0.1:8183

逗号分隔的4个参数,第一个是服务目录,第二个是group名称,第三个是服务ip,第四个是group配置。

其中,group名称需要一样,作为3个服务器加入的同一个集群的标志;因为是本机演示,所以服务ip都是一样的,靠端口区分;最后,group配置,其实就是一串服务ip列表,保存该集群的所有ip地址。

接下来,我们依次启动CounterServer1(端口8181)、 CounterServer2(端口8182)、CounterServer3(端口8183)

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

可以看到“Start the RaftGroupService successfully.”,表示服务器启动完成。

随后,CounterServer1开始发起选举,投自己一票,并向CounterServer2和CounterServer3要选票。Raft算法_SOFAJRaft源码学习_(一、背景及选主演示) 从上图可以看到,CounterServer3返回了granted=true,表示CounterServer3同意了CounterServer1的自荐请求。加上CounterServer1自己投自己的一票,2票同意超过了集群3台服务器的半数,所以最后CounterServer1成功成为了集群的leader:“become leader of group”。

我们再看看CounterServer3的日志情况:

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

可以看到,CounterServer3收到了来自CounterServer1的preVote和Vote请求,并没有表示异议。最后开始成为集群的follower,认可CounterServer1为leader。

 再看看CounterServer2的日志:

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

也有类似的收到 CounterServer1的preVote和Vote请求,以及认可CounterServer1为leader。

服务器重新选主

这个时候,我们的3台服务器的小集群已经到达了可对外服务的稳定状态,CounterServer1作为leader,CounterServer2和CounterServer3作为follower。

我们接下来模拟一下leader挂掉的场景,把CounterServer1的进程终止掉

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

 然后发现CounterServer3开始选举自己为leader,并发送选举请求给 CounterServer2,且收到了认可回复:

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

 最后,CounterServer3成为了集群的新领导者,然后不断尝试重新连接已经挂掉的CounterServer1:

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

 再看看CounterServer2,可以看到其认可CounterServer3成为新leader的日志:

Raft算法_SOFAJRaft源码学习_(一、背景及选主演示)

 

以上就是Counter演示程序的启动和选主流程简要过程,更多复杂的场景,比如网络分裂合并等,需要起更多的进程进行演示,可以自己试试。

下一篇将对这个过程,进行简单的源码流程阅读分析。

上一篇:Kafka从入门到放弃(三) —— 详说生产者


下一篇:实习三月感想