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

第 3 章:四大架构模式——从改装到重造

结论先行:6 个分布式数据库项目采用了 5 种不同的架构策略,本质上是在回答同一个问题:”如何把单机数据库变成分布式?”。从”给现有数据库装插件”到”从零打造新数据库”,侵入程度越深,能力越强,但代价也越大。理解这个光谱,是理解所有后续项目设计决策的关键。


一个汽车改装的故事

假设你有一辆普通家用轿车,现在需要它能跑越野路段。你有几种选择:

方案一:给轿车装一个车顶行李架和越野轮胎(扩展插件)。车的发动机、变速箱、底盘都不动,只在外面加东西。优点是改装成本低、不影响日常用车、随时可以拆掉恢复原样。缺点是越野能力有限——轿车底盘还是那么低,过不了深水区。

方案二:在轿车前面放一辆越野引导车(中间件代理)。引导车负责探路和导航,轿车只需要跟着走。优点是轿车完全不用改、引导车可以换、多辆轿车可以共用一辆引导车。缺点是速度受限于两车的协调,遇到真正恶劣的路况引导车也帮不上忙。

方案三:打开发动机盖,换一台更强的引擎,加装底盘护甲(内核改造)。改装很深入,越野能力大幅提升。但每次原厂推出新车型(数据库版本升级),你都得重新做一遍改装。

方案四:不改装了,直接买一辆专业越野车(原生分布式)。从设计之初就为越野而生。性能最强,但你需要重新学习驾驶方式,而且价格不菲。

这四种策略,对应了分布式数据库领域的四大架构模式(加上 YugabyteDB 的双协议变体,共 5 种)。

模式一:扩展插件模式——Citus 的极简哲学

核心思路

一句话:不改 PostgreSQL 的一行代码,通过 PG 原生的 Extension 机制,把分布式查询能力”安装”进 PG。

想象 Citus 就是手机上的一个 App。你的手机(PostgreSQL)出厂时并不支持”控制多台设备协同工作”,但装了这个 App 之后就可以了。关键是——手机的操作系统完全没变,你原来的所有应用和数据都不受影响。

工作原理

Citus 利用了 PostgreSQL 提供的 hook 机制。PostgreSQL 在查询处理的几个关键环节留了”钩子”——如果有 extension 注册了这些钩子,PostgreSQL 会在执行到对应环节时调用 extension 的代码。

用户发送 SQL
    |
    v
PostgreSQL 收到查询
    |
    v
解析 SQL(PostgreSQL 原生)
    |
    v
[hook] 查询规划  ← Citus 在这里介入!
    |                把单机计划改成分布式计划
    v
[hook] 查询执行  ← Citus 在这里介入!
    |                把子查询发送到 worker 节点
    v
返回结果给用户

具体来说,Citus 的入口文件 shared_library_init.c 在 PostgreSQL 启动时注册了多个 hook:

// Citus 注册 hook 的简化示意
void _PG_init(void) {
    // 替换 PostgreSQL 的标准查询规划器
    prev_planner_hook = planner_hook;
    planner_hook = distributed_planner;  // ← Citus 的分布式规划器
    
    // 替换执行器的相关 hook
    prev_ExecutorStart = ExecutorStart_hook;
    ExecutorStart_hook = CitusExecutorStart;
}

当一条 SQL 进来时,Citus 的 distributed_planner 会判断:这条查询涉及的表是不是分布式表?如果是,就生成一个分布式执行计划,把子查询发送到各个 worker 节点执行;如果不是,就交还给 PostgreSQL 的标准规划器处理。

架构图

                    ┌─────────────────────┐
                    │   Coordinator Node  │
                    │  ┌───────────────┐  │
用户 SQL ──────────►│  │  PostgreSQL   │  │
                    │  │  + Citus Ext  │  │
                    │  └──────┬────────┘  │
                    └─────────┼───────────┘
                         分布式计划
                    ┌─────────┼───────────┐
                    │         │           │
              ┌─────▼───┐ ┌──▼──────┐ ┌──▼──────┐
              │ Worker 1 │ │ Worker 2│ │ Worker 3│
              │ PG+Citus │ │ PG+Citus│ │ PG+Citus│
              │ (分片1,4)│ │ (分片2,5)│ │ (分片3,6)│
              └─────────┘ └─────────┘ └─────────┘

关键设计决策和取舍

决策:用 PG Extension 而不是 Fork

收益

代价

一个精彩的例子:Citus 的列存引擎也是通过 PG 的 Table Access Method 接口实现的——用户只需要 ALTER TABLE orders USING columnar; 就能把一张表从行存切换到列存,不需要迁移数据、不需要改应用代码。这是 PG 扩展机制的极致运用。

模式二:中间件代理模式——Vitess 的流量调度

核心思路

一句话:不改 MySQL 一行代码,在外面加一层智能代理,负责查询路由和分片管理。

想象一个快递分拣中心。它不生产货物(不改 MySQL),也不开运输车(不管存储),它只做一件事:接收快递员送来的包裹(SQL 查询),根据收件地址(分片键)分拣到正确的运输车上(MySQL 实例),然后把收集到的回执(查询结果)合并后交还给快递员。

工作原理

Vitess 的核心是 VTGate——一个伪装成 MySQL 服务器的代理。应用程序连接 VTGate 就像连接普通的 MySQL 一样,完全感知不到背后是分布式集群。

应用程序(以为在连 MySQL)
    |
    v
VTGate(伪装成 MySQL 服务端)
    |
    ├── 解析 SQL(自研解析器 sqlparser/)
    ├── 根据 VSchema 确定分片路由
    └── 生成执行计划(Gen4 Planner)
    |
    ├──────────────────┬──────────────────┐
    v                  v                  v
VTTablet + MySQL    VTTablet + MySQL    VTTablet + MySQL
  (shard -80)        (shard 80-c0)      (shard c0-)

VTGate 的关键工作流程:

  1. 接收连接go/mysql/server.go 实现了完整的 MySQL 服务端协议,包括认证握手
  2. 解析 SQLgo/vt/sqlparser/sql.y 定义了 MySQL 兼容的语法规则
  3. 确定路由:根据 VSchema 中定义的 Vindexes(分片函数),计算 SQL 应该发往哪些 shard
  4. 生成计划:Gen4 Planner 决定是路由到单个 shard 还是需要散射到多个 shard 再合并
  5. 执行和合并:通过 ScatterConn 并行发送到多个 shard,合并结果后返回

架构图

                        ┌─────────────┐
                        │  Topology   │
                        │  Service    │
                        │ (etcd/ZK)   │
                        └──────┬──────┘
                               │ 分片映射
    ┌──────────────────────────┼──────────────────────────┐
    │                          │                          │
    v                          v                          v
┌──────────┐           ┌──────────┐           ┌──────────┐
│ VTGate   │           │ VTGate   │           │ VTGate   │
│ (无状态) │           │ (无状态) │           │ (无状态) │
└────┬─────┘           └────┬─────┘           └────┬─────┘
     │                      │                      │
     └──────────────────────┼──────────────────────┘
                            │
     ┌──────────────────────┼──────────────────────┐
     │                      │                      │
     v                      v                      v
┌──────────┐           ┌──────────┐           ┌──────────┐
│ VTTablet │           │ VTTablet │           │ VTTablet │
│ + MySQL  │           │ + MySQL  │           │ + MySQL  │
│ shard-80 │           │ shard    │           │ shard c0-│
│          │           │ 80-c0    │           │          │
└──────────┘           └──────────┘           └──────────┘

关键设计决策和取舍

决策:VSchema 声明式分片

传统的数据库中间件(如 MyCAT、ShardingSphere)通常需要在配置文件或代码中写分片规则。Vitess 独创了 VSchema 的概念——用 JSON 声明每张表的分片策略和索引关系:

{
  "sharded": true,
  "vindexes": {
    "hash": { "type": "hash" }
  },
  "tables": {
    "users": {
      "column_vindexes": [
        { "column": "user_id", "name": "hash" }
      ]
    },
    "orders": {
      "column_vindexes": [
        { "column": "user_id", "name": "hash" }
      ]
    }
  }
}

这样做的好处是分片策略与应用代码完全解耦——改分片规则不需要改一行应用代码,甚至可以在线做 resharding。

收益

代价

模式三:内核改造模式——OpenTenBase 的深度手术

核心思路

一句话:直接打开 PostgreSQL 的源码,在里面植入分布式能力——全局事务管理、数据分片路由、远程查询执行。

回到汽车改装的类比:OpenTenBase 是直接打开发动机盖,换更强的引擎、加底盘护甲、改装悬挂系统。改装完成后,这辆车的越野能力远超方案一(加行李架)和方案二(用引导车),但每次原厂推出新车型时,你都得重新做一遍改装。

OpenTenBase 基于 Postgres-XL(一个更早的 PostgreSQL 分布式 fork),在此基础上做了 HTAP 增强。它的代码中大量使用 #ifdef __OPENTENBASE__ 来标记自己对 Postgres-XL 基线的修改,这让代码考古变得非常容易——你可以精确看到哪些代码是 OpenTenBase 自己加的。

工作原理

OpenTenBase 引入了三个关键组件,植入到 PostgreSQL 的内核中:

GTM(Global Transaction Manager):独立进程,负责分配全局事务 ID 和全局快照。所有分布式事务都必须向 GTM 申请事务 ID,这保证了全局一致性,但也让 GTM 成为潜在瓶颈。

Coordinator:接收用户的 SQL 请求,做查询规划和路由。Coordinator 本质上是一个增强版的 PostgreSQL,多了分布式查询计划生成的能力。

Datanode:实际存储数据和执行查询的节点。每个 Datanode 也是一个增强版的 PostgreSQL,能执行 Coordinator 下发的子查询。

用户发送 SQL
    |
    v
Coordinator(增强版 PG)
    |
    ├── 解析 SQL(PG 原生解析器)
    ├── 向 GTM 申请事务 ID(全局一致性的关键)
    ├── 查询 Shard Map 确定数据在哪些 Datanode
    ├── 生成分布式执行计划(pgxcship 判断是否可下推)
    └── 把子查询发送到 Datanode
    |
    ├──────────────────┬──────────────────┐
    v                  v                  v
Datanode 1          Datanode 2          Datanode 3
(增强版 PG)         (增强版 PG)         (增强版 PG)
  行存+列存           行存+列存           行存+列存
    |                  |                  |
    └──────────────────┼──────────────────┘
                       v
                      GTM
               (全局事务管理器)
         事务 ID 分配 / 快照管理

架构图(详细版)

                    ┌─────────────────────────┐
                    │        GTM              │
                    │  ┌───────────────────┐  │
                    │  │ gtm_txn.c (4029行)│  │  全局事务 ID
                    │  │ gtm_snap.c        │  │  全局快照
                    │  │ gtm_seq.c         │  │  全局序列
                    │  └───────────────────┘  │
                    │        ↕ 主备复制        │
                    │    GTM Standby          │
                    └────────┬────────────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
         GTM Proxy      GTM Proxy      GTM Proxy
         (批量聚合)     (批量聚合)     (批量聚合)
              │              │              │
              v              v              v
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │Coordinator│  │Coordinator│  │  (可选)  │
        │  1       │  │  2       │  │Coordinator│
        │ (路由+   │  │ (路由+   │  │  3       │
        │  规划)   │  │  规划)   │  │          │
        └────┬─────┘  └────┬─────┘  └──────────┘
             │              │
        ┌────┴──────────────┴────┐
        │         │         │
   ┌────▼────┐┌───▼────┐┌───▼────┐
   │Datanode │ │Datanode ││Datanode │
   │ 1      ││ 2      ││ 3      │
   │行存+列存││行存+列存││行存+列存│
   └─────────┘└────────┘└────────┘

关键设计决策和取舍

决策:中心化的 GTM vs 去中心化的共识

CockroachDB 和 TiDB 使用去中心化的 Raft 协议来保证一致性——没有单点。OpenTenBase 选择了中心化的 GTM——所有事务必须经过 GTM。

这个选择背后的逻辑是:OpenTenBase 基于 Postgres-XL,而 Postgres-XL 的设计目标是”在 PG 内核上做最小改动实现分布式”。GTM 是实现全局一致性最直接的方式——不需要改动 PG 的存储引擎或复制协议,只需要加一个外部协调器。

收益

代价

与 Citus 的对比

同样基于 PostgreSQL,OpenTenBase 和 Citus 代表了两种截然不同的策略:

维度 Citus(扩展) OpenTenBase(Fork)
PG 兼容性 通过 hook,不改 PG 代码 Fork PG,深度修改
PG 版本跟进 快(hook 接口稳定) 慢(需要重新合并改动)
分布式事务 2PC,能力有限 GTM 全局协调,能力更强
HTAP 列存扩展(有限) 行存+列存双引擎
DDL 分布式 有限 更完整
代码入门难度 低(~1000 文件) 中(~3000 文件)

模式四:原生分布式模式——TiDB 和 CockroachDB

核心思路

一句话:不基于任何现有数据库,从零开始设计一个天生的分布式数据库。

这就是直接买一辆专业越野车。它从设计之初就为分布式而生——存储引擎、查询引擎、事务管理、复制协议全都是为分布式场景定制的。

但 TiDB 和 CockroachDB 虽然都是原生分布式,却走了两条不同的路。

TiDB:存算分离 + 双引擎 HTAP

TiDB 的设计哲学是存算分离——计算层(tidb-server)和存储层(TiKV + TiFlash)完全解耦。

想象一家大公司把”接待前台”和”仓库管理”彻底分开。前台只管理解客户需求(SQL 解析和优化),仓库只管存取货物(数据读写),中间靠物流系统(PD 调度器)协调。需要更多接待能力?加前台。需要更多存储空间?加仓库。两者可以独立扩展。

        ┌────────────────────────────────────────────┐
        │              TiDB Server (Go)              │
        │    ┌──────────────────────────────────┐    │
用户 ──►│    │  SQL Parser → Cascades Optimizer  │    │
        │    │  → Executor → DistSQL Client     │    │
        │    └──────────────┬───────────────────┘    │
        └───────────────────┼────────────────────────┘
                            │
                   PD (调度器,Go)
                   管理 Region 分布
                   负载均衡调度
                            │
              ┌─────────────┼─────────────┐
              │             │             │
         ┌────▼────┐   ┌───▼────┐   ┌───▼────┐
         │  TiKV   │   │  TiKV  │   │  TiKV  │
         │ (Rust)  │   │ (Rust) │   │ (Rust) │
         │ 行存    │   │ 行存   │   │ 行存   │
         │ Raft    │   │ Raft   │   │ Raft   │
         └────┬────┘   └───┬────┘   └───┬────┘
              │ Raft        │            │
              │ Learner     │ Raft       │ Raft
              │ 异步复制    │ Learner    │ Learner
              v             v            v
         ┌─────────┐  ┌─────────┐  ┌─────────┐
         │ TiFlash │  │ TiFlash │  │ TiFlash │
         │ (C++)   │  │ (C++)   │  │ (C++)   │
         │ 列存    │  │ 列存    │  │ 列存    │
         └─────────┘  └─────────┘  └─────────┘

TiDB 最精彩的设计是用 Raft Learner 打通 TP(事务处理)和 AP(分析处理):

CockroachDB:存算一体 + 强一致

CockroachDB 的设计哲学是存算一体——每个节点既能处理 SQL 又能存储数据,没有单独的存储层。

想象一个连锁便利店,每家店既能收银又能管库存。总部不需要专门的”存储中心”,任何一家店倒闭,客户去隔壁照常买东西。这种设计消除了单点依赖。

        ┌────────────────────────────────────┐
        │           CockroachDB Node         │
        │  ┌──────────────────────────────┐  │
用户 ──►│  │  SQL Layer                    │  │
        │  │  Parser → Optimizer →         │  │
        │  │  DistSQL Execution            │  │
        │  ├──────────────────────────────┤  │
        │  │  KV Layer                     │  │
        │  │  Transaction → Raft →         │  │
        │  │  Range Management             │  │
        │  ├──────────────────────────────┤  │
        │  │  Storage Layer (Pebble)       │  │
        │  │  MVCC → LSM-Tree              │  │
        │  └──────────────────────────────┘  │
        └──────────────────┬─────────────────┘
                           │ Raft 复制
        ┌──────────────────┼──────────────────┐
        │                  │                  │
   ┌────▼──────┐     ┌────▼──────┐     ┌────▼──────┐
   │  Node 2   │     │  Node 3   │     │  Node 4   │
   │ SQL+KV    │     │ SQL+KV    │     │ SQL+KV    │
   │ +Storage  │     │ +Storage  │     │ +Storage  │
   └───────────┘     └───────────┘     └───────────┘

CockroachDB 最精彩的设计是 Parallel Commits——传统两阶段提交需要两轮网络往返(prepare → commit),CockroachDB 把 commit 决策与最后一批写操作合并在一轮中完成,并用 TLA+ 数学证明了正确性。

TiDB vs CockroachDB 的核心差异

维度 TiDB CockroachDB
计算存储 分离(tidb-server / TiKV / TiFlash) 一体(每个节点全功能)
兼容协议 MySQL PostgreSQL
HTAP 强(TiKV 行存 + TiFlash 列存) 弱(只有行存)
一致性 Snapshot Isolation(默认) Serializable(默认)
编程语言 Go + Rust + C++ Go
仓库结构 多仓库(tidb + tikv + pd) 单仓库
存储引擎 RocksDB(in TiKV) Pebble(自研)
形式化验证 TLA+(ParallelCommits 等)
社区 中文为主 英文为主
Stars ~41K ~32K

模式五:双协议兼容模式——YugabyteDB 的两面性

核心思路

一句话:一个存储引擎(DocDB),上面跑两种查询语言——YSQL(PostgreSQL 兼容)和 YCQL(Cassandra 兼容)。

想象一个翻译官同时会中文和英文。不管你用哪种语言问他问题,他内部的思考过程是一样的(DocDB),但他能用你的语言给你答复。这意味着一个团队里——需要复杂 SQL 查询的后端开发者和需要低延迟 KV 访问的实时系统开发者——可以用同一个数据库。

用户 A(需要 SQL)         用户 B(需要 NoSQL)
     |                          |
     v                          v
  ┌──────────┐            ┌──────────┐
  │  YSQL    │            │  YCQL    │
  │ (PG 兼容)│            │(Cassandra│
  │ pggate/  │            │  兼容)   │
  └────┬─────┘            └────┬─────┘
       │                       │
       └───────────┬───────────┘
                   v
            ┌──────────────┐
            │   DocDB      │
            │ (RocksDB +   │
            │  Raft)       │
            │  HLC 时钟    │
            └──────────────┘

关键设计决策和取舍

决策:双协议并存

收益

代价

五种模式的全景对比

现在让我们把五种模式放在一起,看看它们在各个维度上的差异:

侵入程度光谱

侵入性:低 ◄────────────────────────────────────► 高

    Citus          Vitess        OpenTenBase     TiDB/CRDB
   (PG扩展)      (MySQL代理)    (PG Fork)       (从零造)
     │              │              │              │
     │  不改一行     │  不改一行    │  深度改造     │  完全自研
     │  PG代码      │  MySQL代码   │  PG代码      │  
     │              │              │              │
  能力:有限 ◄────────────────────────────────────► 强大
  维护:简单 ◄────────────────────────────────────► 复杂

对比矩阵

维度 Citus (扩展) Vitess (代理) OpenTenBase (Fork) TiDB (原生) CockroachDB (原生) YugabyteDB (双协议)
基于 PostgreSQL MySQL Postgres-XL/PG 从零 从零 从零
改动程度 零(hook) 零(代理) 深度(#ifdef) 全新 全新 全新
PG/MySQL 兼容 极高 极高 中(MySQL) 中(PG) 中(PG+CQL)
分布式事务 2PC 有限 GTM 全局 Raft+MVCC Raft+MVCC Raft+MVCC
HTAP 列存扩展 行+列双引擎 TiKV+TiFlash 有限 有限
版本跟进 不依赖 不依赖 不依赖
代码量 ~1K 文件 ~3.5K ~3K ~5K+ ~15K+ ~10K
入门难度

选型决策树

你需要分布式数据库吗?
    │
    ├── 只需要 MySQL 水平扩展,不想改应用代码
    │   └── Vitess
    │
    ├── 已经在用 PostgreSQL,想最小改动实现分布式
    │   └── Citus
    │
    ├── 需要 PG 兼容 + HTAP + 分布式事务
    │   └── OpenTenBase
    │
    ├── 需要 MySQL 兼容 + HTAP
    │   └── TiDB
    │
    ├── 需要最强一致性 + 全球多活
    │   └── CockroachDB
    │
    └── 需要同时支持 SQL 和 NoSQL
        └── YugabyteDB

常见误区

误区一:”原生分布式一定比扩展/中间件模式好” → 正确理解:每种模式有自己的最佳场景。如果你的数据量在几十 TB 以内、已经在用 PostgreSQL、团队不想学新技术,Citus 可能是最好的选择。”最好”取决于你的约束条件,不是技术的先进程度。

误区二:”Fork 模式已经过时了” → 正确理解:OpenTenBase 在腾讯内部(微信支付等)有大规模生产验证。Fork 模式的优势在于能做更深层的优化(如 GTM 全局事务),这是扩展模式做不到的。代价是版本跟进的维护成本。

误区三:”中间件模式能力太有限,不值得考虑” → 正确理解:Vitess 管理着 YouTube 的数万个 MySQL 节点,处理着全球最大视频平台的数据库负载。中间件模式的”限制”恰恰是它的”边界”——不碰内核意味着不引入新的 bug,不影响已有的 DBA 工具链。

读完本章你能做什么


导读目录:README.md 上一章:第 2 章 · 阅读路线图与前置知识 下一章:第 4 章 · 共识与复制——让多台机器达成一致