Raft 共识算法 · 演示系统(多终端)

Raft 共识算法 · 演示系统(多终端)

精简版 Raft 实现,用于高级操作系统课程汇报现场演示。

每个节点作为独立进程运行在各自的终端窗口中,通过控制台统一管理。

实验介绍

为什么选择 Go

本实验参考 MIT 6.5840(原 6.824)Lab 3 的设计思路,使用 Go 语言 实现。选择 Go 的原因:

  • 天然并发模型:Go 的 goroutine + channel 是实现分布式节点间并行通信的最简洁方式------每个 RPC 调用、每次投票收集、每轮心跳发送都是一个 goroutine,代码直接映射论文描述,无需手动管理线程池
  • 标准库即够用net/rpc 提供开箱即用的 RPC 框架,net/http 提供管理 API,sync.Mutex 提供互斥锁------零外部依赖,go.mod 中没有任何第三方库
  • 编译为单二进制go build 直接生成可执行文件,无需运行时环境,适合现场演示时快速部署

架构设计

采用 多进程 + 双通道通信 架构,刻意模拟真实分布式系统的部署方式:

复制代码
┌──────────┐  RPC(TCP)  ┌──────────┐  RPC(TCP)  ┌──────────┐
│  Node 0  │◄──────────►│  Node 1  │◄──────────►│  Node 2  │
│ :9001    │            │ :9002    │            │ :9003    │
│ :8001    │            │ :8002    │            │ :8003    │
└────▲─────┘            └────▲─────┘            └────▲─────┘
     │ HTTP                  │ HTTP                  │ HTTP
     └───────────────┬───────┴───────────────────────┘
                     │
              ┌──────┴───────┐
              │  Controller  │
              │   (控制台)    │
              └──────────────┘
  • 节点间通信 :Go net/rpc over TCP------这是 Raft 论文中 RPC 的直接实现(RequestVote、AppendEntries)
  • 管理通道:每个节点额外暴露 HTTP API,供控制台查询状态、提交命令、触发快照、模拟宕机
  • 进程隔离:每个节点是独立进程,拥有独立的内存空间、独立的计时器、独立的终端输出------这不是模拟,而是真正的进程间通信

核心设计思想

  1. 论文驱动:每个函数、每个字段都能在 Raft 论文 Figure 2 / §5 / §7 中找到对应。代码中的中文注释标注了论文原文出处,便于对照阅读
  2. 可观测性优先:颜色按节点固定(而非按角色)、选举计时器可见、候选者自投票显式输出、心跳计数------所有设计决策都服务于"让观众看懂发生了什么"
  3. 最小完备:仅实现 Leader Election + Log Replication + 简化 Snapshot 三个核心子问题,总代码约 830 行,每个文件职责单一,不超过 370 行

快速启动

powershell 复制代码
cd raft-demo-v2

# 方式一:一键启动(自动编译 + 打开3个节点窗口 + 控制台)
.\demo\start_cluster.ps1

# 方式二:手动启动
go build -o node.exe ./node
go build -o controller.exe ./controller

# 终端1: Node0 (红色)
.\node.exe -id 0 -rpc-port 9001 -http-port 8001 -peers 1:9002,2:9003
# 终端2: Node1 (绿色)
.\node.exe -id 1 -rpc-port 9002 -http-port 8002 -peers 0:9001,2:9003
# 终端3: Node2 (蓝色)
.\node.exe -id 2 -rpc-port 9003 -http-port 8003 -peers 0:9001,1:9002
# 终端4: 控制台
.\controller.exe

控制台命令

命令 说明
status 查看所有节点的 Term、角色、在线状态
put x 5 向 Leader 提交 SET x 5 命令
get x 查询各节点状态机中 x 的值
kill 0 模拟 Node0 宕机(关闭进程)
log 查看各节点日志列表
snapshot 0 触发 Node0 日志压缩
snapshot-info 0 查看 Node0 快照信息
demo1 预设演示:Leader 宕机 → 重新选举
demo2 预设演示:日志复制流程
demo3 预设演示:多数节点宕机 → 集群停服

演示流程建议(15分钟)

场景1:正常选举(3min)

  1. 启动集群,观察各节点终端:选举计时器 → 超时 → 成为 Candidate → 投票给自己 → 获得多数票 → 当选 Leader
  2. 在控制台输入 status 确认角色分配

场景2:Leader 宕机重选(3min)

  1. 输入 demo1 或手动 kill <leader_id>
  2. 观察其他节点终端:心跳停止 → 计时器到期 → 发起新选举 → 新 Leader 当选
  3. 输入 status 查看新 Leader(Term +1)

场景3:日志复制(4min)

  1. 输入 demo2 或手动 put x 1put y 2
  2. 观察各节点终端:Leader 写入日志 → 发送 AppendEntries → Follower 确认 → 多数确认提交
  3. 输入 log 查看各节点日志一致,get x 查看状态机一致

场景4:日志压缩(2min)

  1. 输入 snapshot 0 触发 Node0 日志压缩
  2. 输入 log 对比:Node0 日志已截断,其他节点保留完整日志
  3. 输入 snapshot-info 0 查看快照包含的状态

场景5:多数宕机停服(3min)

  1. 输入 demo3 或手动 kill 两个节点
  2. 观察:剩余节点无法成为 Leader(无法获得多数投票)
  3. 说明 CAP 定理:一致性优先于可用性

v2 相比 v1 的改进

改进点 v1 v2
颜色方案 按角色着色(混乱) 按节点固定:Node0红, Node1绿, Node2蓝
终端架构 单进程混合输出 多终端:每个节点独立窗口 + 控制台窗口
选举可视化 无计时器信息 显示计时器超时时间和到期触发
候选者自投票 无显示 明确输出"投票给自己 (得票: 1/3, 需要: 2)"
日志压缩 未实现 snapshot 命令:截断已提交日志 + 保留快照状态
心跳日志 每次都输出 每10次输出一次,减少刷屏

代码结构

复制代码
raft-demo-v2/
├── raft/
│   ├── raft.go          # 核心结构体、状态转换、选举/心跳循环 (~370行)
│   ├── election.go      # RequestVote RPC(对应论文 Figure 2)(~140行)
│   ├── replication.go   # AppendEntries RPC + 日志提交(对应论文 Figure 2)(~240行)
│   ├── snapshot.go      # 简化版日志压缩(对应论文 §7)(~60行)
│   └── log.go           # LogEntry + Snapshot 结构体 (~24行)
├── node/
│   └── main.go          # 节点独立进程(Raft + HTTP管理API)(~170行)
├── controller/
│   └── main.go          # 控制台进程(交互式CLI)(~370行)
├── demo/
│   ├── start_cluster.ps1  # 一键启动集群
│   └── stop_all.ps1       # 停止所有节点
├── go.mod
└── README.md

关键实现对应论文

  • Figure 2 State : raft.go 中的 Raft 结构体字段
  • Figure 2 RequestVote : election.go 中的 RequestVote()startElection()
  • Figure 2 AppendEntries : replication.go 中的 AppendEntries()sendAppendEntries()
  • Figure 4 状态转换 : raft.go 中的 becomeFollower/becomeCandidate/becomeLeader
  • §5.4.2 提交规则 : replication.goupdateCommitIndex() 只提交当前 Term 的日志
  • §7 日志压缩 : snapshot.goTakeSnapshot() 截断已提交日志

截图:

代码仓库:

https://github.com/zzephyrxx/raft-demo-multinode.git

相关推荐
念恒123062 小时前
Python(while循环)
数据结构·python·算法
神奇小汤圆2 小时前
字节面试官:你知道Claude Code的多Agent实现机制吗?
算法
运筹vivo@2 小时前
LeetCode 2540. 最小公共值
算法·leetcode·职场和发展
小许同学记录成长2 小时前
轻量正射实现原理技术文档
算法·无人机
阿文的代码库2 小时前
如何在C++中使用标准库的智能指针
开发语言·c++·算法
城事漫游Molly2 小时前
方差分析(ANOVA)入门——比较三组或更多组均值的利器
大数据·算法·均值算法·论文笔记·科研统计
Yzzz-F2 小时前
[专题]最大子矩形问题
算法
WL_Aurora2 小时前
Python 算法基础篇之查找算法(三):树表查找
python·算法
吃好睡好便好3 小时前
在Matlab中绘制二维直方图
开发语言·人工智能·学习·算法·matlab