TiKV raftstore-v2 组件维护指南:基于 tablet 的 multi-raft 副本栈架构、关键不变量与运维实践
【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv
导读
本文基于 TiKV 仓库的 raftstore-v2 维护指南 展开,系统讲解components/raftstore-v2这一基于 tablet 的 multi-raft 副本栈的架构定位、模块划分、生命周期、关键不变量与变更审查方法。读完本文,你将能够独立阅读 raftstore-v2 源码、理解它如何被EngineType::RaftKv2路径启用、掌握其与经典 raftstore 的差异维护原则,并在修改相关代码时完成正确的风险排查与回归验证。
定位与范围:raftstore-v2 是什么
raftstore-v2是 TiKV 中由EngineType::RaftKv2启用的tablet 化副本栈。它负责:
- router 消息投递与响应通道;
- batch / FSM 执行框架;
- 以 tablet 为后端的 raft 状态存储(raft peer/storage/apply 三层);
- query/write/split/merge 等操作模块;
- PD 上报与 tablet 后台任务 worker。
其 crate 入口 components/raftstore-v2/src/lib.rs 顶部的模块注释给出了核心分工:线程模型基于batch-system,所有状态机定义在fsm模块,一切对 raft 的封装位于raft模块,split/merge/confchange/read/write 等命令实现在operation模块,而各状态机之间通过router模块定义的消息通信。
重点:raftstore-v2 不是经典components/raftstore的小变体。它拥有独立的读/写/apply 路径与独立的故障模式,审查者应将其视为一个独立的维护面(maintenance surface)。经典 raftstore 中发现的修复,不能默认“同理”适用于这里,必须在代码中逐一验证。
架构视图
栈视图(自外向内)
从外到内依次是:
- router 与响应通道——对外接口层,定义消息类型与回调/流式响应通道;
- batch / FSM 执行——基于 components/batch-system 的轮询调度框架;
- raft peer / storage 层——raft 状态机与 tablet 后端存储;
- 操作模块——query / write / split / merge 等命令实现;
- PD 与 tablet 后台 worker——心跳上报、拆分、慢节点检测、tablet 生命周期等后台任务。
边界视图(与仓库其他部分的接缝)
- src/server/raftkv2/mod.rs 是存储层进入本 crate 的桥接入口;
- components/server/src/server2.rs 在进程启动阶段把整套栈装配进来;
- src/storage/config.rs 通过
EngineType::RaftKv2选择这条路径。
在 src/server/raftkv2/mod.rs 中可以看到桥接的具体形态:RaftKv2实现tikv_kv::Enginetrait,其async_write将Vec<Modify>编码为SimpleWriteBinary(通过SimpleWriteEncoder),并借助CmdResChannelBuilder订阅Proposed/Committed/Finished事件,最终包装成Transform流逐级返回给调用方;async_snapshot则处理 read-index、stale read 与 flashback 标记等语义。桥接层的每一步都依赖 router 提供的回调与流式语义,这也是下文“关键不变量”中反复强调契约稳定的原因。
进程生命周期与启动顺序
本 crate 的启动发生在 components/server/src/server2.rs 的run_tikv路径中,前置条件是引擎(engines)、PD client 与主要运行时辅助组件(read pool、concurrency manager、资源管控等)已经就绪。Router、StoreSystem 与 worker 的构造必须与 src/server/raftkv2/mod.rs 的预期保持一致。
关闭顺序同样关键:peer/store/apply 的执行、tablet 任务与响应通道都依赖所有权(ownership)按正确顺序排空。若先关闭响应通道再关闭 router,未完成的回调将悬挂;若先销毁 tablet 再排空 apply 任务,则可能把已提交日志写到已销毁的存储上。修改启动/关闭时序时,必须同时回归 components/raftstore-v2/tests/integrations/test_life.rs 等生命周期测试。
关键运行时锚点:
- 启动与装配:
components/server/src/server2.rs - 桥接入口:
src/server/raftkv2/mod.rs - crate 入口:
components/raftstore-v2/src/lib.rs
数据模型与元数据契约
raftstore-v2 内部的数据契约包括四类:
- router 消息与响应通道契约:
PeerMsg、StoreMsg、RaftRequest及CmdResChannel/CmdResStream等定义在 components/raftstore-v2/src/router/mod.rs; - peer/store/apply FSM 所有权边界:三类状态机各司其职,见下文“Batch 与 FSM 执行”;
- tablet 后端 raft 存储与 apply 状态:raft 层
Peer、Storage、Apply定义在 components/raftstore-v2/src/raft/mod.rs; - 操作层请求/响应语义:由 components/raftstore-v2/src/operation/mod.rs 统一导出,例如
SimpleWriteBinary/SimpleWriteReqEncoder、SplitFlowControl、MergeContext、CatchUpLogs、SPLIT_PREFIX、MERGE_IN_PROGRESS_PREFIX、MERGE_SOURCE_PREFIX等。
高风险契约包括:
- 回调完成与响应流行为:回到
raftkv2桥接层时,WriteEvent::Finished必须被触发一次且仅一次; - 跨 FSM 边界的 peer/apply 顺序:提交日志必须先于其产生的结果返回;
- tablet 状态与 region/raft 元数据对齐:tablet 是 per-region 的本地存储单元,任何状态迁移都必须与 region 元数据保持一致;
- split/merge/bootstrap/destroy 流程:必须维持 router、FSM 与 tablet 三方状态相互一致。
源码阅读路线图
从哪里开始
建议按以下入口文件切入:
- components/raftstore-v2/src/lib.rs —— crate 结构总览与公共导出
- components/raftstore-v2/src/router/mod.rs —— 消息类型与响应通道
- components/raftstore-v2/src/batch/mod.rs —— 专用 batch 系统
- components/raftstore-v2/src/fsm/mod.rs —— 三类状态机
- components/raftstore-v2/src/raft/mod.rs —— peer/storage/apply
- components/raftstore-v2/src/operation/mod.rs —— 命令操作层
- components/raftstore-v2/src/worker/pd/mod.rs —— PD 上报与控制
- components/raftstore-v2/src/worker/tablet.rs —— tablet 后台任务
推荐阅读顺序
components/raftstore-v2/src/lib.rscomponents/raftstore-v2/src/router/mod.rscomponents/raftstore-v2/src/batch/mod.rscomponents/raftstore-v2/src/fsm/mod.rscomponents/raftstore-v2/src/raft/mod.rscomponents/raftstore-v2/src/operation/mod.rscomponents/raftstore-v2/src/worker/pd/mod.rscomponents/raftstore-v2/src/worker/tablet.rssrc/server/raftkv2/mod.rscomponents/server/src/server2.rs
lib.rs中还有一个值得注意的设计原则:凡是不依赖 batch system 细节的字段应定义在 raft 模块的Peer中,而fsm中的PeerFsm只做薄封装,这样未来即使替换并发执行方案,也只需改动fsm层。
内部结构深入
Router 与响应层
router/*定义消息类型、响应通道与 router 实现,是src/server/raftkv2消费的外层接口。核心导出包括:
- 消息:
PeerMsg、PeerTick、StoreMsg、StoreTick、RaftRequest; - 响应通道:
CmdResChannel/CmdResStream/CmdResEvent(Proposed/Committed/Finished)、QueryResChannel、AnyResChannel与DebugInfoChannel; - 实现:
RaftRouter、UnsafeRecoveryRouter。
Batch 与 FSM 执行
batch/*提供专用的 store batch 系统。components/raftstore-v2/src/batch/mod.rs 注释明确指出:StoreSystem 用于轮询 raft 状态机,ApplySystem 用于应用日志。StoreSystem、StoreRouter与create_store_batch_system均由此导出。
fsm/*定义了三种 FSM(见 components/raftstore-v2/src/fsm/mod.rs):
- StoreFsm:处理控制消息与全局初始化(
Store、StoreMeta); - PeerFsm:处理单个 raft peer 专属消息(
PeerFsm、PeerFsmDelegate); - ApplyFsm:处理单个 peer 的 apply 任务(
ApplyFsm、ApplyScheduler、ApplyResReporter)。
消息顺序与所有权转移是这里的核心正确性约束:同一 peer 的消息必须串行处理,apply 结果必须按提交顺序回报。
Raft 与操作
raft/*包含 peer 与 storage 逻辑:Peer、Storage、Apply。operation/*在 FSM/raft 之上实现业务行为,从目录结构看分为:
- command/admin:split、merge(prepare/commit/rollback)、conf_change、compact_log、flashback、transfer_leader;
- command/write:ingest 与通用写;
- query:local 读、lease 读、replica 读、capture;
- ready:apply_trace、async_writer、snapshot、replay;
- unsafe_recovery:create/demote/destroy/force_leader/report;
- 其余:bucket、disk_snapshot_backup、life、pd、txn_ext 等。
operation/mod.rs对外导出了SimpleWriteEncoder/SimpleWriteDecoder、ApplyFlowControl、SplitFlowControl、MergeContext、CatchUpLogs、ProposalControl、SplitInit、RequestSplit、RequestHalfSplit等符号,写路径通过write_initial_states初始化持久状态。
后台 worker
worker/pd/*:面向 PD 的上报与控制路径。从 components/raftstore-v2/src/worker/pd/mod.rs 的Task枚举可以看到其职责范围:StoreHeartbeat、RegionHeartbeat、UpdateReadStats/UpdateWriteStats/UpdateRegionCpuRecords、AskBatchSplit/ReportBatchSplit/AutoSplit、UpdateMaxTimestamp、ReportBuckets、ReportMinResolvedTs、InspectLatency/TickSlownessStats/UpdateSlownessStats、DestroyPeer、GracefulShutdownState 等。PdReporter同时实现FlowStatsReporter、StoreStatsReporter与资源计量Collectortrait,把读/写流量、store 信息、region CPU 记录统一调度进该 worker;worker/tablet.rs:tablet 专属后台任务,包括Trim、PrepareDestroy、Destroy、DirectDestroy、CleanupImportSst、Flush、DeleteRange、SnapGc等,并区分高低优先级线程池(默认高优先级池大小为 2、低优先级为 6);worker/refresh_config.rs:承载运行时配置热更新(refresh-config)逻辑。
关键不变量(Critical Invariants)
以下不变量是 raftstore-v2 的正确性基石,任何改动都不得破坏:
- router 消息类型与响应通道必须保持
src/server/raftkv2所依赖的回调与流式语义——包括 callback 完成、CmdResEvent顺序与WriteEvent生命周期; - peer/store/apply FSM 的所有权必须保持单所有者且顺序安全——不允许两个执行体同时持有同一 peer 的可变状态;
- 读路径不得悄然削弱 local-read、read-index、snapshot 或 apply 保证——例如 stale read 的 start_ts 编码、flashback 标志、lease 读等语义都要在
raftkv2桥接层完整传递; - tablet 状态迁移必须与 region/raft 元数据保持对齐;
- maybe-tombstone 提示约束:store 可能收到来自 PD 的含糊 not-found 响应而得到 maybe-tombstone 提示,它只能用来确认已在移除记录(removal records)中的 peer;连接重试与 tombstone 阻塞仍归 raft client 所有;
- pending 的 pre-transfer-leader 消息与缓存预热状态归属:它们属于接受该消息的 leader ID 与 term,一旦二者发生变化(包括未产生新 Raft
SoftState的 term 变化),必须被丢弃; - split/merge/bootstrap/destroy 流程必须保持 router、FSM 与 tablet 状态三方一致——例如
SPLIT_PREFIX、MERGE_IN_PROGRESS_PREFIX、MERGE_SOURCE_PREFIX这类前缀标记被写入后,读路径必须能识别合并/拆分进行中的状态。
可观测性与运维信号
排查问题时优先关注以下信号:
- PD worker 日志与指标:心跳失败、
AskBatchSplit超时、AutoSplit积压; - 响应通道与 router 失败:
check_send失败、CmdResStream提前终止、callback 未完成; - tablet 任务与 refresh-config 行为:
Flush/Destroy/SnapGc任务的积压或失败、配置热更新是否生效; src/server/raftkv2桥接层暴露的差异:Transform流中出现的意外错误响应。
建议从以下位置开始 triage:
components/raftstore-v2/src/router/*components/raftstore-v2/src/fsm/*components/raftstore-v2/src/worker/pd/*src/server/raftkv2/mod.rs
测试体系与故障注入
raftstore-v2 的本地集成测试覆盖集中在components/raftstore-v2/tests/integrations/*,涵盖 basic_write、conf_change、life、merge、pd_heartbeat、read、split、status、trace_apply、transfer_leader 等场景;failpoint 覆盖位于components/raftstore-v2/tests/failpoints/*(bootstrap、bucket、life、merge、pd_heartbeat、split、trace_apply 等)。集群级测试 harness 由components/test_raftstore-v2提供。
在修改涉及生命周期(如 tests/integrations/test_life.rs)、拆分(如 tests/integrations/test_split.rs)、合并(如 tests/integrations/test_merge.rs)与心跳(如 tests/integrations/test_pd_heartbeat.rs)的代码时,应同时补充或运行对应 failpoint 测试以验证异常路径。
变更管理指南
- 若改动涉及回调时序、router 语义、启动/关闭顺序、tablet 状态假设或 PD/tablet worker 行为,应在同一 patch 中同步更新本维护指南;
- 审查经典
raftstore的修复时,显式检查同一问题是否也存在于 raftstore-v2; - 不要默认两者实现等价,一切以代码验证为准。
变更影响矩阵
| 变更类型 | 需要检查的范围 |
|---|---|
| Router 或回调变更 | router/*、src/server/raftkv2/mod.rs、响应通道 |
| FSM 顺序或 apply 路径变更 | fsm/*、raft/*、相关 operation 模块 |
| 读/写/query 路径变更 | operation/*、raft/*、server 桥接行为 |
| tablet 生命周期或配置变更 | worker/tablet.rs、worker/refresh_config.rs、启动装配 |
| PD/上报变更 | worker/pd/*、对外指标与日志 |
审查清单
提交代码前逐项自查:
- 该改动是否触及 router 消息、响应通道或
src/server/raftkv2的桥接假设? - 是否修改了
fsm/*、raft/*或operation/*中影响回调时序或顺序的部分? - 同一修复是否也需要应用到经典
components/raftstore,或反之? - 是否修改了 tablet 生命周期、PD 上报或 refresh-config 行为却没有更新对应的 worker 路径?
- 是否在 poller 或热读/写路径上引入了同步工作、额外日志或分配压力?
常见故障模式
- 修复只落在经典 raftstore,导致 raftstore-v2 与经典实现发散(这是最常见的错误来源);
- router/响应行为偏离
raftkv2的预期,表现为回调不完成、事件顺序错乱; - peer/apply 生命周期缺陷以callback 缺失完成的形式暴露——例如 region 被销毁但
WriteEvent::Finished未发出,调用方永久挂起; - tablet/bootstrap 状态与 region 元数据失步,读路径读到过期或已删除的 tablet;
- PD worker 逻辑滞后于 split、slowness 或运行时配置变更,导致心跳/拆分上报失真。
阅读地图与配套文档
推荐的代码阅读顺序(与“源码阅读路线图”一致):lib.rs→router/mod.rs→batch/mod.rs→fsm/mod.rs→raft/mod.rs→operation/mod.rs→worker/pd/mod.rs→worker/tablet.rs。
配套文档(均已转换为仓库根相对路径):
- repo-overview.md —— 仓库整体概览
- src/server.md —— 服务端组件维护说明
- src/storage.md —— 存储层维护说明
- components/server.md —— server 组件维护说明
术语表
- Tablet:tablet 化引擎路径中 per-region 的本地存储单元;
- Router:把消息投递到 store/peer/apply 执行的投递层;
- Apply FSM:负责已提交日志应用的 apply 侧状态机;
- Response channel:回传调用方的回调/流式响应桥。
结语
raftstore-v2 是 TiKV 存储引擎中独立的 tablet 化 multi-raft 实现,其正确性取决于 router 契约、FSM 所有权、tablet 与 region 元数据对齐以及后台 worker 行为这四类不变量。维护者在改动它时,应始终以 raftstore-v2 维护指南 为骨架,对照 lib.rs 等源码锚点逐层验证,并利用components/raftstore-v2/tests与components/test_raftstore-v2的集成与 failpoint 测试建立回归防线,切忌将经典 raftstore 的结论直接照搬。
【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考