The source notes for this track are currently maintained in Chinese.
1. Apache Doris 是什么
Apache Doris 是面向实时分析的 MPP 架构 + 列式存储数据库。定位在大规模数据(单表百亿行级)上的亚秒级查询响应, 是 ClickHouse/StarRocks/TiDB 等同类产品的直接竞争者。
核心特性:
- 亚秒级查询: Bitmap 索引 + ZoneMap + 向量化 + MPP 并行
- 实时导入: Stream Load 支持秒级数据可见, Routine Load 支持 Kafka 订阅
- MySQL 兼容: 标准 MySQL 协议, 可直接用 MySQL Client 或 JDBC 连接
- 联邦查询: 通过 External Catalog 直接查询 Hive/Iceberg/Hudi/MySQL 等外部数据
- 物化视图: 同步 + 异步物化视图, 自动查询改写
2. 核心架构——FE / BE 拓扑

FE (Frontend)
- 语言: Java
- 角色: 三种
角色 功能 数量限制 Master 唯一写入者: DDL + Journal 写入 + 集群协调 1 Follower 元数据复制(候选 Master) + 查询服务 N (建议 2~5) Observer 纯查询服务, 不参与选举和 Journal 写入 N (不限) - 启动所有角色的 JVM 进程完全相同, 角色由 BDBJE(嵌入式 Berkeley DB Java Edition)选举决定
- Master FE 挂了 -> BDBJE 自动选举新 Master(Follower 晋升)
BE (Backend)
- 语言: C++
- 角色: 无状态计算节点, 没有 Master/Slave 之分
- 每个 BE 管理本地磁盘上的多个 Tablet(数据分片)
- 数量: 不限, 水平扩展
FE ↔ BE 通信
| 场景 | 协议 | 说明 |
|---|---|---|
| DDL / 心跳 / 控制命令 | Thrift RPC | 如 CreateTable, TabletHeartbeat, PublishVersion |
| 数据流(扫描/写入/Exchange) | Brpc (百度 RPC) | 高性能, 支持流控和 Backup Request |
3. 关键术语速览
| 术语 | 定义 | 类比 |
|---|---|---|
| Catalog | 元数据容器(内表 + 外表) | MySQL 的 "server instance" |
| Database | 数据库, 包含多个 Table | MySQL database |
| Table | 表, 包含多个 Partition | MySQL table |
| Partition | 按范围/列表划分的数据分区 | Hive partition |
| Bucket (Tablet) | 表按 Hash 分桶后的最小数据分片, | ClickHouse 的 part 或 HBase 的 region |
| Replica | Tablet 的副本, 默认 3 | HDFS block replica |
| Rowset | 一次写入产生的不可变数据集合(含多个 Segment 文件) | LSM-Tree 的 SSTable |
| Segment | 列存物理文件 | Parquet 的 Row Group |
| Version | Tablet 版本号(单调递增 int64), 每次写入递增 | LSM-Tree 的 sequence number |
| Fragment | 物理计划的分布式执行单元(按 Exchange 切分) | Spark 的 Stage |
| Pipeline | Fragment 中的执行流水线(由 Operator 组成) | 数据库的 "执行流水线" |
| Coordinator | FE 端的查询执行协调器, 负责生成 Fragment 并调度到 BE | 分布式调度器 |
4. Doris 的核心设计决策
| # | 决策 | Doris 的选择 | 为什么 | 代价 |
|---|---|---|---|---|
| 1 | 存算一体 vs 存算分离 | Classic 版存算一体, Cloud 版存算分离 | 存算一体消除网络开销, 查询延迟低; Cloud 版面向弹性 | 扩缩容需迁移数据(Classic); Cloud 版有网络延迟 |
| 2 | 分片模型 | Tablet + Multi-Replica (默认 3 副本) | Tablet 粒度适中(~1-10GB), 便于负载均衡和修复 | 3 副本存储成本, 写放大 |
| 3 | 一致性协议 | Version-based (非 Paxos) | Master FE 是唯一写入者, Publish 机制 = 2PC-lite, 简单高效 | 依赖 Master FE 存活 |
| 4 | 存储格式 | 列存 Segment + Page 分页 + 多编码压缩 | 列存 = I/O 减量 + SIMD 友好 | OLTP 点查不占优(但有 ShortKey/MOW 补充) |
| 5 | 执行引擎 | MPP + Pipeline + 向量化 | MPP 天然分布式; Pipeline 减少内存; 向量化利用 SIMD | 实现复杂度高(三层叠加) |
| 6 | SQL 优化器 | Cascades (Nereids), 2023 年引入 | Cascades = 规则可扩展, 支持 CBO + 物化视图自动改写 | 比旧 RBO 复杂, 有学习曲线 |
| 7 | 元数据存储 | BDBJE Replicated Journal | 成熟的嵌入式 KV 存储, Java 原生, Replication 开箱即用 | 主要维护者少; Java 堆内存大; 只支持单写 |
| 8 | 外部数据 | External Catalog (HMS/Iceberg/Hudi/ES/MySQL) | 统一查询入口, 自动统计信息收集 | 查询性能取决于外部系统 |
| 9 | 实时写入 | Label 去重 + 两阶段 Publish (不依赖外部事务协调器) | 简单, 不依赖 ZooKeeper/Kafka 做协调 | Exactly-once 语义有边界(Label 有效期内) |
5. 一条 SQL 在 Doris 中的旅行

关键标注: 步骤 ①⑥ → FE (doc-01 §1-6), 步骤 ⑦⑪ → BE (doc-01 §7-10), 步骤 ⑨ → Storage (doc-06/08)
6. 源码导航——代码库地图
FE (Java)
| 架构组件 | 代码路径 | 入口类/关键方法 |
|---|---|---|
| MySQL 协议层 | fe/fe-core/src/main/java/org/apache/doris/mysql/ | MysqlServer, MysqlChannel, ConnectProcessor.processOnce() |
| Nereids Parser | fe/fe-core/.../nereids/parser/ | NereidsParser, 基于 ANTLR(gensrc/antlr4/) |
| Nereids Analyzer | fe/fe-core/.../nereids/analyzer/ | Analyzer, Scope, CatalogBinder |
| Nereids Rules | fe/fe-core/.../nereids/rules/ | 100+ 优化规则(RBO+CBO) |
| Nereids Cost | fe/fe-core/.../nereids/cost/ | CostModel, CostCalculator |
| Nereids Stats | fe/fe-core/.../nereids/stats/ | Statistics, StatsCalculator |
| 物理计划 | fe/fe-core/.../nereids/plans/physical/ | 各类 Physical* 算子 |
| 查询协调 | fe/fe-core/.../qe/Coordinator.java | Coordinator.exec(), FragmentMgr |
| Catalog (元数据) | fe/fe-core/.../catalog/ | Env, CatalogIf, Database, OlapTable, Tablet |
| Journal (持久化) | fe/fe-core/.../journal/ | Journal, EditLog, BDBJEJournal |
| FE HA | fe/fe-core/.../ha/ | HaProtocol, BDBHA |
| 数据导入 | fe/fe-core/.../load/ | StreamLoadHandler, BrokerLoadJob, RoutineLoadManager |
| 表变更 | fe/fe-core/.../alter/ | Alter, SchemaChangeHandler, RollupHandler |
| 克隆/修复 | fe/fe-core/.../clone/ | TabletScheduler, TabletChecker |
BE (C++)
| 架构组件 | 代码路径 | 入口类/关键方法 |
|---|---|---|
| BE 服务入口 | be/src/service/ | doris_main.cpp, BackendService, PInternalService |
| Pipeline 引擎 | be/src/exec/pipeline/ | Pipeline, PipelineTask, PipelineFragmentContext |
| 向量化执行 | be/src/vec/ | Block (vec/core/), IColumn (vec/columns/), ColumnVector<T> (core/column/) |
| 向量化表达式 | be/src/vec/exprs/ | VectorizedExpr, VExprContext |
| 向量化函数 | be/src/vec/functions/ | IFunction, FunctionSimpleUnary, AggregateFunction |
| 存储引擎 | be/src/storage/ | Tablet (storage/tablet/), BetaRowset (storage/rowset/), SegmentWriter (storage/rowset/segment_v2/) |
| Compaction | be/src/storage/compaction/ | CumulativeCompaction, BaseCompaction, CompactionMerger |
| 索引 | be/src/storage/index/ | ZoneMapIndex, OrdinalIndex, BloomFilter, BitmapIndex |
| 数据导入 | be/src/load/ | TabletWriter, DeltaWriter, LoadStreamStub |
| I/O 层 | be/src/io/ | FileReader, S3Reader, HdfsReader, FileCacheAction (service/http/) |
| 运行时 | be/src/runtime/ | MemTracker, MemPool, RuntimeState, RuntimeFilter |
| 执行算子 | be/src/exec/ | OlapScanOperator, HashJoinNode, AggregationNode |
RPC IDL
| 协议 | 文件路径 | 说明 |
|---|---|---|
| Thrift FE ↔ BE | gensrc/thrift/FrontendService.thrift | FE RPC 接口 |
gensrc/thrift/BackendService.thrift | BE RPC 接口 | |
gensrc/thrift/Data.thrift | 数据类型定义 | |
| Protobuf | gensrc/proto/olap_file.proto | Segment 文件格式 |
gensrc/proto/internal_service.proto | BE 间数据交换 |
7. 学习路线图
doc-00 架构概览 (你在这里)
★ 15-20 分钟
│
┌─────────────┴─────────────┐
▼ ▼
doc-01 查询链路 doc-02 写入链路
★ 90-120 分钟 ★ 90-120 分钟
SQL → Result Client → Publish
│ │
└─────────┬─────────────────┘
│
┌─────────────┼──────────────┬───────────────┐
▼ ▼ ▼ ▼
doc-03 doc-04 doc-05 doc-06
Nereids Pipeline 向量化计算 Tablet/Rowset
优化器 执行引擎 框架 /Segment
60 min 45 min 40 min 50 min
│ │ │ │
▼ ▼ ▼ ▼
doc-07 doc-08 doc-09 doc-10
Compaction 存储索引 Catalog 数据导入
版本管理 45 min 元数据 体系全览
40 min 45 min 50 min
│ │ │ │
▼ ▼ ▼ ▼
doc-11 doc-12 doc-13 doc-14
FE HA BE 副本 RPC 通信 资源管理
元数据复制 一致性/修复 框架 Workload Group
30 min 30 min 30 min 40 min
│
▼
doc-15
Doris Cloud 存算分离
40 min
依赖约束: 除 doc-00 必须先读, doc-15 最后读外, 其他可以按兴趣跳读。推荐至少读完 doc-01 和 doc-02 两条主线后再跳读。
实操建议:
- 本地编译:
sh build.sh --fe --be(需要 JDK 17 + Clang 17+, macOS 需安装brew install cmake llvm@17) - FE 调试: IntelliJ IDEA → 添加 Remote JVM Debug 配置 → Port 5005 → 打断点在感兴趣的类 → 用 MySQL Client 触发 SQL 进入断点
- BE 调试: 用 lldb/gdb attach 到 doris_be 进程, 推荐断点位置:
OlapScanOperator::get_block()(扫描),PipelineTask::execute()(Pipeline 执行),SegmentWriter::append_block()(写入) - 单测定位:
- FE:
fe/fe-core/src/test/java/org/apache/doris/→ 找*Test.java→ 运行单测验证理解 - BE:
be/test/→ 找*_test.cpp→ 编译运行
- FE:
- 逆向理解法: 先读对应模块的测试(尤其 FE 单测往往包含完整的 MiniCluster 场景), 从测试预期反推 API 契约
8. 常见问题 / 面试题
Q1: Doris vs ClickHouse 的核心区别? A: Doris 定位为"实时数据仓库" (支持高并发点查 + 复杂分析 + 实时写入), ClickHouse 更偏向"分析引擎"(在线聚合), 写入路径和并发控制模型不同。Doris 有完整的 MySQL 协议支持和事务语义(Label 去重/两阶段提交), ClickHouse 更注重单查询性能极致优化。此外 Doris 支持 Routine Load 实时消费 Kafka, ClickHouse 需要外部工具配合。
Q2: 为什么 FE 不直接用 MySQL/PG 存元数据, 而用 BDBJE? A: BDBJE 是嵌入式 Java KV 存储, 自带 Replicated Journal (类 Raft), 零运维依赖。如果用 MySQL 存元数据则需额外部署 MySQL 集群, 引入外部依赖, 且不支持 WAL 复制语义。BDBJE 的开销: 堆内存高(所有元数据在内存), 序列化启动慢(pb 文件很大)。
Q3: 为什么 Doris 不直接用 Paxos/Raft 做副本一致性? A: Doris 的写入路径是单写者(Master FE)→ 多 BE 副本, 不需要 Raft 提供的一致性保证。FE 的 PublishVersion RPC 相当于简化版 2PC: FE 确认所有副本写入成功后 Publish, 副本失败触发 Clone/Repair。这比 Raft 简单高效, 但理论上多副本间版本差异窗口很大(取决于 Publish 频率)。
Q4: Doris Classic (存算一体) vs Cloud (存算分离) 什么时候应该用哪个? A: Classic 适合: 数据规模相对稳定(10~100节点), 追求最低延迟(本地磁盘读取)。Cloud 适合: 数据弹性伸缩需求高, 计算和存储需要各自独立扩缩容, 数据放在对象存储降低成本。关键权衡: Cloud 版多了 FileCache (本地 NVMe 缓存) 层来补偿对象存储的延迟。
Q5: 一个 Tablet 多大合适? 为什么默认 ~10GB?
A: 太小的 Tablet 导致元数据膨胀(FE 内存中每个 Tablet 都有元数据条目), 太多 Tablet 使扫描时需要合并更多 Rowset(影响查询性能)。太大的 Tablet 导致负载不均衡, 某个 BE 可能过热。经验值 ~1-10GB, 由 dynamic_partition.buckets 参数和总数据量决定。

下一步
- 理解查询: doc-01-query-lifecycle.md——追踪一条 SQL 从客户端到磁盘再返回的完整旅程
- 理解写入: doc-02-write-lifecycle.md——追踪 Stream Load 从 HTTP 请求到 Rowset 生成的全过程