A distributed storage framework based on the Raft consensus algorithm, implemented in Java.
基于 Raft 共识算法的分布式存储框架,使用 Java 实现。当前提供高可用的键值存储(Key-Value Store),并计划后续支持元数据管理、配置服务等更多分布式系统组件。
本项目采用 Apache License 2.0 开源,欢迎社区参与维护与贡献。
- 完整 Raft 共识:Leader 选举(Pre-Vote + lease + transfer)、日志复制与提交、快照与日志压缩。
- 线性一致性读:基于 ReadIndex 协议,确保读操作的强一致性。
- 双存储引擎:支持 RocksDB 与纯 Java 自研 LSM 引擎无缝切换,Raft 代码零改动。
- KV 状态机:支持提交后 apply、幂等执行、会话去重,以及 GET/SET/DEL/INCR 等键值操作。
- 运行时成员变更:支持单服务器模式与 Joint Consensus,可在线动态增减节点(ADDNODE / REMOVENODE)。
- Redis 兼容:支持 RESP2/RESP3 协议前端,redis-cli 直连,非 Leader 节点自动 -MOVED 重定向。
- 高可靠测试:包含故障注入、线性一致性折磨测试(linearizability test)及全量对抗式 audit。
- 可观测性与基准:集成 Micrometer 指标监控,提供 JMH 基准测试套件。
自顶向下分为四层 + 两条旁路:RESP 协议前端层、命令/KV 状态机层、Raft 共识层、存储引擎层,以及客户端入口和节点间通信两条旁路。核心设计原则:Raft 共识层不直接操作 KV,它只保证"日志一致"。
客户端入口旁路实际有两条独立路径,不共用同一前端:redis-cli 走 RESP2/RESP3 协议前端层;SkiffClient 走 Netty 长度前缀 protobuf,直连 Raft 端口的 ClientRpcService,不经过 RESP 层。两条路径最终都汇入同一个命令/KV 状态机层。
flowchart TD
subgraph entry["客户端入口 (旁路)"]
RC["redis-cli (RESP2/RESP3)"]
SC["SkiffClient (原生异步客户端, redirect-not-proxy, LeaderCache)"]
end
RESP["RESP 协议前端层 (io.github.nitouge.skiff.server.resp, RESP2/RESP3, 端口 6380)"]
RPC["Raft RPC 服务层 (ClientRpcService, Netty 长度前缀 protobuf, Raft 端口 7001+)"]
SM["命令/KV 状态机层 (KvStateMachine: GET/SET/DEL/INCR...)"]
RAFT["Raft 共识层 (只保证日志一致, 不直接操作 KV)"]
STORE["存储引擎层 (StorageEngine SPI: RocksDB 或 自研 LSM)"]
PEER["对等节点 Raft 层 (peer nodes)"]
TRANS["RaftTransport (SimTransport / NettyRaftTransport)"]
RC -->|"RESP2/RESP3"| RESP
SC -->|"Netty 长度前缀 protobuf, 直连 Raft 端口"| RPC
RESP -->|"命令解析与分发, 非 leader 回 -MOVED"| SM
RPC -->|"命令分发, 非 leader 回 RESULT_NOT_LEADER + leaderHint"| SM
SM -->|"写: propose 命令"| RAFT
SM -->|"线性一致读: ReadIndex (M9)"| RAFT
RAFT -->|"已提交 entry 经 apply 线程喂给状态机"| SM
SM -->|"在一个 WriteBatch 内写 data + lastApplied"| STORE
RAFT -->|"raft-log / raft-meta 持久化"| STORE
RAFT <-->|"requestVote / appendEntries / installSnapshot"| TRANS
TRANS <-->|"复制日志到对等节点"| PEER
📝 架构说明
- 客户端入口旁路(两条独立路径):redis-cli 通过 RESP2/RESP3 接入 RESP 协议前端层(端口 6380);SkiffClient 通过 Netty 长度前缀 protobuf 直连 Raft 端口(7001+)的
ClientRpcService,不经过 RESP 层。- RESP 协议前端层:负责 RESP2/RESP3 编解码、命令解析与分发,对非 Leader 请求返回文本协议的
-MOVED host:port重定向。- Raft RPC 服务层:
ClientRpcService(由KvCommandService实现)接收 SkiffClient 的 protobuf 命令并分发;对非 Leader 请求返回RESULT_NOT_LEADER+ leaderHint,由 SkiffClient 的 LeaderCache 在客户端侧完成重定向和重试,不是-MOVED文本重定向。- 命令/KV 状态机层:处理 GET、SET、DEL、INCR 等命令;写请求通过 propose 进入 Raft,读请求通过 ReadIndex 保证线性一致性。两条入口路径最终都汇入这一层。
- Raft 共识层:负责 Leader 选举、日志复制、提交、快照、成员变更和线性一致读确认,但不直接操作 KV 数据。
- 状态机应用路径:已提交 entry 由 apply 线程交给状态机,状态机再将 KV 数据和
lastApplied在同一个 WriteBatch 中写入存储引擎。- 存储引擎层:通过 StorageEngine SPI 支持 RocksDB 和自研 LSM;Raft 层同时持久化 raft-log 和 raft-meta。
- 节点间通信旁路:Raft 层通过 RaftTransport(SimTransport 或 NettyRaftTransport)发送 requestVote、appendEntries、installSnapshot,与对等节点完成复制和恢复。
- JDK 17:用于编译/运行。
⚠️ gradle.properties默认把 Gradle toolchain 指向 JDK 17 所在路径,请修改该配置。 - 存储引擎:
- 默认
rocksdb。Windows 需安装 MSVC C++ redistributable,建议数据目录路径保持简短。 - 也可在配置里设
node.storageEngine=lsm使用纯 Java 的自研 LSM 引擎——无任何 native 依赖、跨平台、零额外前置。
- 默认
./gradlew -q javaToolchains # sanity:应列出 JDK 17(build 前先跑)
./gradlew build # 编译全部模块 + 跑测试
./gradlew :skiff-it:test # 仅集成 / 冷启动测试
./gradlew :skiff-bench:jmh # RocksDB-vs-LSM 基准config/n{1,2,3}.properties 已配好单主机 3 节点(Raft 端口 7001–7003、RESP 端口 6380–6382、storageEngine、互相的 RESP redirect 端点)。
1. 启动节点
各开一个终端启动一个节点:
./gradlew :skiff-server:run --args="config/n1.properties"
./gradlew :skiff-server:run --args="config/n2.properties"
./gradlew :skiff-server:run --args="config/n3.properties"2. 验证读写
节点会自动选出 leader 并复制日志。用 redis-cli(或任意 Redis 客户端)连到任一节点的 RESP 端口:
redis-cli -p 6380 # 连到 n1(若它不是 leader,写/读会回 -MOVED 指向 leader 端口)
> SET foo bar # 经 Raft 复制提交
> GET foo # 线性一致读(ReadIndex)
> INCR counter
> HELLO 3 # 协商 RESP3(可选)💡 写经 propose 复制提交、读经 ReadIndex 线性一致;连到非 leader 的写/读会以 -MOVED host:port 重定向到 leader(redirect-not-proxy)。Ctrl+C 可干净关闭。把 node.storageEngine 改成 lsm 即可整集群切到自研 LSM 引擎、Raft 代码零改动。完整冷启动路径由 ClusterBootstrapTest 端到端验证。
3. 运行时成员变更 (M18)
先以 node.join=true(cluster.peers 仅含自身)启动一个新节点 n4,再对 leader 执行管理命令在线增减成员——endpoint 随 CONFIG 复制给全体,新节点经 learner 追赶后提升为投票者:
redis-cli -p 6380 MEMBERS # 查看当前投票者
redis-cli -p 6380 ADDNODE n4 127.0.0.1 7004 # 在线加入 n4(仅 leader 可执行;非 leader 回 -MOVED)
redis-cli -p 6380 REMOVENODE n2 # 在线移除 n2💡 真实 Netty 下的运行时增减节点(含把 leadership 转移给新增节点)由 MembershipChangeClusterIT 端到端验证。
| Module | Responsibility |
|---|---|
skiff-common |
叶子工具:NodeId、Endpoint、ClusterConfig、Clock、CRC32C |
skiff-proto |
Protobuf schema(envelope / raft / kv)+ 生成的 Java 代码 |
skiff-wire |
SkiffEnvelope 与 ByteBuf 互转的 Netty 编解码器 + 帧格式常量(长度前缀/上限),server 与 client 共享 |
skiff-storage-api |
StorageEngine SPI + Raft log/meta 适配器 |
skiff-storage-rocksdb |
基于 RocksDB 的第一阶段引擎 |
skiff-storage-lsm |
第二阶段自定义 LSM 引擎 |
skiff-raft |
共识核心:RaftCore、RaftNode、传输层/状态机 SPI |
skiff-rpc |
Netty 传输层 |
skiff-statemachine |
基于 StorageEngine 的 KV 状态机 |
skiff-server |
装配、节点 bootstrap、RESP 前端 |
skiff-client |
原生异步客户端 |
skiff-testkit |
确定性测试 + 线性一致性 (linearizability) 基础设施 |
skiff-it |
多节点集成 / chaos 测试 |
skiff-bench |
JMH 基准测试 |
M0-M18 全部完成。
| 里程碑 | 状态 | 功能 |
|---|---|---|
| M0 脚手架 | ✅ | 14-module Gradle DAG、protobuf 代码生成、3 节点单 JVM 引导 |
| M1 单节点 Raft core | ✅ | 纯 RaftCore + 副作用 RaftNode + 确定性 sim;单节点选举/提交/应用 |
| M2 多节点 leader 选举 | ✅ | 心跳、随机超时、分区故障注入;稳定单一 leader |
| M3 日志复制与提交 | ✅ | 完整 AppendEntries、基于多数派的提交规则、冲突修复、跨节点收敛 |
| M4 持久化与崩溃恢复 | ✅ | RocksDB 引擎 + 引擎无关适配器;销毁+重建恢复、防 double-vote |
| M5 Netty transport + Protobuf RPC | ✅ | 真实 TCP 选举+复制收敛;超时/重连/分帧;sim 测试不变仍绿 |
| M6 KV 状态机 + 客户端 + 检查器 | ✅ | 崩溃原子幂等 apply + 会话去重;SkiffClient redirect;线性一致性证伪器 |
| M7 RESP2 前端 | ✅ | RESP2 编解码 + 命令表 + RespServer;写经 Raft、读 leader 本地;非 leader -MOVED |
| M8 快照与日志压缩 | ✅ | 流式 SM 快照(含会话表);maybeCompact;InstallSnapshot 分块、断点续传 |
| M9 线性一致读 ReadIndex | ✅ | NO-OP 门槛 + 一轮被 quorum ack 的心跳确认 leadership;被隔离旧 leader 绝不返回陈旧值 |
| M10 Pre-Vote + 租约 + leadership transfer | ✅ | Pre-Vote + stickiness 防扰动;send-time 租约快路径读;TimeoutNow 优雅转移 |
| M11 单服务器成员变更 | ✅ | 动态投票者集合(log 派生、applied-on-append);learner 追赶+自动提升;两条安全前置;自我移除退位 |
| M12 故障注入与折磨测试 | ✅ | sim nemesis(分区/丢包/延迟/重复/crash-restart)+ 并发客户端 + 重配置下复用线性一致性检查器;可复现 seed + 转储器;FaultyTransport |
| M13 自研 LSM 引擎 | ✅ | LSM-Tree(InternalKey/MemTable/WAL/SSTable/bloom/flush/compaction/MANIFEST);整套引擎无关 + RaftNode 恢复在 lsm 下通过(配置翻转、零 Raft 改动) |
| M14 基准测试 (JMH) | ✅ | RocksDB-vs-LSM put/get + log-append 延迟分位 |
| M15 可观测性 | ✅ | Metrics 门面(NoOp/Simple/Micrometer);RaftNode + LSM 指标;MDC + 状态转换日志;RaftStateDumper |
| M16 加固与文档 | ✅ | SkiffNode 完整装配(引擎可配置);RESP3 + HELLO;冷启动 README;config 校验 |
| M17 延伸:joint consensus | ✅ | 联合共识(C_{old,new})原子多服务器变更:joint 期间须 Cold 与 Cnew 双多数派;自动过渡到 Cnew |
| M18 运行时成员变更接线 + audit 加固 | ✅ | endpoint 随 CONFIG 传播 + 传输层动态 peer + join 模式 + RESP ADDNODE/REMOVENODE/MEMBERS;真实 Netty 端到端运行时增减节点;+ 全量 audit 确认项修复 |
欢迎提交 Issue 和 Pull Request!在提交 PR 前,请确保:
- 代码通过
./gradlew build全量测试。 - 新增功能包含对应的单元测试或集成测试。
- 遵循现有的代码风格与架构设计原则。
本项目基于 Apache License 2.0 开源。