toyDB 架构总览:基于 Raft 共识与 MVCC 的分布式 SQL 数据库组件全景
【免费下载链接】toydbDistributed SQL database in Rust, written as an educational project项目地址https://gitcode.com/gh_mirrors/to/toydb点击查看免费下载toyDB 是一个用 Rust 编写的分布式 SQL 数据库以教学项目的形式展示真实分布式数据库的核心架构一个由节点集群构成的复制状态机replicated state machine客户端可连接任意节点执行 SQL 事务。本文将自底向上剖析 toyDB 的五大核心组件——存储引擎、Raft 共识引擎、SQL 引擎、服务端与客户端——并说明其设计目标、明确的非目标、配置方式与快速上手的运行方法帮助读者建立对分布式 SQL 数据库整体架构的系统认知。核心思想节点集群上的复制状态机toyDB 的本质是一个在节点集群上运行的复制状态机每个节点都执行 SQL 事务数据通过 Raft 共识协议在节点间复制。客户端可以连接到集群中的任意节点提交 SQL 语句无论它连接的是否是当前领导者。集群的可用性遵循多数派quorum原则少数节点宕机或断连时集群仍然可用——例如 5 节点集群可容忍 2 个节点失效多数节点失效时集群停止服务——这是为了保障一致性与持久性避免出现双主split brain等脑裂场景。从源码结构看这一模型对应 src/raft/mod.rs 中的raft::Node以及 src/server.rs 中的toydb::ServerServer将网络层收到的 SQL 请求与 Raft 节点消息统一路由给内层 Raft 节点再由 Raft 节点驱动状态机执行命令。设计目标与非目标明确取舍的教学定位toyDB 的架构文档docs/architecture/overview.md开篇就明确了它的目标属性与非目标这是理解其设计取舍的关键。目标属性属性说明底层实现分布式Distributed运行在节点集群之上Raft 集群拓扑见 cluster/toydb1/toydb.yaml 中的peers配置高可用Highly available容忍少数节点故障Raft 多数派提交与领导选举SQL 兼容SQL compliant正确支持大多数常见 SQL 特性sql 模块 的解析、规划、优化、执行流水线强一致Strongly consistent已提交的写入对所有读者立即可见线性一致性读请求仅在领导者执行并经过读仲裁见 raft.md 的 Read Processing 一节事务性Transactional提供 ACID 事务MVCC 快照隔离见 mvcc.md其中 ACID 四个字母逐一对应Atomic原子性一组写入作为一个原子单元在同一时刻提交生效其他用户不会看到部分写入Consistent一致性数据库约束与引用完整性始终被强制由 SQL 执行层实现Isolated隔离性并发事务互不影响采用快照隔离冲突事务需要重试Durable持久性已提交的写入永不丢失。明确的非目标为了保持简单且可理解toyDB 明确声明以下方面不是它的目标不可扩展每个节点都存储完整数据集读写都在单个节点上执行不可靠只处理崩溃crash故障不处理网络分区、节点停滞等复杂故障性能差数据处理慢没有任何优化不高效整表加载进内存无压缩、无垃圾回收等功能不全仅实现基础 SQL 功能不向后兼容数据格式与协议变更会破坏旧数据库不灵活运行中不能增删节点新节点加入耗时长不安全没有认证、授权与加密。这一取舍与 README 中的定位一致toyDB 的意图是简单、可理解同时功能正确而性能、可扩展性、可用性等生产级数据库中最复杂的部分被刻意排除以免掩盖底层基础概念。五大核心组件总览toyDB 内部由五个主要组件构成架构文档给出的对应关系如下组件职责仓库位置存储引擎Storage engine数据落盘与事务管理src/storageRaft 共识引擎Raft consensus engine数据复制与集群节点协调src/raftSQL 引擎SQL engine组织 SQL 数据、管理会话、执行语句src/sql服务端Server管理网络通信SQL 客户端与 Raft 节点两侧src/server.rs客户端Client提供 SQL 用户界面并与服务端通信src/client.rs单节点内部结构如下图所示即 docs/architecture/images/architecture.svg下面按照架构文档自底向上的顺序逐一展开。存储引擎可插拔的有序键值存储存储引擎位于 src/storage 模块是一个嵌入式键值存储将任意键值以二进制字节串存储。它只提供简单的 set/get/delete 操作本身不提供事务——事务在其之上由 MVCC 层构建。两个关键特性奠定了上层一切能力的基础键按字典序排序支持范围扫描range scan可在两个键之间或按指定前缀迭代所有键值对——扫描某 SQL 表的全部行、扫描 MVCC 某键的全部版本、扫描 Raft 日志尾部都依赖这一能力可插拔实现用户可在配置文件中选择具体引擎。所有实现都遵循storage::Enginetrait见 src/storage/engine.rs 的trait Engine定义其核心方法包括get、set、delete、scan、scan_prefix、flush。Memory 存储引擎storage::Memory是最简实现直接用 Rust 标准库的BTreeMap在内存中存放数据不落盘主要用于测试。它完整代码仅约 70 行见 src/storage/memory.rs是理解Enginetrait 的最小范例。BitCask 存储引擎storage::BitCask是主用引擎是 BitCask单一追加写日志文件写键值对即追加到文件末尾删除键则追加特殊墓碑值tombstone读时使用内存KeyDir索引将键映射到其最新值在文件中的位置因此所有键必须能放进内存键值对文件格式依次为键长度大端u324 字节、值长度大端i324 字节-1 表示墓碑、二进制键n 字节、二进制值n 字节。例如foobar的十六进制表示为00000003 00000003 666f6f 626172日志即 WAL因为数据文件本身就是追加日志无需单独的预写日志write-ahead log即可做崩溃恢复启动时全量扫描重建KeyDir随后按需压缩启动时将每个存活键值对的最新值写入新文件并替换旧文件且按排序序写入以加速后续扫描对应配置项compact_threshold与compact_min_bytes。存储引擎不关心键值内容的语义——SQL 表与行如何映射到键值结构由上层 SQL 存储层负责。Raft 共识引擎线性化状态机复制Raft 是一种分布式共识协议在节点集群间以一致且持久的方式复制数据与 Paxos、Viewstamped Replication 同源但被设计得更简单、可理解、实用广泛用于 CockroachDB、TiDB、etcd、Consul 等工业系统。toyDB 的 Raft 实现位于 src/raft 模块实现了论文中的最简子集刻意省略了状态快照、日志截断、领导者租约等优化。其协议要点详见 raft.md领导者选举Raft 选举一个领导者节点协调写入并复制给追随者一旦多数50%节点确认写入即视为持久提交。领导者通常也服务读请求因为它始终持有最新数据从而保证强一致多数派仲裁只有多数节点存活并连通时集群才可用否则不提交写入——由于集群中只能有一个多数派这杜绝了并发双主与分叉写入有序命令日志领导者将写入追加到有序命令日志并复制给追随者多数节点复制到某条目后该前缀被提交并应用到状态机保证所有节点按相同顺序应用相同命令并最终收敛到相同状态命令需确定性术语term机制时间被划分为单调递增的 term每个 term 至多一个领导者term 永不回退且每个 term 每节点只能投一票term 与投票都持久化到磁盘。关键实现细节日志存储raft::Log将日志条目raft::Entry含index、term、command存放在storage::Engine键值存储中同时用独立键保存当前 term、投票与提交索引见 src/raft/log.rs。Log::append追加单条、Log::splice批量追加并可替换未提交条目、Log::commit标记提交使其不可变状态机接口raft::Statetrait 通过get_applied_index询问最后应用的条目、apply应用新提交条目。状态机允许在崩溃后回退通过重放未应用日志追赶它必须保证确定性不能读取当前时间或生成随机数这类值必须包含在命令中非确定性错误如 IO 错误会直接 panic 崩溃节点节点角色raft::Node枚举包含Follower、Candidate、Leader三种状态内部使用RawNodeRole泛型typestate 模式将状态转换不变量在编译期通过类型系统强制例如只有RawNodeCandidate才有into_leader()方法。角色转换如图docs/architecture/images/raft-states.svg节点驱动模型raft::Node只有两个主方法——tick()推进逻辑时间toyDB 每 100 ms tick 一次见 src/raft/mod.rs 中TICK_INTERVAL Duration::from_millis(100)step()处理入站消息。整个模型完全同步且确定性同一初始状态下的相同调用序列必然产生相同结果非常便于测试与理解协议参数见 src/raft/mod.rs心跳间隔HEARTBEAT_INTERVAL 4ticks约 400 ms选举超时在ELECTION_TIMEOUT_RANGE 10..20ticks即 1~2 秒内随机选取避免所有节点同时发起竞选导致选举平局单个 append 消息最多MAX_APPEND_ENTRIES 100条写入复制领导者将命令追加到本地日志并发送Message::Append给追随者追随者确认后领导者更新其match_index一旦达到多数派即提交并应用到状态机同时把结果作为Message::ClientResponse回给客户端提交索引通过下一次心跳传播给追随者线性化读取读请求只在领导者执行但领导者必须向追随者发送Message::Read含读序号确认自己仍是当前 term 的领导者多数派确认后才执行读——这额外增加一次网络往返真实系统通常改用领导者租约优化但对 toyDB 而言足够。完整的协议行为由 src/raft/testscripts/node 下数十个测试脚本覆盖如election、election_tie、election_contested、append、heartbeat_commits_follower、request_leader_read_quorum等是学习 Raft 各种场景的最佳素材。需要注意Raft 解决的是复制与可用性问题而非扩展性问题——所有读写都经领导者、每个节点存储完整数据集。真实系统通过分片sharding将大数据集拆分到多个独立 Raft 集群实现水平扩展但这是 toyDB 明确排除的范围。SQL 引擎从词法到执行的完整流水线SQL 引擎是数据库的主要接口基于键值存储、MVCC 事务与 Raft 复制构建位于 src/sql 模块。它由若干独立组件组成一条处理流水线详见 sql.mdClient → Session → Lexer → Parser → Planner → Optimizer → Executor → Storage数据模型SQL 表、行与类型定义在 src/sql/types包含数据类型、Schema 与表达式解析Lexer词法分析、Parser语法分析并生成抽象语法树AST见 src/sql/parser规划Planner将 AST 转为执行计划含作用域与名称解析见 src/sql/planner/planner.rs优化Optimizer实现启发式优化包括常量折叠constant folding、过滤条件下推filter pushdown、索引查找index lookups、哈希连接hash join与短路求值short circuiting见 src/sql/planner/optimizer.rs执行Executor基于迭代器模型执行计划Session管理会话状态见 src/sql/execution事务与复制底层由 MVCCsrc/storage/mvcc.rs提供快照隔离事务由sql::engine::Raftsrc/sql/engine/raft.rs将 SQL 读写请求提交给 Raft 节点复制执行。整个 SQL 引擎由 src/sql/testscripts 下的脚本系统测试覆盖表达式、优化器、查询、Schema、事务与写入等场景例如queries/join_inner、queries/aggregate、transactions/isolation等。服务端把各组件串起来的网络中枢toydb::Serversrc/server.rs负责把前述所有组件串起来。它包裹一个内层 Raft 节点raft::Node管理 SQL 状态机并负责在 Raft 节点、Raft 对等节点与 SQL 客户端之间路由网络流量。网络协议使用 Bincode 编码经 TCP 传输无需额外帧格式Bincode 根据目标类型自行确定每个消息的字节数服务端不使用 async Rust / Tokio而是普通 OS 线程配合 Crossbeam channels 在线程间传递消息——这刻意避免了异步复杂性对核心概念的遮蔽监听端口Server::serve()为 Raft 对等节点监听 Raft 端口如 9705、为 SQL 客户端监听 SQL 端口如 9605并为每个连接派生处理线程src/server.rsRaft 路由核心是Server::raft_route()线程——周期性tick()Raft 节点、将入站 Raft 对等消息step()进节点、将出站消息发送给对等节点同时接收来自sql::engine::Raft的客户端请求并转发响应。节点启动时为每个 Raft 对等节点派生raft_send_peer()线程持续尝试 TCP 连接并发送出站消息另由raft_accept()监听入站 Raft 连接、raft_receive_peer()读取对等消息SQL 服务使用toydb::Request/toydb::Response枚举作为客户端协议同样 Bincode over TCP。Request::Execute在sql::execution::Session上执行 SQL 语句并返回StatementResult为每个客户端连接创建独立会话并派生sql_session()线程服务toydb 二进制src/bin/toydb.rs 是toydb::Server的薄封装先解析toydb.yaml配置再初始化 Raft 日志存储与 SQL 状态机最后启动服务端。客户端程序化 API 与 REPL 界面客户端模块src/client.rs使用与服务端相同的 Bincode 协议发送toydb::Request并接收toydb::Responsetoydb::Client库初始化时经 TCP 连接 toyDB 服务端自动建立 SQL 会话可发送请求、接收响应其中Client::execute在当前会话中执行任意 SQL 语句toysql二进制src/bin/toysql.rs基于 Rustyline 库实现的典型 REPLread-evaluate-print loop通过toydb::Client连接服务端循环提示用户输入 SQL 语句、执行并将结果格式化输出。配置文件详解toyDB 使用 YAML 配置文件控制节点行为默认配置见 config/toydb.yaml各参数含义如下配置项默认值/示例说明id1节点 ID集群内必须唯一peers{}对等节点 ID 到 Raft 地址的映射单节点为空listen_sqllocalhost:9601SQL 客户端监听地址listen_raftlocalhost:9701Raft 对等连接监听地址log_levelINFO日志级别可取DEBUG、INFO、WARN、ERRORdata_dirdata数据目录Raft 日志存于raft文件SQL 数据库存于sql文件storage_raftbitcaskRaft 日志使用的存储引擎bitcask或memorystorage_sqlbitcaskSQL 数据库使用的存储引擎bitcask或memoryfsynctrue是否将写入 fsync 到磁盘关闭可获得更好写性能但主机崩溃时可能丢数据并破坏 Raft 保证。只影响 Raft 日志写入——SQL 状态机从不 fsync因为它可由 Raft 日志重建compact_threshold0.2触发 BitCask 日志压缩的最小垃圾比例compact_min_bytes1000000触发压缩的最小日志字节数单机多节点集群的节点配置示例见 cluster/toydb1/toydb.yaml其中peers指向其余四个节点的 Raft 端口9702~9705。快速上手运行一个 5 节点集群仓库自带的 cluster/run.sh 脚本可一键构建并启动 5 节点集群SQL 端口 9601~9605Raft 端口 9701~9705数据存放于cluster/*/data/# 安装 Rust 工具链后在仓库根目录执行 $ ./cluster/run.sh Starting 5 nodes on ports 9601-9605 with data under cluster/*/data/. To connect to node 1, run: cargo run --release --bin toysql toydb4 21:03:55 [INFO] Listening on [::1]:9604 (SQL) and [::1]:9704 (Raft) toydb1 21:03:55 [INFO] Listening on [::1]:9601 (SQL) and [::1]:9701 (Raft) toydb2 21:03:56 [INFO] Starting new election for term 1 [...] toydb2 21:03:56 [INFO] Won election for term 1, becoming leader连接节点 1localhost:9601并执行 SQL$ cargo run --release --bin toysql Connected to toyDB node n1. Enter !help for instructions. toydb CREATE TABLE movies (id INTEGER PRIMARY KEY, title VARCHAR NOT NULL); toydb INSERT INTO movies VALUES (1, Sicario), (2, Stalker), (3, Her); toydb SELECT * FROM movies; 1, Sicario 2, Stalker 3, HertoyDB 支持大多数常见 SQL 特性包括连接join、聚合aggregate与事务。可用EXPLAIN查看查询计划例如 README 中展示的多表连接 分组 排序的复杂查询会输出由Scan、HashJoin、Aggregate、Projection、Order等节点构成的计划树。测试验证体系toyDB 主要使用 Goldenscript 脚本化测试脚本记录各种场景的事件与输出之后断言行为保持不变Raft 集群测试src/raft/testscripts/node选举、心跳、追加、请求转发、读仲裁等数十个场景MVCC 事务测试src/storage/testscripts/mvcc隔离级别、各类异常现象如脏读/幻读/写偏斜、银行转账冲突等SQL 执行测试src/sql/testscripts表达式、优化器、查询、Schema、事务、写入端到端测试tests/scripts 与 tests/testcluster.rs。运行全部测试只需cargo test。小结从整体架构看toyDB 是典型的分布式 SQL 数据库架构一个由 Raft 集群管理的事务型键值存储其上叠加SQL 查询引擎。五层组件自底向上——存储引擎提供有序键值与持久化MVCC 在其上提供快照隔离事务Raft 将事务请求复制到所有节点并保证线性一致性SQL 引擎完成从文本到执行计划的完整流水线服务端与客户端提供网络与交互界面。理解这套组件及其职责边界也就理解了多数生产级分布式 SQL 数据库的骨架。赞分享【免费下载链接】toydbDistributed SQL database in Rust, written as an educational project项目地址https://gitcode.com/gh_mirrors/to/toydb点击查看免费下载相关推荐终极指南快速上手Evennia MUD游戏开发系统终极指南快速上手Evennia MUD游戏开发系统 Evennia是一款基于Python的现代在线多人文字游戏MUD/MUSH/MU 开发框架它提供了完游戏开发后端RL4CO与TorchRL集成构建高效强化学习组合优化系统RL4CO与TorchRL集成构建高效强化学习组合优化系统 强化学习RL在组合优化CO领域的应用正迅速改变传统解决方案的效率边界。 RL4CO 作为基toyDB 技术参考资料解析从 Raft 共识、SQL 解析到 MVCC 事务的研究路线图toyDB 技术参考资料解析从 Raft 共识、SQL 解析到 MVCC 事务的研究路线图 toyDB 是一个以教育为目的、用 Rust 编写的分布式 SQL上一篇如何免费实现网盘满速下载终极直链获取工具使用指南下一篇Gumroad开源电商平台完整指南3步跑通数字产品销售系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考