本系列是在实现了绝大部分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提供了服务端和客户端。
服务端启动参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 |
有三种启动模式
- group-member
- standalone
- 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开始的才是服务自身的命令。
1 2 3 4 5 |
usage: xraft–kvstore–client [OPTION]... –gc group config, required. format: . format of server config: ,,. e.g A,localhost,8001 B,localhost,8011 |
启动后
1 2 3 4 5 6 7 8 9 10 |
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就说明成功
之后会详细讲解设计与实现。