.png)
【译】NuRaftOnTheRocks: 一种基于 RocksDB and NuRaft 的分布式 Key-value 存储方案
原文地址:https://www.mydistributed.systems/2024/12/nuraftontherocks-replicated-key-value.html
NuRaft 是一种开源的 C++ Raft 实现方案,具有广泛的可插拔性,它允许你实现自己的持久化日志存储与状态管理,并将其运用到你自己的状态机之中。无论你需要日志复制与否,你都可以把 NuRaft 同任何应用程序联系起来。
在这篇文章之中,我将向您展现如何将 NuRaft 同 RocksDB 整合,以此构建出一个分布式的 Key-Value 存储方案来。
Raft 协议简介
Raft 是一种共识协议与基于日志的复制协议。Raft 允许分布式系统在一组共享的日志的基础之上,由多个进程形成共识。在此协议所保证的可靠性基础之上,我们可以将状态机的备份实现为跟随节点(Follows):我们将会拥有一组自最初状态始的状态机的集合,并在共享日志中记录状态机所发生的一切操作,同时将会按照共享日志中指定的要求,在本地的状态机上面执行对应的操作。
在这种策略的指引下,所有的状态机都将同其它节点的状态机保持同步。状态机复制非常有用。我们可以创建被复制的数据存储。在这篇文章里面,我们使用 Raft 来做这件事。而在这个案例之中,我们的状态机将会是使用了 RocksDB 的 Key-Value 存储。
(图一 使用了 Raft 协议的状态机复制)
对于 Raft 协议的细节,我在早前的一篇博客中有所提及,如果你希望了解到 Raft 协议的更多内幕细节,可以阅读这篇文章。
NuRaft 简介
NuRaft 是由我所在的 eBay 团队所研究出来的开源 C++ Raft 实现方案,具有高度的可插拔性。你可以将其集成到你自己所使用的任何的持久化日志、状态管理、以及你本人所使用的状态机中最重要的大部分实现中。
特别注意,为了使用 NuRaft,你需要实现如下的接口:
而当你实现了这些接口的是否,你就不需要再关系任何有关复制的问题了,NuRaft 将会替你关心这些事情,同时它将会保证串行化的一致性。简单来说,就是所有的状态机将会按照(和日志产生的时间顺序)精确的一样的顺序来执行(日志中所指定的)操作。
RocksDB 简介
RocksDB 是一款流行的开源嵌入式方案,它并非分布式或是可复制的 Key-Value 存储方案。作为替代的,你可以将其作为你所实现的分布式/可复制 Key-Value 存储对的一部分组件。它是一个使用 Lsm-Tree 作为底层存储格式的存储引擎。
(图二 使用了层级压缩的 Lsm-Tree 模型)
在 稍早的一篇博客中,我讨论了基于日志结构的存储引擎与 Lsm-Tree 模型,如果你对于 Lsm-Tree 的内部料感兴趣的话,欢迎阅读这篇文章。
将 RocksDB 同 NuRaft 融合起来的复制方案
在这篇文章里面,我将会向你展现你如何能够把 NuRaft 同 RocksDB 融合起来,构成一个可复制的 Key-Value 存储方案。
我使用了 NuRaft 仓库中提供出来的在内存中的日志存储与状态管理的案例程序。注意,在内存中的日志存储我们实际上是没有一致性保证的。在实际的生产系统里面你需要实现这些一致性的接口。可以参考 ClickHouse 的日志实现 中生产级持久化日志存储的实现。
源代码
https://github.com/roohitavaf/NuRaftOnTheRocks 中存储着本文所阐述的可复制的 Key-Value 键值对存储。
在阅读下一个章节前,可以参考仓库中的 README 文件,并且遵循里面的指示来构建或者是运行回归测试。
源代码布局简介
NuRaftOnTheRocks 被组织为如下的内容:
- api
这个文件夹包含着简单的 gRPC 服务。牵涉到两组操作的集合:一个是 Key-Value 的 CRUD(放置或是获取键值对);一个是 Raft 操作(比如,将一个徐新的服务器节点加入到 Raft 集群之中) - scripts
运行测试 - src
主要的源代码 - tests
包含着全部的测试的实现代码
让我们开始分析 src 文件夹中的内容。我在其中实现了许多不同的内容。
- single in-memory:在内存中的单节点 Key-Value 存储
- single RocksDB:以 RocksDB 作为后端的单节点持久化 Key-Value 存储实现
- multi in-memory:一个可复制的在内存中的由 NuRaft 驱动的 Key-Value 存储
- multi RocksDB:一个由 NuRaft 与 RocksDB 同时驱动的可复制持久化 Key-Value 存储
这些实现的具体内容都摆放于 src/common 文件夹之中。让我们看一看这些文件夹中有什么东西。
- grpc_server:定义 GrpcServer 类,它实现了 gRPC 服务。它有一组 KeyValueServer (Key-Value 服务器)接口
- kv_server_interface:定义 KeyValueServer 接口,它包含着 Key-Value CRUD 操作以及 Raft 的操作
- raft_kv_server:实现了 KeyValueServer 存储接口,并且实现了一组 KeyValueStateMachine 的接口
- state_machine_interface: 实现了一组 KeyValueStateMachine 接口,它是一个虚类,实现了 nuraft::state_machine 接口并且要求外部实现一定的虚函数:
- getKey
- scan
- applyPayload
对于单独的 Key-Value 存储而言(比如,单独的在内存中的节点以及单独的 RocksDB),我们直接是实现了 KeyValueServer 接口。
而对于可复制的 Key-Value 存储(比如,多节点内存引擎或者多 RocksDB 节点),我们实现了 KeyValueStateMachine 接口
图三展现了我们整体程序的设计,并且展现出了各个类之间的关联关系。
(图三 对应的接口与类)
run_server.hxx 文件创建合适的 GrpcServer 接口的工程逻辑,基于向 main 函数传递的输入参数来加以实现。
结论
在这篇文章中,我们看到了我们能够如何使用 NuRaft 来创建可复制的状态机系统。在这里,我们使用 RocksDB 作为我们的状态机,并且构建可复制的 Key-Value 存储引擎(方法是将其嵌入到 NuRaft 之中)。但是你可以将任何状态机都放置于 NuRaft 中,用以实现强一致性以及日志复制。