Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Skiff: Raft-from-scratch Distributed KV Store

A distributed storage framework based on the Raft consensus algorithm, implemented in Java.

JDK Consensus License

基于 Raft 共识算法的分布式存储框架,使用 Java 实现。当前提供高可用的键值存储(Key-Value Store),并计划后续支持元数据管理、配置服务等更多分布式系统组件。

本项目采用 Apache License 2.0 开源,欢迎社区参与维护与贡献。

✨ Features

  • 完整 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 基准测试套件。

🏗️ Architecture

自顶向下分为四层 + 两条旁路: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
Loading

📝 架构说明

  1. 客户端入口旁路(两条独立路径):redis-cli 通过 RESP2/RESP3 接入 RESP 协议前端层(端口 6380);SkiffClient 通过 Netty 长度前缀 protobuf 直连 Raft 端口(7001+)的 ClientRpcService,不经过 RESP 层。
  2. RESP 协议前端层:负责 RESP2/RESP3 编解码、命令解析与分发,对非 Leader 请求返回文本协议的 -MOVED host:port 重定向。
  3. Raft RPC 服务层ClientRpcService(由 KvCommandService 实现)接收 SkiffClient 的 protobuf 命令并分发;对非 Leader 请求返回 RESULT_NOT_LEADER + leaderHint,由 SkiffClient 的 LeaderCache 在客户端侧完成重定向和重试,不是 -MOVED 文本重定向。
  4. 命令/KV 状态机层:处理 GET、SET、DEL、INCR 等命令;写请求通过 propose 进入 Raft,读请求通过 ReadIndex 保证线性一致性。两条入口路径最终都汇入这一层。
  5. Raft 共识层:负责 Leader 选举、日志复制、提交、快照、成员变更和线性一致读确认,但不直接操作 KV 数据。
  6. 状态机应用路径:已提交 entry 由 apply 线程交给状态机,状态机再将 KV 数据和 lastApplied 在同一个 WriteBatch 中写入存储引擎。
  7. 存储引擎层:通过 StorageEngine SPI 支持 RocksDB 和自研 LSM;Raft 层同时持久化 raft-log 和 raft-meta。
  8. 节点间通信旁路:Raft 层通过 RaftTransport(SimTransport 或 NettyRaftTransport)发送 requestVote、appendEntries、installSnapshot,与对等节点完成复制和恢复。

🚀 Quick Start

Prerequisites

  • JDK 17:用于编译/运行。

    ⚠️ gradle.properties 默认把 Gradle toolchain 指向 JDK 17 所在路径,请修改该配置。

  • 存储引擎
    • 默认 rocksdbWindows 需安装 MSVC C++ redistributable,建议数据目录路径保持简短。
    • 也可在配置里设 node.storageEngine=lsm 使用纯 Java 的自研 LSM 引擎——无任何 native 依赖、跨平台、零额外前置。

Build & Test

./gradlew -q javaToolchains   # sanity:应列出 JDK 17(build 前先跑)
./gradlew build               # 编译全部模块 + 跑测试
./gradlew :skiff-it:test      # 仅集成 / 冷启动测试
./gradlew :skiff-bench:jmh    # RocksDB-vs-LSM 基准

Cold Start (3-Node Cluster)

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=truecluster.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 端到端验证。

📦 Modules

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 基准测试

Milestones

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 确认项修复

🤝 Contributing

欢迎提交 Issue 和 Pull Request!在提交 PR 前,请确保:

  1. 代码通过 ./gradlew build 全量测试。
  2. 新增功能包含对应的单元测试或集成测试。
  3. 遵循现有的代码风格与架构设计原则。

📄 License

本项目基于 Apache License 2.0 开源。

About

A Java-based distributed storage framework powered by Raft consensus, featuring a fault-tolerant Key-Value store and extensible distributed services.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages