
<h1 align="center">🌟 Luminous</h1>
## 为什么有这个库
在另一个存储库 [Iris](https://github.com/hanbings/icarus/tree/main/iris) 中,基于 [Raft 原始论文](https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf) 和使用 `actix-web` 来编写了一个非常简单的 Raft 工具库。
工具库包括以下部分:
- Follower、Leader 和 Candidate 节点状态机
- 任期、日志索引
- `AppendEntries` RPC、`VoteRequest` RPC
- heartbeat 包机制、超时选举、随机选举超时和选举超时
未实现部分:
- 日志一致性检查流程
- 高度一致性(准确来说,是节点加入集群前的日志复制流程)
- 解决集群分裂问题所需的两阶段方式
- 日志压缩
在这个工具库中,只能够以固定逻辑存储 `String` 类型的键和值,因为内部数据存储使用 `Map<String, String>`,显而易见这是一个巨大的缺陷,这意味着多余的序列化和反序列化步骤,考虑到 Rust 的所有权机制等,还会有额外的 clone,且因为逻辑固定,也很难在每一个使用工具库的应用中实现普通 kv 存储器的拓展功能(如自动过期、自动落盘备份、分片存储等)。
## 主要内容
按照原始论文划分为以下部分:
- 计时器(心跳包机制、超时机制、随机选举超时和选举超时机制)
- 网络(包括 RPC 、序列化反序列化和根据状态机处理数据包)
- 内部存储
- 落盘持久化
为了尽可能好的性能和并发安全,首先考虑了 `Actor` 模式对操作进行封装。
以存储一个 `Log Entry` 为例子,具体方式如下:
1. 定义基本数据类型
```rust
pub trait RaftDataType: Clone + Debug + Send + Sync + Serialize + DeserializeOwned + 'static {}
pub trait RaftError: Error + Debug + Send + Sync + Serialize + DeserializeOwned + 'static {}
```
这里指的基本数据类型是在整个实现中,`Log Entry` 将携带的数据,例如 Iris 实现中的基本数据类型是 `String`。
此外,从实现的完整性来说,我们还需要定义一个 `Error` 类型,以尽可能准确表达处理数据过程中可能出现的错误。
2. 实现一个 `SaveLog` 结构体并实现 `actix::Message`
```rust
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct SaveLog<T: RaftDataType, E: RaftError> {
pub term: u64,
pub index: u64,
pub data: T,
e: std::marker::PhantomData<E>,
}
impl<T: RaftDataType, E: RaftError> actix::Message for SaveLog<T, E> {
type Result = Result<(), E>;
}
```
看起来会有些怪?实际上这个 `SaveLog` 充当的是一个函数的作用,声明了将会传入 `Actor` 的参数(`term: u64, index: u64, data: T`)以及返回值(`type Result = Result<(), E>;`)。
至于 `std::marker::PhantomData<E>`,是因为在结构体中并没有使用 `E` 作为一个字段,它将产生 [无界生命周期](https://doc.rust-lang.org/nomicon/unbounded-lifetimes.html)。于是我们需要使用一个用于占位的工具,使得它在结构体内被声明但不能产生真正的内存消耗,[PhantomData](https://doc.rust-lang.org/nomicon/phantom-data.html) 就很适合。
3. 定义一个 “聚合” 特性,提供给用户实现它并通过某个接口传入实现中。
```rust
pub trait RaftLog<T, E>
where
T: RaftDataType,
E: RaftError,
Self: Actor<Context = Context<Self>>,
Self: Handler<log::SaveLog<T, E>>,
Self::Context: ToEnvelope<Self, log::SaveLog<T, E>>,
{
}
```
到这一步来说,我们就为用户提供了一个 “函数”,用于处理保存 `Log Entry`。使用方式大致如下:
```rust
impl RaftLog<String, XXError> for RaftCluster<String, XXError> { }
impl Actor for RaftCluster<String, XXError> {
type Context = XXContext;
}
impl Handler<log::SaveLog<String, XXError>> for RaftCluster<String, XXError> {
fn handle(&mut self, msg: SaveLog<String, XXError>) {
}
}
```
## 参考与致谢
[In Search of an Understandable Consensus Algorithm](https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf) Raft 的原始论文
[actix-raft](https://github.com/bjornmolin/actix-raft) 的严谨且具有拓展性实现
[raft-rs](https://github.com/tikv/raft-rs) 是一个高性能和易于理解的实现
感谢!