注意:中文文档有可能未及时更新,请以最新的英文readme为准。
raft协议的完全异步C语言实现。
raft库采用模块化设计,核心部分实现了完全独立于平台的raft算法逻辑。重要的是为网络传输(发送/接收rpc消息)和存储持久化(日志和快照)提供了可插拔的I/O接口实现。
在使用默认选项构建库时,基于 libuv提供了 I/O 接口的库存实现,适合绝大多数应用场景。唯一的问题是它目前 需要 Linux,因为它使用 Linux AIO API 用于磁盘输入/输出。欢迎添加补丁以支持更多的平台。
可以通过raft.h来查看全部接口。
这个 raft C 库是在略微修改的 LGPLv3 版本下发布的,其中包括一个版权例外,允许用户在他们的项目中静态链接这个库的代码并按照自己的条款发布最终作品。如有需要,请查看完整license文件。
该实现包括 Raft 论文中描述的所有基本功能:
-
领导选举
-
日志复制
-
日志压缩
-
成员变更
它还包括一些可选的增强功能:
- 优化流水线以减少日志复制的延迟
- 并行写入领导者的磁盘
- 领导者失去法定人数时自动下台
- 领导权转移扩展
- 预投票协议
如果您使用的是基于 Debian 的系统,您可以从 dqlite 的dev PPA 获得最新的开发版本:
sudo add-apt-repository ppa:dqlite/dev
sudo apt-get update
sudo apt-get install libraft-dev
从源码编译libraft,需要准备:
sudo apt-get install libuv1-dev liblz4-dev libtool pkg-config build-essential
autoreconf -i
./configure --enable-example
make
理解如何使用raft库的最好方式是阅读源码目录下的example server。
可以运行如下命令来了解example server的运行情况:
./example/cluster
这个命令启动了一个由3节点组成的集群,并且会不断的随机停止一个节点并重启。
以下是一个如何使用raft库的快速指南(为了简洁起见,省略了错误处理),要想了解详细的信息建议阅读raft.h。
- 创建一个
raft_io
接口实现的实例(如果库附带的接口确实不适合,则实现您自己的实例):
const char *dir = "/your/raft/data";
struct uv_loop_s loop;
struct raft_uv_transport transport;
struct raft_io io;
uv_loop_init(&loop);
raft_uv_tcp_init(&transport, &loop);
raft_uv_init(&io, &loop, dir, &transport);
- 定义应用层的
Raft FSM
,实现raft_fsm
接口
```C
struct raft_fsm
{
void *data;
int (*apply)(struct raft_fsm *fsm, const struct raft_buffer *buf, void **result);
int (*snapshot)(struct raft_fsm *fsm, struct raft_buffer *bufs[], unsigned *n_bufs);
int (*restore)(struct raft_fsm *fsm, struct raft_buffer *buf);
}
```
- 为每个服务节点选择一个唯一的 ID 和地址并初始化 raft 对象:
unsigned id = 1;
const char *address = "192.168.1.1:9999";
struct raft raft;
raft_init(&raft, &io, &fsm, id, address);
- 如果这是您第一次启动集群,请创建一个包含集群中服务器节点(节点通常只有一个,因为稍后可以使用
raft_add
和raft_promote
扩展集群)的配置对象,并启动配置:
struct raft_configuration configuration;
raft_configuration_init(&configuration);
raft_configuration_add(&configuration, 1, "192.168.1.1:9999", true);
raft_bootstrap(&raft, &configuration);
- 启动raft服务器节点
raft_start(&raft);
uv_run(&loop, UV_RUN_DEFAULT);
- 异步提交请求将新命令应用到应用程序状态机
static void apply_callback(struct raft_apply *req, int status, void *result) {
/* ... */
}
struct raft_apply req;
struct raft_buffer buf;
buf.len = ...; /* The length of your FSM entry data */
buf.base = ...; /* Your FSM entry data */
raft_apply(&raft, &req, &buf, 1, apply_callback);
- 添加更多的服务器节点到集群使用
raft_add()
和raft_promote
APIs
默认基于 libuv 的 raft_io
实现使用 liblz4
库压缩 raft 快照。 除了节省磁盘空间,lz4
压缩快照以内容校验和的形式提供额外的数据完整性检查,这允许 raft
检测存储期间发生的损坏。 因此,建议不要通过--disable-lz4
配置标志禁用 lz4
压缩。
当环境变量LIBRAFT_TRACE在启动时被设置,将启用详细跟踪。
- dqlite
当然,最感谢的是 Diego Ongaro
:)(raft论文的作者)。下面的raft实现提供了很多灵感和想法:
-
CoreOS' Go implementation for etcd
-
Hashicorp's Go raft
-
Willem's C implementation
-
LogCabin's C++ implementation