精解计算机系统课程

  1. MIT6.824 分布式系统系列

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 的测试框架设计完全一致,确保了测试的可靠性和可重复性。

  1. MIT6.824 分布式系统系列

捐赠

整理这本书耗费了我们大量的时间和精力。如果你觉得有帮助,一瓶矿泉水的价格支持我们继续输出优质的分布式存储知识体系,2.99¥,感谢大家的支持。

下载本书 PDF 版

开源协议#

遵循MIT协议开源。

感谢 「赫蹏」 提供如此优秀的中文排版系统

本站总访问量