项目地址:https://github.com/thedevplus/kvs-system
1、立项背景和目标
基于PingCap的Practical Networked Applications in Rust,使用 Rust 从零实现一个高性能日志结构键值存储系统。目标是深入掌握存储引擎设计(Bitcask 模式)、并发线程池架构、自定义网络协议与性能调优等系统编程核心能力,并通过 Criterion 基准测试与 Sled 等生产级引擎横向对比,验证自研方案的吞吐表现。
2、软件功能、核心功能模块
- Bitcask 存储引擎 (src/kvs.rs):追加写日志 + 内存 HashMap 索引,1 MiB 段轮转,O(1) 点读取经 read_at 定位,后台压缩线程按陈旧条目阈值触发,原子替换索引,WAL。
- 可插拔线程池 (src/thread_pool.rs):统一 spawn API,三种后端——Naive(一任务一线程)、SharedQueue(定长 worker + crossbeam 通道,含 catch_unwind 容错)、Rayon(工作窃取,第三方对比库)。
- 自定义 TCP 线协议 (src/protocol.rs):2 字符 opcode + 双 tab 分隔的行帧格式,手写 Serde 序列化/反序列化,Set/Get 共用 value 帧,错误码内联消息。
- 客户端连接池 (src/client.rs):预建立多条长连接,AtomicUsize 轮询分配请求,读写半分离避免锁竞争,双包 Sd 实现优雅关闭。
- CLI 与库双模式 :支持命令行(kvs-client/kvs-server)和 Rust 库两种调用方式。
3、业务流程、功能路径描述
- 写入路径 :客户端构造 ClientArgs → 协议层序列化为 st 帧 → 连接池原子轮询选连接写入 → 服务端线程池 spawn 处理 → 追加写日志段 + 更新内存索引 → 返回成功响应。
- 读取路径 :客户端发送 gt 帧 → 服务端查询 HashMap 获取 KvPointer{log,pos,sz} → read_at 精确定位读取 → 命中返回值 / 未命中返回 Gn。
- 压缩路径 :后台线程监测陈旧条目达 1000 → 重写活跃记录到新段 → 写锁下原子替换索引 → 两级反压(yield_now + sleep)防止写饥饿。
- 关闭路径 :客户端发 Sd 到池连接置 exit_status 标志 → 再经新 TcpStream::connect 发 Sd 唤醒阻塞的 accept 循环 → 服务端优雅退出。
1、整体架构和设计思路
项目采用分层架构:存储引擎层(KvStore Bitcask 追加日志 + Sled 引擎(第三方对比库))、线程池层(Naive/SharedQueue/Rayon (第三方对比库)三种可插拔后端)、网络协议层(自定义 2 字符 opcode + 双 tab 分隔的行帧格式,手写 Serde)、客户端连接池层(AtomicUsize 轮询分配长连接)。技术栈:Rust 1.95+、crossbeam 通道、rayon 工作窃取、serde、clap CLI、criterion 基准测试。整体设计以无锁/弱锁优先、最弱原子序(全项目无 SeqCst)为并发准则。
2、负责模块和结果
独立完成全部 4 个核心模块。存储引擎:实现 1 MiB 段轮转 + HashMap{KvPointer{log,pos,sz}} 索引,O(1) 点读,后台压缩线程 1000 陈旧条目触发,原子替换索引。线程池:SharedQueueThreadPool 用 catch_unwind 实现 panic 安全。客户端池:预建多连接 + fetch_add 轮询,基准测试 1000 并发 key 读写验证。协议层:St/Gt 共用 value 帧格式,支持批量回执关联。
3、难点与解决方案
- 1000 并发连接爆错 :初始单 Mutex 共享连接导致 99% 线程睡眠,改为连接池 + AtomicUsize 轮询,每连接独立锁。
- channel_bound 不足 :SharedQueue 通道容量 5 在 burst 提交时 producer 阻塞、worker 空闲。定位后文档化说明需按客户端线程数等比放大。
- 固定数量 TCP 连接导致 incoming 阻塞 :服务端 accept 循环被 listener.incoming().next() 阻塞,exit_status 标志无法唤醒。解决方案为双包 Sd:池连接置标志 + 新 TcpStream::connect 唤醒 accept。
- 读写锁未分离 :单一 Mutex 同时保护读写半,并发下锁竞争严重。改用 reader 锁保护读半、shared 锁保护写半,两者独立加锁互不阻塞。
- BufReader 多线程 seek 覆盖 :多 worker 共享同一 BufReader 时内部游标互相覆盖。改用 FileExt::read_at 按 KvPointer 中的 pos 直接定位读取,绕过游标状态,彻底消除跨线程覆盖。