精解计算机系统课程
MIT 分布式系统(一)项目概览与构建测试 #
作为系列的开篇,我们将介绍 raft_cpp 项目——一个使用 C++17 实现的 MIT 6.824 分布式系统课程 Labs。本项目涵盖了 Raft 共识算法、基于 Raft 的 KV 存储、分片配置管理和分片 KV 存储四大模块,完整对应 MIT 6.824 的 Lab 2/3/4。本书还在不断完善中,评论区是放开的,欢迎大家讨论参与本书的优化。
项目介绍
raft_cpp 是一个用 C++17 编写的 MIT 6.824 分布式系统 Labs 实现。与 Go 语言版本的 Lab 不同,本项目使用 C++ 标准库提供的并发原语(std::thread、std::mutex、std::condition_variable)替代 Go 的 goroutine 和 channel,使用自定义二进制序列化替代 protobuf(网络传输层可选使用 gRPC),使用 RocksDB 作为底层存储引擎。
项目包含四大模块,与 MIT 6.824 Lab 的对应关系如下:
| 模块 | 对应 Lab | 核心文件 | 功能 |
|---|---|---|---|
| Raft 核心 | Lab 2 (2A/2B/2C/2D) | raft.h/cpp, types.h, util.h, persister.h | Leader 选举、日志复制、持久化、快照 |
| KVRaft | Lab 3 | kvserver.h/cpp, kvcommon.h, kvstatemachine.h, clerk.h | 基于 Raft 的强一致 KV 存储 |
| ShardCtrler | Lab 4A | shardctrler.h/cpp, sc_common.h, sc_clerk.h, sc_statemachine.h | 分片配置管理服务器 |
| ShardKV | Lab 4B | shardkv.h/cpp, skv_common.h, skv_clerk.h | 分片 KV 存储与迁移 |
架构概览
下图展示了整个系统的架构设计:
系统中包含以下角色和概念:
1.Client(客户端):用户接入端,通过 Clerk 客户端与系统交互。
2.ShardCtrler(配置服务器):系统的配置管理中心,维护集群服务分组与分片(Shard)的映射关系。客户端访问数据前需要先查询配置信息。
3.ShardKV(数据服务器):系统中实际存储用户数据的服务器。每个分组负责一部分分片的数据。
4.Shard(分片):数据管理的逻辑单元。系统中有 NShards(默认 10)个分片,通过 key2shard 哈希函数将 key 映射到分片。
请求处理流程
以一个 put testkey testvalue 请求为例,处理流程如下:
1.客户端启动时从 ShardCtrler 拉取最新配置信息,缓存到本地。
2.收到 put 请求后,计算 key2shard("testkey") 得到分片 ID,从配置表中找到负责该分片的服务器分组。
3.客户端将请求发送到对应分组的 ShardKV 服务器。
4.Leader ShardKV 收到请求后,将操作序列化并提交到 Raft 共识层。Raft 通过 AppendEntries RPC 将日志复制到 Follower,获得多数派确认后提交。
5.applier 线程从 Raft 的 applyCh 读取已提交的消息,应用到状态机,然后通过 notifyChan 通知等待中的请求处理函数,返回结果给客户端。
代码目录结构
raft_cpp/
├── CMakeLists.txt # CMake 构建配置
├── include/
│ └── raft/
│ ├── raft.h # Raft 核心类定义
│ ├── raft_peer.h # Raft 节点抽象接口
│ ├── types.h # Raft 消息类型定义
│ ├── util.h # Timer、BlockingQueue 工具类
│ ├── persister.h # 持久化存储
│ ├── config.h # 测试框架(Config 类、InMemPeer)
│ ├── kvcommon.h # KV 操作类型与序列化
│ ├── kvstatemachine.h # 状态机接口与 RocksDBKV
│ ├── kvserver.h # KVServer 类定义
│ ├── clerk.h # KV 客户端
│ ├── sc_common.h # 配置服务器类型与序列化
│ ├── sc_statemachine.h # 配置状态机
│ ├── sc_clerk.h # 配置客户端
│ ├── shardctrler.h # 配置服务器类定义
│ ├── skv_common.h # 分片 KV 类型与序列化
│ ├── skv_clerk.h # 分片 KV 客户端
│ └── shardkv.h # 分片 KV 类定义
├── src/
│ ├── raft.cpp # Raft 核心实现
│ ├── util.cpp # 工具类实现
│ ├── persister.cpp # 持久化实现
│ ├── config.cpp # 测试框架实现
│ ├── kvserver.cpp # KVServer 实现
│ ├── kvstatemachine.cpp # 状态机实现
│ ├── clerk.cpp # KV 客户端实现
│ ├── shardctrler.cpp # 配置服务器实现
│ ├── sc_statemachine.cpp # 配置状态机实现
│ ├── sc_clerk.cpp # 配置客户端实现
│ ├── shardkv.cpp # 分片 KV 实现
│ ├── skv_clerk.cpp # 分片 KV 客户端实现
│ ├── grpc_client.cpp # gRPC 客户端(可选)
│ └── grpc_server.cpp # gRPC 服务端(可选)
├── proto/
│ ├── raft.proto # Raft RPC 定义
│ ├── shardctrler.proto # 配置服务器 RPC 定义
│ └── shardkv.proto # 分片 KV RPC 定义
├── test/
│ ├── test_main.cpp # Lab 2 测试(选举/复制/持久化/快照)
│ ├── kvtest_main.cpp # Lab 3 测试(KV 系统)
│ ├── sctest_main.cpp # Lab 4A 测试(配置服务器)
│ └── skvtest_main.cpp # Lab 4B 测试(分片 KV)
└── doc/
└── raft-core-tests.md # 测试说明文档
构建方法
raft_cpp 使用 CMake 构建,依赖以下组件:
必需依赖:CMake 3.16+、C++17 编译器、GoogleTest
可选依赖:RocksDB(用于 KV Raft 的存储引擎)、gRPC + Protobuf(用于网络传输层)
在 macOS 上安装依赖:
# 安装依赖
brew install cmake googletest
# 可选:安装 RocksDB 和 gRPC
brew install rocksdb grpc
在 Linux(Ubuntu/Debian)上安装依赖:
# 安装依赖
sudo apt install cmake build-essential libgtest-dev
# 可选:安装 RocksDB 和 gRPC
sudo apt install librocksdb-dev libgrpc-dev libprotobuf-dev
构建项目:
git clone https://github.com/eraft-io/eraft-io.github.io.git
cd eraft-io.github.io/raft_cpp
mkdir build && cd build
cmake ..
make -j$(nproc)
CMake 构建系统会自动检测已安装的依赖,根据检测结果决定编译哪些模块:
- 找到 GoogleTest 时,构建 raft_test(Lab 2 测试)和 shardctrler_test(Lab 4A 测试)和 shardkv_test(Lab 4B 测试)。
- 同时找到 GoogleTest 和 RocksDB 时,额外构建 kvraft_test(Lab 3 测试)。
- 找到 gRPC 时,启用 gRPC 网络传输层(编译 grpc_client.cpp 和 grpc_server.cpp)。
- 未找到某个可选依赖时,对应的模块会被跳过并输出提示信息。
运行测试
构建完成后,使用 CTest 运行所有测试:
# 在 build 目录下运行所有测试
ctest --output_on_failure
# 或直接运行单个测试二进制
./raft_test # Lab 2:Raft 核心测试
./kvraft_test # Lab 3:KV Raft 测试(需要 RocksDB)
./shardctrler_test # Lab 4A:配置服务器测试
./shardkv_test # Lab 4B:分片 KV 测试
各测试覆盖的内容如下:
| 测试 | 测试用例 | 验证内容 |
|---|---|---|
| raft_test (Lab 2) | 2A: InitialElection, ReElection, ManyElections | Leader 选举:初始选举、重新选举、多轮随机选举 |
| 2B: BasicAgree, FailAgree, FailNoAgree, Rejoin, Backup | 日志复制:基本复制、分区下复制、多数派丢失、旧 Leader 回归 | |
| 2C: Persist1, Figure8, UnreliableAgree | 持久化:重启恢复、Figure 8 极端场景、不可靠网络下一致性 | |
| 2D: SnapshotBasic, SnapshotInstall, SnapshotInstallUnreliable, SnapshotInstallCrash | 快照:基本快照、断连后安装快照、不可靠网络下安装、崩溃恢复后安装 | |
| kvraft_test (Lab 3) | 基本 KV 操作、持久化、快照、去重 | 基于 Raft 的 KV 存储系统:Put/Get/Append、客户端重试去重、快照 |
| shardctrler_test (Lab 4A) | Join/Leave/Move/Query | 配置服务器:分组加入/离开、分片迁移、配置查询 |
| shardkv_test (Lab 4B) | 分片迁移、GC、并发配置更新 | 分片 KV:数据迁移、垃圾回收、多分组协作 |
测试框架说明
raft_cpp 的测试框架定义在 config.h 中。Config 类模拟了一个 Raft 集群,通过 InMemPeer 在内存中模拟节点间的网络通信(而非使用真实的 gRPC),这样可以快速运行测试而无需启动网络服务。InMemPeer 支持模拟网络分区(断开连接)、消息丢失和延迟,用于测试 Raft 在各种异常场景下的正确性。
// config.h — 测试框架核心
class Config {
public:
// 创建一个 N 节点的 Raft 集群
void makeConfig(int n, bool unreliable, std::optional<int> maxRaftState = std::nullopt);
// 连接/断开节点
void connect(int i); // 连接节点 i
void disconnect(int i); // 断开节点 i
// 连接/断开节点对
void connectOne(int i, int j);
void disconnectOne(int i, int j);
// 检查是否只有一个 Leader
int checkOneLeader();
// 检查没有 Leader
void checkNoLeader();
// 等待并获取 N 个节点的术语值
std::vector<int> checkTerms();
};
// InMemPeer — 内存网络模拟
class InMemPeer : public RaftPeer {
// 直接调用 Raft 的 HandleRequestVote/HandleAppendEntries/HandleInstallSnapshot
// 支持 enabled_ 标志来模拟网络断开
};
这种内存网络模拟的测试方式与 MIT 6.824 Go 版本 Lab 的测试框架设计完全一致,确保了测试的可靠性和可重复性。
捐赠
整理这本书耗费了我们大量的时间和精力。如果你觉得有帮助,一瓶矿泉水的价格支持我们继续输出优质的分布式存储知识体系,2.99¥,感谢大家的支持。
开源协议#
遵循MIT协议开源。
感谢 「赫蹏」 提供如此优秀的中文排版系统