Doris architecture

ANALYTICAL DATABASE / SOURCE READING / LESSON 00

Apache Doris architecture overview

A Chinese deep dive into Doris FE/BE topology, metadata, execution, storage, and external-system boundaries.

Reading
15-20 min
Track
Doris architecture
Source
Chinese source notes

The source notes for this track are currently maintained in Chinese.

预计阅读时间: 15-20 分钟 前置阅读: 无 下一次阅读: doc-01(查询链路) 或 doc-02(写入链路)


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 拓扑

Apache Doris 架构概览 图 01

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数据库, 包含多个 TableMySQL database
Table表, 包含多个 PartitionMySQL table
Partition按范围/列表划分的数据分区Hive partition
Bucket (Tablet)表按 Hash 分桶后的最小数据分片, 110 GBClickHouse 的 part 或 HBase 的 region
ReplicaTablet 的副本, 默认 3HDFS block replica
Rowset一次写入产生的不可变数据集合(含多个 Segment 文件)LSM-Tree 的 SSTable
Segment列存物理文件Parquet 的 Row Group
VersionTablet 版本号(单调递增 int64), 每次写入递增LSM-Tree 的 sequence number
Fragment物理计划的分布式执行单元(按 Exchange 切分)Spark 的 Stage
PipelineFragment 中的执行流水线(由 Operator 组成)数据库的 "执行流水线"
CoordinatorFE 端的查询执行协调器, 负责生成 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实现复杂度高(三层叠加)
6SQL 优化器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 中的旅行

Apache Doris 架构概览 图 02

关键标注: 步骤 ①⑥ → 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 Parserfe/fe-core/.../nereids/parser/NereidsParser, 基于 ANTLR(gensrc/antlr4/)
Nereids Analyzerfe/fe-core/.../nereids/analyzer/Analyzer, Scope, CatalogBinder
Nereids Rulesfe/fe-core/.../nereids/rules/100+ 优化规则(RBO+CBO)
Nereids Costfe/fe-core/.../nereids/cost/CostModel, CostCalculator
Nereids Statsfe/fe-core/.../nereids/stats/Statistics, StatsCalculator
物理计划fe/fe-core/.../nereids/plans/physical/各类 Physical* 算子
查询协调fe/fe-core/.../qe/Coordinator.javaCoordinator.exec(), FragmentMgr
Catalog (元数据)fe/fe-core/.../catalog/Env, CatalogIf, Database, OlapTable, Tablet
Journal (持久化)fe/fe-core/.../journal/Journal, EditLog, BDBJEJournal
FE HAfe/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/)
Compactionbe/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 ↔ BEgensrc/thrift/FrontendService.thriftFE RPC 接口
gensrc/thrift/BackendService.thriftBE RPC 接口
gensrc/thrift/Data.thrift数据类型定义
Protobufgensrc/proto/olap_file.protoSegment 文件格式
gensrc/proto/internal_service.protoBE 间数据交换

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 单测往往包含完整的 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 参数和总数据量决定。


Doris 五层架构总览

下一步