犀牛鸟 2026 研究笔记 estelledc.github.io

第 12 章:十二大核心技术挑战——分布式数据库的”终极 Boss”

结论先行:分布式数据库是计算机科学中工程复杂度最高的领域之一。本章梳理 12 个核心技术挑战,每个挑战都对应一个根本矛盾——理解这些矛盾比记住具体实现更重要。这些挑战不是为了吓退你,而是帮你建立”问题雷达”——知道困难在哪里,才能在源码阅读和竞赛贡献中少走弯路。


总览:12 个挑战的难度分布

难度评级(基于源码复杂度和理论深度):

  ★★★★★ 极难(涉及分布式理论根本限制)
  ├── 1. 分布式事务一致性
  ├── 4. 全局时钟与因果序
  └── 6. 共识协议工程化

  ★★★★☆ 困难(需要大量工程权衡)
  ├── 2. 分布式查询优化
  ├── 3. 数据分片与再均衡
  ├── 5. HTAP 双引擎隔离
  ├── 7. 分布式 DDL
  ├── 9. SQL 兼容性完整度
  ├── 10. 故障检测与自动恢复
  └── 12. 全局死锁检测

  ★★★☆☆ 中等(成熟方案可借鉴)
  ├── 8. 连接管理与执行调度
  └── 11. 多租户隔离

挑战 1:分布式事务一致性 ★★★★★

核心矛盾:一致性 vs 性能

你和朋友同时在两个 ATM 上操作同一个银行账户——一个存钱、一个取钱。单机数据库用锁就能保证不出错。但分布式环境下数据分散在不同机器上,如何保证”要么全做完、要么全没做”?做得太严格每笔交易都要多台机器达成共识,速度慢;做得太松,用户可能看到中间态。

各项目怎么解决

CockroachDB:Parallel Commits
  传统 2PC 需要两轮 RTT(prepare + commit)
  → 把 commit 决策与最后一批写并行发出
  → 一轮 RTT 完成事务
  → 用 TLA+ 数学证明正确性
  源码:docs/tla-plus/ParallelCommits/ParallelCommits.tla

TiDB:Percolator 协议
  基于 Google Percolator 论文
  → 用 TiKV 的 MVCC + 锁机制实现 2PC
  → 通过 PD 分配全局时间戳保证一致性
  
OpenTenBase:GTM 中心化
  所有事务 ID 和快照由 GTM 统一分配
  → 简化了一致性保证
  → 但 GTM 成为性能瓶颈
  源码:src/gtm/main/gtm_txn.c(4029 行)

Vitess:不提供分布式事务
  → 一致性由应用层负责
  → 最简单但对开发者要求最高

根本限制

FLP 不可能定理(Fischer-Lynch-Paterson, 1985):在异步网络中,即使只有一个节点可能故障,也不存在一个确定性算法能保证一致性。所有实际系统都通过超时、重试等机制绕过这个限制——但这意味着在极端情况下可能需要牺牲可用性。


挑战 2:分布式查询优化 ★★★★☆

核心矛盾:计划质量 vs 优化时间

想象你要从北京经过多个城市到上海,可能的路线有成千上万种。单机数据库的查询优化已经是 NP-Hard 问题;分布式环境下还要考虑数据在哪些节点上、网络传输成本、哪些操作可以下推到存储节点——搜索空间呈指数增长。

各项目怎么解决

TiDB:Cascades 框架(最完整的开源实现)
  源码:pkg/planner/cascades/cascades.go
  - Memo 存储等价执行计划
  - 规则引擎驱动搜索
  - 40+ 优化规则
  - 同时评估 TiKV 和 TiFlash 路径

Citus:五层渐进式(最优雅的工程设计)
  源码:planner/README.md
  Layer 1: Fast-path Router → 单分片快路径
  Layer 2: Router Planner → 协同分片路由
  Layer 3: Modification Planning → DML 专用
  Layer 4: Recursive Planning → 子查询递归
  Layer 5: Logical Planner → 多关系代数树
  命中即停,大部分 OLTP 查询在前两层解决

CockroachDB:基于代价的优化器(CBO)
  Cascades 风格,与 Pebble 存储紧密集成
  DistSQL 执行引擎支持跨节点并行执行

OpenTenBase:pgxcship 可下推判断
  源码:src/backend/optimizer/util/pgxcship.c
  判断查询能否整条下推到单个 Datanode
  能下推 → 直接发送(接近单机性能)
  不能 → 走分布式执行计划

根本限制

统计信息的不完整性。优化器需要估算每个操作处理的行数来选择最优计划。但在分布式环境下,全局统计信息总是滞后的——数据在不断变化,而统计信息的收集有延迟。错误的行数估算可能导致优化器选择一个灾难性的执行计划。


挑战 3:数据分片与再均衡 ★★★★☆

核心矛盾:均衡性 vs 可用性

一个图书馆要把书从旧书架搬到新书架重新排列。搬书过程中读者还在借书还书——你不能全关了再搬,但搬的过程中书的位置在变,目录得保持准确。

各项目怎么解决

CockroachDB:Range 自动分裂 + Raft Learner 迁移
  源码:pkg/kv/kvserver/split_queue.go
  - load_based_splitter.go 选择最优分裂点
  - 先创建 Learner 副本同步数据
  - 数据追上后原子切换
  对客户端完全透明

Vitess:VReplication
  源码:go/vt/wrangler/resharder.go
  - 后台复制数据到新分片
  - 切换流量时原子更新路由规则
  - 支持在线 resharding

TiDB:Region 分裂 + PD 调度
  PD(Placement Driver)自动检测热点 Region
  → 分裂热点 Region
  → 迁移 Region 到负载较轻的节点

OpenTenBase:静态分片
  建表时指定 DISTRIBUTE BY HASH(col)
  分片数量和策略相对固定
  在线再均衡能力有限 → 贡献方向

根本限制

移动数据的同时保持一致性。数据迁移过程中可能出现”旧位置的数据还在被读、新位置的数据还没写完”的中间状态。边界情况极其复杂——网络分区、节点宕机、并发写入都可能在迁移过程中发生。


挑战 4:全局时钟与因果序 ★★★★★

核心矛盾:精度 vs 成本

世界各地的人同时参加在线拍卖——纽约和东京的竞拍者几乎同时出价,谁先谁后?物理时钟有误差、网络有延迟,如何定义全局公认的先后顺序?

三种方案对比

方案 A:TrueTime(Google Spanner)
  用 GPS + 原子钟提供高精度物理时间
  误差 ~7μs
  代价:需要专用硬件,成本极高
  开源世界无人使用(硬件要求太高)

方案 B:TSO(TiDB)
  中心化的时间戳分配器(PD 组件)
  精度:逻辑递增,绝对精确
  代价:PD 是单点,吞吐量有上限
  源码:TiDB 的 PD server

方案 C:HLC(CockroachDB、YugabyteDB)
  混合逻辑时钟 = 物理时钟 + 逻辑计数器
  精度:依赖 NTP(通常 < 250ms 偏差)
  代价:不确定窗口内可能读到过期数据
  无需中心化组件、无需专用硬件

根本限制

光速限制——信息从一个数据中心传到另一个至少需要几毫秒。这个物理限制无法消除,只能用不同方式应对。本质上这是一个”花钱买硬件精度”还是”花性能买软件容错”的经典 trade-off。


挑战 5:HTAP 双引擎隔离 ★★★★☆

核心矛盾:新鲜度 vs 干扰隔离

一家餐厅的厨房——前台点菜(TP 事务处理)和后台盘点库存(AP 分析)共用一个厨房。如果盘点员占了所有灶台做菜就停了;如果做菜优先,库存永远对不上。

各项目怎么解决

TiDB(物理隔离,最佳方案):
  TiKV(行存) ──Raft Learner──► TiFlash(列存)
  不同进程、可以在不同机器上
  AP 查询不影响 TP 延迟
  代价:架构复杂,运维两套存储

OpenTenBase(逻辑隔离):
  行存 + 列存 在同一个 Datanode 进程中
  简单,但高负载下可能资源竞争

Citus(可选列存):
  ALTER TABLE ... USING columnar;
  表级别选择存储格式
  不是真正的 HTAP,更像是分析友好的存储选项

CockroachDB / YugabyteDB:
  只有行存,OLAP 能力有限

根本限制

TP 和 AP 的资源需求模式完全冲突。TP 需要低延迟、小数据量的随机读写;AP 需要高吞吐、大数据量的顺序扫描。它们对 CPU cache、内存带宽、IO 调度的需求几乎正交。


挑战 6:共识协议工程化 ★★★★★

核心矛盾:正确性 vs 性能

Raft 论文只有 18 页,但工业级实现要处理论文没提到的无数细节——leader 选举风暴、日志压缩时机、慢 follower 的流控、配置变更的安全性。

CockroachDB 的深度定制(最具参考价值)

源码:pkg/raft/(fork 自 etcd Raft,深度修改)

三大创新:

1. StoreLiveness(替代传统心跳)
   传统:leader 定期发心跳,follower 超时则选新 leader
   问题:网络抖动导致不必要的选举风暴
   StoreLiveness:不仅检测节点在线,还评估存储层健康状态
   源码:pkg/raft/raftstoreliveness/

2. Fortification(防止不必要的选举)
   当 leader 确认与所有 follower 的连接都健康时
   → follower 承诺不会发起选举
   → 减少选举风暴
   源码:tracker/fortificationtracker.go

3. 异步存储写入(Asynchronous Storage Writes)
   传统:leader 必须先把日志写入本地磁盘,再发给 follower
   优化:leader 与 follower 并行写入磁盘
   → 减少一次磁盘写入的延迟

根本限制

正确性验证。CockroachDB 用 TLA+ 形式化验证、大量 data-driven 测试和混沌测试(pkg/kv/kvnemesis/)来建立信心,但依然不断发现边界 bug。共识协议的边界情况组合空间是天文数字——穷举测试不可能,只能通过形式化方法和概率性测试来增加信心。


挑战 7:分布式 DDL ★★★★☆

核心矛盾:原子性 vs 在线可用

单机数据库加一列只需修改一个元数据文件。分布式数据库需要在所有节点同步执行变更——期间不能中断查询,不能让不同节点看到不同的表结构。

各项目怎么解决

CockroachDB:F1 渐进式 Schema 变更
  灵感来源:Google F1 论文
  一次变更经过多个阶段:
  delete-only → write-only → public
  任何时刻集群中最多只有两个相邻版本的 schema
  相邻版本被设计为互相兼容

TiDB:异步 DDL 框架
  源码:pkg/ddl/
  通过 owner 选举确保同一时刻只有一个 TiDB 节点执行 DDL
  DDL 操作被记录在系统表中,节点可以异步执行

OpenTenBase:基于 2PC 的同步 DDL
  Coordinator 通过两阶段提交同步 DDL 到所有 Datanode
  简单但 DDL 期间可能有短暂的阻塞

Citus:利用 PG 原生 DDL + 分布式传播
  在 coordinator 执行 DDL 后传播到 worker
  依赖 PG 原生的 DDL 锁机制

挑战 8:连接管理与执行调度 ★★★☆☆

核心矛盾:并发度 vs 资源开销

每个分布式查询可能需要与多个 worker 节点通信。维护太多连接浪费资源,太少又成瓶颈。

Citus 的自适应执行器(最精巧的设计):
  源码:executor/adaptive_executor.c(5359 行)
  - Slow Start 算法动态调整连接池(类似 TCP 拥塞控制)
  - WaitEventSet 多路复用所有连接的 IO 事件
  - 支持多副本 failover

Vitess 的 ScatterConn:
  源码:go/vt/vtgate/scatter_conn.go
  - 通过 health check 动态选择最健康的 tablet
  - 连接池按 keyspace/shard 组织

根本难点是长尾延迟放大——一条跨 N 个分片的查询,总延迟取决于最慢的那个分片。当 N 增大时,”至少一个分片慢”的概率急剧增加。


挑战 9:SQL 兼容性完整度 ★★★★☆

核心矛盾:兼容范围 vs 分布式约束

用户期望”无缝迁移”——把现有 SQL 原封不动地跑在分布式数据库上。但分布式环境对某些 SQL 特性天然不友好。

难以兼容的 SQL 特性:

  全局自增 ID(AUTO_INCREMENT):
    单机靠本地计数器
    分布式需要中心化分配或范围预取

  存储过程中的事务控制:
    单机在同一个进程中执行
    分布式可能跨多个节点执行

  全局 ORDER BY + LIMIT:
    需要从所有分片获取排好序的数据
    取 TOP 10 可能需要从每个分片取 TOP 10 再合并

  跨分片 JOIN:
    单机只需在本地做
    分布式需要通过网络传输数据

各项目的兼容策略:Fork PG/MySQL 前端(YugabyteDB)兼容度最高但维护成本最高;自研解析器(TiDB、Vitess)控制力最强但兼容性需要逐个特性实现;Extension 模式(Citus)继承 PG 大部分兼容性但某些分布式操作有限制。


挑战 10:故障检测与自动恢复 ★★★★☆

核心矛盾:检测速度 vs 误判率

检测太快容易把网络抖动当成节点故障(误判),检测太慢真正的故障迟迟得不到处理。

CockroachDB 的 StoreLiveness:
  不仅检测节点是否在线
  还评估存储层的健康状态
  比传统心跳更精细

YugabyteDB 的 Master Leader:
  YB-Master 通过 Raft 管理元数据
  检测到 TServer 宕机后重新分配 tablet leader

OpenTenBase 的 GTM 主备切换:
  GTM Standby 持续同步主 GTM 的状态
  主 GTM 宕机后自动接管

根本限制:”无法区分慢和死”。一个节点的响应时间从 1ms 变成 10s,它是慢了还是挂了?这个判断本质上不可能完美解决。


挑战 11:多租户隔离 ★★★☆☆

核心矛盾:资源利用率 vs 隔离度

多个租户共享一个集群。完全隔离意味着资源浪费,完全共享意味着互相干扰。

CockroachDB:SQL 层多租户
  源码:pkg/multitenant/
  tenantrate/ 基于 token bucket 限流
  每个租户有独立的 SQL 执行环境

Citus:Schema-based 分租户
  源码:schema_based_sharding.c
  每个租户一个 schema
  利用 PG 的 schema 隔离机制

挑战 12:全局死锁检测 ★★★★☆

核心矛盾:检测精度 vs 网络开销

分布式环境下等待图分散在多个节点上。收集频率太高浪费带宽,太低死锁长时间无法解除。

YugabyteDB 的方案:
  源码:src/yb/docdb/deadlock_detector.cc
  每个 tablet 维护本地等待关系
  周期性汇报给 coordinator 构建全局等待图

Citus 的方案:
  通过 WaitGraph 在协调节点上构建全局等待图

根本难点:Phantom Deadlock(幻影死锁)
  由于网络延迟,不同节点收集到的等待关系属于不同时刻
  → 可能检测到一个"其实已经不存在"的死锁
  → 导致不必要的事务中止

12 个挑战与犀牛鸟的关联

哪些挑战最适合犀牛鸟参赛者切入?

  高可行性 + 高影响力(推荐):
  ├── GTM 性能优化(挑战 1 在 OpenTenBase 的具体体现)
  ├── 查询优化器增强(挑战 2,pgxcship 下推判断)
  └── 向量搜索集成(不在 12 个挑战中,但是热门方向)

  可行但需要更多基础:
  ├── HTAP 列存完善(挑战 5)
  └── 连接管理优化(挑战 8)

  适合学习但不建议作为贡献目标:
  ├── 共识协议(挑战 6,太深)
  ├── 全局时钟(挑战 4,太深)
  └── 分布式 DDL(挑战 7,涉及面太广)

常见误区

误区一:”这 12 个挑战都要精通才能参与贡献” → 正确理解:你只需要深入理解你选择的贡献方向涉及的 1-2 个挑战。对其他挑战有”知道它难在哪里”的认知就足够了。

误区二:”这些问题都已经被头部项目解决了” → 正确理解:没有一个挑战是”完全解决”的。CockroachDB 和 TiDB 的解决方案都有各自的 trade-off。特别是在 OpenTenBase 的上下文中,很多挑战的解决方案还有显著的优化空间。

误区三:”理论最优的方案就是工程上最好的” → 正确理解:工程是 trade-off 的艺术。Citus 的五层渐进式规划器理论上不如 Cascades 优化器强大,但它在实际 OLTP 场景中可能更快——因为大部分查询在前两层就被处理了,根本不需要复杂的搜索。


读完本章你能做什么


导读目录:README.md 上一章:第 11 章 · 跨项目全景对比 下一章:第 13 章 · 犀牛鸟 2026 实战指南