本节摘要:RocksDB 的分层不是模块堆叠,而是契约的分解:API 层承诺原子与顺序,内存层承接状态跃迁,持久层编织时间与空间的拓扑,系统交互层负责与操作系统共舞。本节给出四层视图与各自的承诺清单,随后展开两个关键运行时状态机——MemTable 五态与后台线程池四态。它们是理解写入洪峰缓冲与在线运维的理论底座。
一个新手团队的排错记录值得引以为戒:他们的服务偶发写入延迟飙升,团队的第一反应是「磁盘不行了」,换盘、换机型折腾两周无果。后来有人打开引擎的运行时指标,发现问题出在别处——他们的报表任务每天定时发起全表扫描,把 Block Cache 冲得干干净净,随后的点查全部落空,延迟自然飙升。磁盘无辜,问题出在内存管理层与使用方式的关系上。
这个误判的根源,是把引擎当成了一个不可分的整体。而 RocksDB 的分层结构恰恰是为了让这类问题可以被「定位」而不是被「猜测」:每一层有明确的职责边界,每一层的违约都有专属的症状与指标。本节先把四层契约讲清楚,再给两个状态机——它们是这套架构里最容易出状态、也最值得盯着的动态部分。
第一层,API 语义层。 Put、Get、迭代器这些调用是引擎的前台大厅。它向上承诺三件事:原子性(单次写要么完整生效要么完全不可见)、顺序性(写入按调用顺序获得递增序列号,成为一切版本仲裁的标尺)、可靠性(显式要求同步时,返回即代表日志已落盘)。它刻意不暴露任何内部状态——你问不到「MemTable 满了没有」。这个「不暴露」是设计而非疏漏:一旦上层依赖内部状态,接口就再也改不动了。
第二层,内存管理层。 承接写入的第一跳。MemTable 与冻结表负责把洪峰缓冲在内存里,内存池负责分配纪律。它向 API 层承诺:冻结瞬时完成、刷盘异步进行、快照视图始终一致。第 2.2 节会拆它的内部结构。
第三层,持久化管理层。 SST 文件、层级拓扑、Compaction、版本管理都住在这里。它向内存层承诺:刷出的文件可恢复、压缩不破坏版本语义、文件生命周期有唯一的权威记录(版本账本,第 3 章主角)。它是三本放大账的主要发生地,第 5、6 章的主角。
第四层,系统交互层。 引擎与操作系统打交道的边界:直读写还是走页缓存、后台线程绑不绑核、内存水位如何协商。设计姿态是谦卑的——不假设独占硬件,而是读懂操作系统的行为再与之配合。比如报表扫描冲缓存的问题,正解不是换盘,而是让扫描走不受缓存干扰的通道。

排错时这张图当决策树用:写入丢失查第一、三层的衔接(日志与刷盘);延迟毛刺查第二层(缓存与冻结排队);空间膨胀查第三层(压缩与版本);整机资源争抢查第四层(线程、限速、IO 模式)。先定位层,再定位机制,比全局乱猜快得多。
内存里的每一张表都走一条固定的状态路径,五态依次是:
这条状态路径的价值在第 2 态与第 3 态的解耦上:冻结是微秒级的(改个指针),刷盘是毫秒到秒级的(真要写盘)。两者的解耦让写入洪峰在内存里排起了队,而不是直接顶在磁盘上。引擎允许多张冻结表同时排队,排队的上限有配置约束——队列太深会吃光内存,太浅会失去缓冲意义。第 8 章调优时这对参数是一对经典的搭档。
后台线程池承担刷盘与压缩,它自己也有状态:空闲(无任务,线程休眠)、运行(执行刷盘或压缩任务)、暂停(收到暂停指令,不再分发新任务,但手头任务做完)、停止(实例关闭,等任务收尾并析构)。看起来平淡,但「暂停」这一态是运维的宝贝:在线变更配置、做诊断、给宿主机腾 IO,都可以暂停后台任务而不中断前台读写。配合动态调整后台线程数的能力,它让「运维操作不伤服务」从愿望变成机制。
线程数本身是一对要算账的参数:刷盘线程与压缩线程共享还是分开、各配多少,直接决定洪峰时谁先被服务、压缩积压时写入还能撑多久。这些权衡留在第 8 章,这里先记住结构:调度与执行分离——压缩调度器只负责挑选与打包任务,执行交给线程池,两者互不阻塞。
下面这段会话式代码演示如何在不读源码的前提下「看见」状态机。把它跑在你自己的测试实例上,边写边看:
// 持续小写入,同时观察冻结与刷盘的节奏 for (int i = 0; i < 1000000; ++i) { std::string k = "key" + std::to_string(i); db->Put(WriteOptions(), k, std::string(200, 'x')); if (i % 100000 == 0) { // 查询当前内存中未刷盘数据的近似规模 std::string val; db->GetProperty("rocksdb.estimate-pending-compaction-bytes", &val); // val 反映待压缩数据量;配合统计里的刷盘次数即可看到: // 每写满一个 write_buffer_size,冻结一次、刷盘一次——五态循环走一遍 } }
跑完你会得到一组台阶状的刷盘计数:每个台阶对应一次冻结与刷盘。台阶的间距就是你设定的写缓冲大小,台阶的高度差透露每次刷出的数据量。这个实验花十分钟,胜过读十篇讲冻结机制的文章。
四层契约已经铺开。下一站推开三大组件的机房门,看 WAL、MemTable、SSTable 各自的内部构造。