本项目已停止维护
dqlite 团队不再将我们的 raft 实现作为独立项目进行维护。相反,raft 源代码已作为私有实现细节被整合到 canonical/dqlite 中。v0.18.1 是 dqlite 的 libraft 的最后一个版本。我们对此变更可能造成的任何不便表示歉意。
如果您依赖 dqlite 但不直接依赖 raft,请查看 canonical/dqlite 以获取有关如何使用 dqlite 捆绑的 raft 构建配置的最新说明。如果您之前依赖 dqlite 的 libraft,您应该切换到维护的分支 cowsql/raft。
dqlite 存储库上有一个讨论线程,欢迎就此变更提出任何问题。
以下 README 内容仅具有历史参考价值。
Raft 共识协议的全异步 C 语言实现。
该库采用模块化设计:其核心部分仅实现核心 Raft 算法逻辑,完全独立于平台。在此基础上,可插拔接口定义了网络(发送/接收 RPC 消息)和磁盘持久化(存储日志条目和快照)的 I/O 实现。
使用默认选项构建库时,会提供一个现成的 I/O 接口实现。它基于 libuv,应该适用于绝大多数用例。唯一的限制是它目前需要 Linux,因为它使用了 Linux AIO API 进行磁盘 I/O。欢迎提交补丁以增加对更多平台的支持。
完整文档请参阅 raft.h。
许可证
这个 raft C 库是在略微修改的 LGPLv3 版本下发布的,其中包括一个版权例外条款,允许用户在他们的项目中静态链接库代码,并以自己的条款发布最终作品。请查看完整的许可证文本。
特性
此实现包括 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
示例
了解如何使用该库的最佳方式可能是阅读源代码中包含的示例服务器的代码。
您也可以通过运行以下命令来查看示例服务器的运行情况:
./example/cluster
这将启动一个由 3 个服务器组成的小型集群,运行一个示例工作负载,并不时随机停止和重新启动一个服务器。
快速指南
建议您阅读 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
接口:
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);
异步提交请求以将新命令应用到您的应用程序 FSM:
static void apply_callback(struct raft_apply *req, int status, void *result) {
/* ... */
}
struct raft_apply req;
struct raft_buffer buf;
buf.len = ...; /* 您的 FSM 条目数据的长度 */
buf.base = ...; /* 您的 FSM 条目数据 */
raft_apply(&raft, &req, &buf, 1, apply_callback);
要向集群添加更多服务器,请使用 raft_add()
和 raft_promote
API。
使用注意事项
默认的基于 libuv 的 raft_io
实现使用 liblz4
库压缩 raft 快照。除了节省磁盘空间外,lz4 压缩的快照还提供了额外的数据完整性检查,以内容校验和的形式,这允许 raft 检测存储过程中发生的损坏。因此,建议不要通过 --disable-lz4
配置标志禁用 lz4 压缩。
当启动时设置环境变量 LIBRAFT_TRACE
时,将启用详细跟踪。其值可以在 [0..5]
范围内,表示跟踪级别,其中 0
表示"不输出跟踪",5
启用最小级别(仅 FATAL 记录),1
启用最大详细程度(所有:DEBUG、INFO、WARN、ERROR、FATAL 记录)。
知名用户
致谢
当然,最大的感谢要给 Diego Ongaro :)(Raft 论文的原作者)。
许多想法和灵感来自其他 Raft 实现,如: