Raft实现笔记-开篇


本系列是在实现了绝大部分raft论文中描述的功能之后实现过程中遇到的问题,设计的决策等的记录。随着功能的增减,项目的逐渐完善,系统中的实现笔记可能会有偏差,但是基本上对于第一次实现或者想要理解raft的人来说可以作为一个参考。

现在,2018-08-09已经实现的功能

  • leader election + log replication
  • membership change(one server change)
  • log compaction(snapshot)

为了测试简便,现在只实现了memory log,服务器每次重启之后数据会丢失。接下来会实现基于文件的日志。

源代码

https://github.com/xnnyygn/xraft

使用语言和依赖的库

Java 8+(需要Lambda表达式)

库名 版本 描述
netty 4.0.36 网络通信
protobuf 3.6.0 序列化

程序分为xraft-core和xraft-kvstore两个模块,后者是基于前者的服务实现,之后可能会增加其他服务。

raft属于分布式一致性算法,本身没有限制服务类型。论文中给出了一个简单的KV store的例子,所以这里就直接用了。

xraft-kvstore提供了服务端和客户端。

服务端启动参数:

usage: xraft-kvstore-server [OPTION]...
 -gc                group config, required when starts with
                                 group-member mode. format: 
                                 ..., format of node-config:
                                 ,,, eg:
                                 A,localhost,8000 B,localhost,8010
 -h                        host, required when starts with
                                 standalone or standby mode
 -i,--id                node id, required. must be unique in
                                 group. if starts with mode group-member,
                                 please ensure id in group config
 -m                        start mode, available: standalone,
                                 standby, group-member. default is
                                 standalone
 -p1,--port-raft-server    port of raft server, required when starts
                                 with standalone or standby mode
 -p2,--port-service        port of service, required

有三种启动模式

  1. group-member
  2. standalone
  3. standby

group-member即以集群成员方式启动,需要指定集群成员(-gc)。特殊的,如果集群成员只有一个(并且是当前)节点,那么和standalone启动方式一致。

standalone为单节点启动,启动后第一次选举timeout之后自动变成leader。standalone方式下日志并不会复制到其他节点,所以添加日志后立马就可以commit。

standby是之后要加入集群的节点,启动后第一次选举timeout时保持follower状态,不会变成candidate。往集群中加入节点时,如果新节点以现有集群配置+自身启动的话,可能会扰乱现有集群,所以xraft中新节点以standby方式启动(不需要指定集群配置),等待来自集群leader的主动连接。

除了服务端的模式之外,服务端还有两个端口,port-raft-server,port-service。也就是常见的内部端口和服务端口。现在新增集群节点和删除集群节点的话是通过服务端口进行的。原因是xraft中内部通信需要交换集群节点id,新增节点的请求端因为不是集群节点,无法指定集群节点id。同样的,删除节点也无法指定当前集群节点id。

kvstore客户端内置一个console,支持以下命令

命令 描述
client-add-server <node-id> <host> <port-service> client程序增加可用节点
client-remove-server <node-id> client程序删除可用节点
client-list-server 列出当前client程序可用节点
client-get-leader 输出当前client程序使用的leader
client-set-leader <node-id> 设置当前client程序使用的leader
raft-add-server <node-id> <host> <port-raft-server> 向集群中增加节点
raft-remove-server <node-id> 删除集群节点
kvstore-get <key> kvstore获取key对应的value
kvstore-set <key> <value> kvstore设置key对应的key

client开头的是用于设置当前client的命令。raft开头的是内部处理,比如集群成员增减。kvstore开始的才是服务自身的命令。

usage: xraft-kvstore-client [OPTION]...
 -gc    group config, required. format: 
                       . format of server config:
                       ,,. e.g
                       A,localhost,8001 B,localhost,8011

启动后

Welcome to XRaft KVStore Shell

***********************************************
current server list:

A,localhost,3333
B,localhost,3334
C,localhost,3335
***********************************************
kvstore-client 0.0.1>

本console使用了jline,按两次tab可以列出所有可用命令。

测试用启动流程

  • 服务端和客户端console可以分别启动。客户端启动时不会连接服务端
  • 假设3节点,以group-member方式启动
    • -m group-member -gc A,localhost,2333 B,localhost,2334 C,localhost,2335 -i A -p2 3333
    • -m group-member -gc A,localhost,2333 B,localhost,2334 C,localhost,2335 -i B -p2 3334
    • -m group-member -gc A,localhost,2333 B,localhost,2334 C,localhost,2335 -i C -p2 3335
    • group-member方式启动中可能部分节点访问不到其他节点,可能导致term增加,或者日志中出现持续出现连接失败的消息(可优化)
  • 假设3节点,分别是standalone, standby, standby方式启动,客户端在所有节点启动后向standalone发起命令,增加服务器来组成集群
    • xraft-kvstore-server -m standalone -i A -p1 2333 -p2 3333
    • xraft-kvstore-server -m standby -i B -p1 2334 -p2 3334
    • xraft-kvstore-server -m standby -i C -p1 2335 -p2 3335
    • xraft-kvstore-cli -gc A,localhost,3333
    • kvstore-client> raft-add-server B localhost 2334
    • kvstore-client> raft-add-server C localhost 2335
  • 测试服务
    • xraft-kvstore-cli -gc A,localhost,3333 B,localhost,3334 C,localhost,3335
    • kvstore-client> kvstore-get foo
    • kvstore-client> kvstore-set foo 1
    • kvstore-client> kvstore-get foo
    • 如果看到1就说明成功

之后会详细讲解设计与实现。