Thanks to visit codestin.com
Credit goes to github.com

Skip to content

juanjuandog/Mini-RaftKV

Repository files navigation

Mini-RaftKV:基于 Raft 协议的分布式 KV 存储系统

这是一个使用 Go 语言实现的分布式 KV 存储项目。系统通过 Raft 协议在多个节点之间复制日志,保证多数派节点提交后的数据一致。

项目功能

本项目已经实现以下能力:

  • Raft 三种角色:FollowerCandidateLeader
  • Leader 选举、随机选举超时、投票机制、心跳维持
  • 日志复制和 commitIndex 推进
  • 基于 prevLogIndexprevLogTerm 的日志一致性校验
  • Leader 通过 nextIndex 回退机制修复 Follower 落后或冲突日志
  • KV 状态机接口:GetPutDelete
  • 写请求先进入 Raft 日志,多数派提交后再应用到 KV 状态机
  • 基于 clientId + requestId 的客户端请求去重,避免重试导致重复写入
  • 使用 bbolt 做持久化,保存 term、vote、log、snapshot 等状态
  • Snapshot 快照和日志截断,降低日志膨胀
  • gRPC 服务接口
  • Protobuf 接口定义
  • Prometheus 指标暴露
  • 内置节点状态页面,可在浏览器查看角色、Leader、term、commitIndex 和 Snapshot 状态
  • 客户端命令行工具 raftkvctl
  • YAML 配置文件启动
  • Docker Compose 一键启动三节点集群和 Prometheus
  • Follower 自动转发读写请求到 Leader,避免读旧副本
  • Leader 读请求先确认多数派心跳,再读取状态机,提供更强的读一致性
  • 写请求必须多数派提交后才返回成功,少数派不可提交
  • 单元测试覆盖选主、复制、删除、冲突修复、重复请求、重启恢复、Leader 故障、网络分区和 Snapshot
  • 真实 TCP gRPC 集成测试覆盖客户端读写和 Follower 转发
  • 端到端 demo 脚本覆盖启动集群、读写、幂等请求和 Leader 故障恢复
  • GitHub Actions 自动运行测试和构建
  • 简单压测脚本和 Grafana Dashboard 示例

技术栈

  • Go
  • gRPC
  • Protobuf
  • bbolt
  • Prometheus

目录说明

api/raftkv.proto

Protobuf 接口定义,描述 Raft 节点之间通信和客户端 KV 操作的 RPC 接口。

cmd/raftkv/

节点启动入口。运行这个目录下的程序可以启动一个 RaftKV 节点。

cmd/raftkvctl/

客户端命令行工具,可以执行 putgetdelete

configs/

本地和 Docker 环境下的节点配置文件。

deploy/

Prometheus 配置文件。

deploy/grafana-dashboard.json

Grafana Dashboard 示例,可以导入后观察提交日志、应用日志、角色变化和复制错误。

scripts/

本地一键启动三节点集群脚本。

internal/raft/

Raft 核心实现,包括选举、日志复制、状态机、持久化、快照和测试。

internal/server/

gRPC 服务层,把外部 RPC 请求转成 Raft 节点内部调用。

internal/pb/

api/raftkv.proto 生成的标准 Protobuf 和 gRPC Go 代码。

运行测试

在项目根目录执行:

go test ./...

如果测试通过,会看到类似输出:

ok   github.com/juanjuandog/mini-raftkv/internal/raft
ok   github.com/juanjuandog/mini-raftkv/internal/server

其中:

  • internal/raft 测试 Raft 核心算法
  • internal/server 测试真实 TCP gRPC 集群和 Follower 读写请求转发

重新生成 Protobuf 代码

项目使用 buf 调用 protoc-gen-goprotoc-gen-go-grpc 生成代码:

make proto

生成目标:

internal/pb/raftkv.pb.go
internal/pb/raftkv_grpc.pb.go

编译项目

make build

编译后会生成:

bin/raftkv
bin/raftkvctl

启动 3 个本地节点

方式一:使用 Makefile

分别打开三个终端:

make run-n1
make run-n2
make run-n3

方式二:使用一键脚本

make run-cluster

这个命令会同时启动 n1、n2、n3,并把日志写到:

logs/n1.log
logs/n2.log
logs/n3.log

方式三:手动启动

也可以打开三个终端,分别执行下面三个命令。

启动节点 n1:

go run ./cmd/raftkv \
  -config configs/n1.yaml

启动节点 n2:

go run ./cmd/raftkv \
  -config configs/n2.yaml

启动节点 n3:

go run ./cmd/raftkv \
  -config configs/n3.yaml

三个节点启动后,会自动进行 Leader 选举。

使用客户端读写数据

启动集群后,可以用 raftkvctl 写入数据:

go run ./cmd/raftkvctl -addr 127.0.0.1:7001 put name raftkv

读取数据:

go run ./cmd/raftkvctl -addr 127.0.0.1:7002 get name

删除数据:

go run ./cmd/raftkvctl -addr 127.0.0.1:7003 delete name

即使请求打到 Follower,读写请求也会根据已知 Leader 自动转发。

如果要验证幂等去重,可以重复使用同一个 clientrequest

go run ./cmd/raftkvctl -addr 127.0.0.1:7001 -client c1 -request 100 put idempotent first
go run ./cmd/raftkvctl -addr 127.0.0.1:7001 -client c1 -request 100 put idempotent second
go run ./cmd/raftkvctl -addr 127.0.0.1:7001 get idempotent

结果仍然应该是:

first

查看集群状态

除了浏览器状态页面,也可以直接用客户端查看节点状态:

go run ./cmd/raftkvctl status -http 127.0.0.1:9001

查看三节点集群:

go run ./cmd/raftkvctl cluster -http-addrs 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003

持续刷新,适合观察 Leader 切换:

go run ./cmd/raftkvctl watch -http-addrs 127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003

输出会包含节点地址、节点 ID、角色、Leader、term、commitIndex、lastApplied 和 snapshotIndex。

运行端到端 Demo

make demo

这个脚本会自动完成:

  • 编译 raftkvraftkvctl
  • 启动本地三节点集群
  • 执行 put/get/delete
  • 验证重复请求不会覆盖第一次写入
  • 杀掉当前 Leader
  • 等待重新选主
  • 在新 Leader 上继续写入

运行期间可以打开浏览器查看节点状态:

http://127.0.0.1:9001/debug/ui
http://127.0.0.1:9002/debug/ui
http://127.0.0.1:9003/debug/ui

也可以直接查看 JSON 状态接口:

http://127.0.0.1:9001/debug/status

日志会输出到:

logs/n1.log
logs/n2.log
logs/n3.log

运行 Leader 故障恢复演示

如果只想重点演示故障恢复,可以运行:

make failover-demo

这个脚本会自动完成:

  • 启动三节点集群
  • 发现当前 Leader
  • 写入一条数据
  • 杀掉 Leader
  • 等待新 Leader 选出
  • 继续写入新数据
  • 验证故障前后的数据仍然可以读取

运行时可以同时打开节点状态页面,观察 Leader 和 term 的变化。

简单压测

集群启动后可以执行:

make bench

默认向 127.0.0.1:7001 写入 100 个 key。可以通过环境变量调整:

ADDR=127.0.0.1:7002 N=1000 make bench

输出示例:

addr=127.0.0.1:7001 requests=100 ok=100 fail=0 seconds=1 qps=100

使用 Docker Compose 启动

make docker-up

这会启动:

  • n1
  • n2
  • n3
  • Prometheus

停止并清理:

make docker-down

Prometheus 页面:

http://127.0.0.1:9090

查看 Prometheus 指标

启动节点后,可以在浏览器访问:

http://127.0.0.1:9001/metrics
http://127.0.0.1:9002/metrics
http://127.0.0.1:9003/metrics

目前暴露的指标包括:

  • Raft 角色切换次数
  • 已提交日志数量
  • 已应用日志数量
  • Snapshot 生成次数
  • 日志复制失败次数

查看节点状态页面

项目内置了一个轻量的节点状态页面,不需要额外前端服务:

http://127.0.0.1:9001/debug/ui

页面会自动刷新并展示:

  • 当前节点 ID
  • 当前角色:Follower、Candidate 或 Leader
  • 当前 Leader
  • currentTerm
  • commitIndex
  • lastApplied
  • lastLogIndex / lastLogTerm
  • snapshotIndex / snapshotTerm
  • peer 的 nextIndex / matchIndex

如果只想看 JSON,可以访问:

http://127.0.0.1:9001/debug/status

一致性语义

写请求不会直接修改本地 KV,而是先追加到 Leader 的 Raft 日志。只有当日志被多数派复制并提交后,才会按顺序 apply 到状态机并返回成功。

读请求也不会从任意 Follower 直接读取。Follower 收到读请求会转发给 Leader;Leader 在读取本地状态机前,会先向多数派发送心跳确认自己仍然拥有领导权。如果 Leader 被网络隔离到少数派,读写都会返回 quorum unavailable,避免读到旧 Leader 的过期数据。

对应测试位于:

internal/raft/cluster_test.go

重点测试包括:

  • TestLeaderRequiresQuorumToCommitWrites
  • TestLinearizableReadRequiresQuorum
  • TestLinearizableReadSucceedsWithMajority

实现说明

Raft 核心

Raft 相关逻辑集中在:

internal/raft/node.go
internal/raft/types.go

节点在 FollowerCandidateLeader 三种状态之间切换。选举过程使用随机超时、投票请求和日志新旧比较;Leader 定期发送心跳,并通过 commitIndex 推进已提交日志。

日志复制和冲突修复

Leader 复制日志时会带上 prevLogIndexprevLogTerm。Follower 如果发现前置日志不匹配,会拒绝本次追加;Leader 根据响应回退对应节点的 nextIndex,直到找到双方一致的位置,再继续复制后续日志。

相关代码:

internal/raft/node.go

KV 状态机

KV 状态机位于:

internal/raft/state_machine.go

PutDelete 请求会先进入 Raft 日志,只有日志被多数派提交后才会应用到状态机。Get 请求由 Leader 在确认多数派后读取本地状态机;Follower 收到客户端请求时会转发给已知 Leader。

请求幂等

写请求携带 clientIdrequestId。状态机会记录每个客户端已经处理过的请求,重复请求会直接返回之前的处理结果,避免客户端重试造成重复写入。

持久化和 Snapshot

持久化实现位于:

internal/raft/storage.go

节点会保存 termvote、日志和 Snapshot 状态。Snapshot 生成后会截断已经包含在快照中的旧日志,重启时可以从持久化状态恢复已提交数据。

测试覆盖

核心测试位于:

internal/raft/cluster_test.go
internal/server/grpc_integration_test.go

覆盖的场景包括选主、日志复制、日志冲突修复、客户端请求去重、Leader 故障恢复、Follower 重启追赶、Snapshot 安装、少数派不可提交,以及真实 gRPC 读写转发。

Protobuf 说明

api/raftkv.proto 是项目的 RPC 契约。当前仓库已经使用 buf 和 Go Protobuf 插件生成了标准代码:

internal/pb/raftkv.pb.go
internal/pb/raftkv_grpc.pb.go

重新生成命令:

make proto

About

基于 Go + Raft + gRPC + Protobuf + bbolt + Prometheus 实现的分布式 KV 存储系统,支持 Leader 选举、日志复制、强一致读写、持久化恢复、Snapshot 日志压缩和故障测试。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages