未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文主要用于理解Doris经典存算一体架构,不是已验证的生产部署或调优手册。原稿未注明版本;压缩算法支持及默认值、并行参数效果需按目标版本核验。目录树仅作概念示意,不能用于定位真实文件;查询图简化了协调与汇聚过程,普通查询也可由非Leader FE接入,聚合通常在BE执行。SQL依赖已有示例表与数据,修改索引、压缩和副本配置前应在测试环境验证;FE高可用须保证有投票权节点的多数派存活。
Apache Doris架构深度解析 - 分布式MPP引擎的技术内幕
abstract 📋 本节概览
- 🎯 学习目标:深度掌握Doris的分布式架构设计原理、FE/BE协作机制和查询执行流程
- 📚 前置知识:Doris概述与特性、分布式系统基础概念
- ⏱️ 预计用时:30分钟阅读 + 45分钟深入理解
- 🔗 相关文档:Doris、Doris核心概念
🌟 为什么要深入理解Doris架构?
quote 💭 架构理解的重要性
理解系统架构就像理解一座城市的规划:只有掌握了道路网络、功能分区和交通流向,才能在其中高效工作和生活。对于Doris架构的深度理解,将帮助你在实际使用中做出最优的设计决策,并在遇到性能问题时快速定位和解决。
🎯 架构知识的实际价值
技术决策能力
- 合理规划集群规模和节点配置
- 优化查询性能和资源利用率
- 设计高可用和容灾方案
问题排查能力
- 快速定位性能瓶颈和故障根因
- 理解监控指标的业务含义
- 制定有效的运维策略
系统优化能力
- 基于架构特性进行SQL优化
- 合理设计数据分布和存储策略
- 提升系统整体性能
💡 Doris整体架构
Doris采用经典的MPP(大规模并行处理)架构,主要由两类节点组成:
- FE(Frontend) - 前端节点,负责元数据管理、查询规划和协调
- BE(Backend) - 后端节点,负责数据存储和查询执行
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
FE[FE Node]
Client[客户端应用]
FE_Leader[前端节点FE Leader]
FE_Follower1[FE Follower 1]
FE_Follower2[FE Follower 2]
FE_Observer[FE Observer]
BE1[后端节点BE-1]
BE2[后端节点BE-2]
BE3[后端节点BE-3]
Client -->|SQL查询| FE_Leader
subgraph "FE集群(元数据管理)"
FE_Leader
FE_Follower1
FE_Follower2
FE_Observer
end
FE_Leader -->|主从复制| FE_Follower1
FE_Leader -->|主从复制| FE_Follower2
FE_Observer -.->|只读| FE_Leader
FE_Leader -->|1.分发查询计划| BE1
FE_Leader -->|1.分发查询计划| BE2
FE_Leader -->|1.分发查询计划| BE3
BE1 -->|2.并行执行| BE2
BE2 -->|2.并行执行| BE3
BE3 -->|2.并行执行| BE1
BE1 -->|3.返回结果| FE_Leader
BE2 -->|3.返回结果| FE_Leader
BE3 -->|3.返回结果| FE_Leader
FE_Leader -->|4.合并结果| Client
subgraph "BE集群(数据存储与计算)"
BE1
BE2
BE3
end
class FE,Client,FE_Leader,FE_Follower1,FE_Follower2,FE_Observer,BE1,BE2,BE3 default
🔍 FE(Frontend)节点详解
核心功能
- 元数据管理:维护数据库、表、分区等元数据信息
- 查询解析与规划:SQL解析、语义分析、查询优化、执行计划生成
- 集群管理:节点健康检查、负载均衡、资源调度
- 用户权限管理:认证与授权
- 查询协调:分发执行计划、收集结果、错误处理
FE节点角色
| 角色 | 数量 | 功能 | 元数据写入 | 元数据读取 | 处理查询 |
|---|---|---|---|---|---|
| Leader | 1 | 主节点,负责元数据写入 | ✅ | ✅ | ✅ |
| Follower | 0-N(高可用常见配置为2个) | 备用主节点,参与选举 | ❌ | ✅ | ✅ |
| Observer | 0-N | 只读节点,提供读扩展 | ❌ | ✅ | ✅ |
-- 查看FE节点状态
SHOW FRONTENDS;
-- 添加Follower节点
ALTER SYSTEM ADD FOLLOWER "fe_host:9010";
-- 添加Observer节点
ALTER SYSTEM ADD OBSERVER "fe_host:9010";
元数据管理
FE采用类似Paxos的多数派协议保证元数据一致性,元数据以journal日志形式持久化:
+-- meta/
+-- bdb/
| +-- 00000000.jnl
| +-- 00000001.jnl
+-- image/
+-- metadata.000001
📊 BE(Backend)节点详解
核心功能
- 数据存储:管理数据文件、副本、版本
- 查询执行:执行查询计划、数据过滤、聚合计算
- 数据导入:接收并处理数据导入请求
- 数据压缩与索引:管理数据压缩和索引构建
BE存储结构
+-- storage/
+-- data/
+-- db_id/
+-- table_id/
+-- partition_id/
+-- rowset_id/
+-- segment_id.dat
数据组织形式
Doris采用分层数据组织结构:
- Tablet:数据分片的基本单位,对应一个表的一个分区的一个分桶
- Rowset:Tablet内一组数据的集合,可由导入或Compaction产生,不与一次导入全局一一对应
- Segment:Rowset内的数据文件,按列存储
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
Table[表] --> Partition1[分区1]
Table --> Partition2[分区2]
Partition1 --> Tablet1[Tablet-1]
Partition1 --> Tablet2[Tablet-2]
Tablet1 --> Rowset1[Rowset-1]
Tablet1 --> Rowset2[Rowset-2]
Rowset1 --> Segment1[Segment-1]
Segment1 --> Column1[文件内列1数据]
Segment1 --> Column2[文件内列2数据]
Segment1 --> Column3[文件内列3数据]
class Table,Partition1,Partition2,Tablet1,Tablet2,Rowset1,Rowset2,Segment1,Column1,Column2,Column3 default
🚀 查询执行流程
1. 查询接收与解析
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
sequenceDiagram
participant Client
participant FE as FE Node
Client->>FE: 发送SQL查询
FE->>FE: SQL解析
FE->>FE: 语义分析
FE->>FE: 查询重写
FE->>FE: 生成逻辑计划
2. 查询优化与规划
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
sequenceDiagram
participant FE as FE Node
FE->>FE: 逻辑优化(谓词下推、列裁剪等)
FE->>FE: 物理计划生成
FE->>FE: 物理优化(分区裁剪、索引选择等)
FE->>FE: 分布式执行计划生成
3. 查询执行与结果返回
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
sequenceDiagram
participant BE1 as BE1 Node
participant FE as FE Node
participant BE3 as BE3 Node
participant BE2 as BE2 Node
participant Client
FE->>BE1: 分发执行计划片段
FE->>BE2: 分发执行计划片段
FE->>BE3: 分发执行计划片段
BE1-->>BE1: 执行计划片段
BE2-->>BE2: 执行计划片段
BE3-->>BE3: 执行计划片段
BE1->>BE2: 数据交换(Shuffle)
BE2->>BE3: 数据交换(Shuffle)
BE1->>FE: 返回结果
BE2->>FE: 返回结果
BE3->>FE: 返回结果
FE->>FE: 合并结果
FE->>Client: 返回最终结果
查询执行示例
-- 查询示例
SELECT
region_name,
SUM(sales_amount) as total_sales
FROM sales_fact
JOIN dim_region ON sales_fact.region_id = dim_region.region_id
WHERE sale_date BETWEEN '2023-01-01' AND '2023-01-31'
GROUP BY region_name
ORDER BY total_sales DESC
LIMIT 10;
-- 查看查询计划
EXPLAIN SELECT
region_name,
SUM(sales_amount) as total_sales
FROM sales_fact
JOIN dim_region ON sales_fact.region_id = dim_region.region_id
WHERE sale_date BETWEEN '2023-01-01' AND '2023-01-31'
GROUP BY region_name
ORDER BY total_sales DESC
LIMIT 10;
🔧 存储引擎详解
列式存储
Doris采用列式存储格式,在Segment文件内按列组织数据,并非每列必然对应一个独立文件,具有以下优势:
- 高压缩比:同类型数据放在一起,压缩效率高
- I/O减少:只读取查询需要的列
- 向量化执行:便于CPU缓存优化和SIMD指令
# 行存储 vs 列存储示意图
行存储:
+-------+----------+------+--------+
| id | name | age | score |
+-------+----------+------+--------+
| 1 | Alice | 20 | 85 |
| 2 | Bob | 22 | 90 |
| 3 | Charlie | 21 | 78 |
+-------+----------+------+--------+
列存储:
id: [1, 2, 3]
name: ["Alice", "Bob", "Charlie"]
age: [20, 22, 21]
score: [85, 90, 78]
数据压缩
Doris支持多种压缩算法,可以根据数据特点选择最适合的算法:
| 压缩算法 | 压缩比 | 压缩速度 | 解压速度 | 适用场景 |
|---|---|---|---|---|
| LZ4 | 中 | 快 | 极快 | 平衡性能与压缩比;是否为默认算法需按版本确认 |
| ZLIB | 高 | 慢 | 中 | 存储空间敏感场景 |
| ZSTD | 高 | 中 | 快 | 较好平衡压缩比和性能 |
| SNAPPY | 低 | 极快 | 极快 | 性能优先场景 |
-- 设置表的压缩算法
CREATE TABLE compressed_table (
id BIGINT,
name VARCHAR(100),
data STRING
)
DISTRIBUTED BY HASH(id) BUCKETS 10
PROPERTIES (
"compression" = "zstd"
);
索引技术
Doris提供多种索引技术加速查询:
- 前缀索引:自动基于排序键前缀创建索引,前缀长度受36字节上限、列数和字段类型等限制,并非必然覆盖排序键的前36个字节;具体规则需按目标版本确认
- Bloom Filter:高效过滤不存在的值
- Bitmap索引:适用于低基数列
- Zone Map:记录数据块的统计信息,用于快速过滤
-- 为高基数列添加Bloom Filter索引
ALTER TABLE user_behavior SET ("bloom_filter_columns" = "user_id");
-- 如已有其他Bloom Filter列,应将需保留的列一起列入该属性;执行前检查现有配置
-- 为低基数列添加Bitmap索引
ALTER TABLE user_behavior ADD INDEX idx_behavior_type (behavior_type) USING BITMAP;
📈 计算引擎详解
向量化执行引擎
Doris采用向量化执行引擎,批量处理数据,充分利用现代CPU的缓存和SIMD指令:
- 批处理:一次处理多行数据,减少函数调用开销
- SIMD指令:单指令多数据并行处理
- 流水线执行:减少数据在内存中的移动
并行执行模型
Doris采用多级并行执行模型:
- 节点间并行:多BE节点并行处理数据
- 节点内并行:单BE节点内多线程并行
- 算子内并行:单个算子内的数据并行处理
-- 设置查询并行度
SET parallel_fragment_exec_instance_num = 8;
-- 查看查询并行度
SHOW VARIABLES LIKE '%parallel%';
查询优化技术
- 分区裁剪:根据查询条件过滤不相关分区
- 谓词下推:尽早过滤数据
- 运行时过滤:利用Join条件动态生成过滤条件
- 物化视图选择:自动选择最优物化视图
- CBO优化:基于成本的优化器
⚠️ 常见问题与解决方案
1. FE节点故障
- 问题:FE Leader节点宕机
- 解决方案:
- 有投票权的FE节点保持多数派存活时,Follower节点可自动选举新Leader;Observer不参与投票
- 高可用部署通常采用3个有投票权的FE节点(1 Leader + 2 Follower),并分散故障域;Observer不能替代投票节点
- 使用Observer节点扩展读能力
2. BE节点故障
- 问题:BE节点宕机导致数据不可用
- 解决方案:
- 设置适当的副本数(通常为3)
- 开启副本自动修复功能
- 合理规划BE节点部署,避免单点故障
-- 设置表的副本数
CREATE TABLE replicated_table (
id BIGINT,
name VARCHAR(100)
)
DISTRIBUTED BY HASH(id) BUCKETS 10
PROPERTIES (
"replication_num" = "3"
);
-- 查看副本状态
SHOW TABLET 12345; -- 12345为tablet_id
3. 查询性能问题
- 问题:查询执行缓慢
- 解决方案:
- 使用EXPLAIN分析查询计划
- 优化表结构和索引
- 调整并行度参数
- 创建合适的物化视图
✅ 掌握检查
- [ ] 理解Doris的整体架构和工作原理
- [ ] 掌握FE和BE节点的核心功能与角色
- [ ] 了解Doris的存储结构和数据组织方式
- [ ] 理解查询执行流程和优化技术
🔗 学习连接
前置知识: Doris概述与特性 下一步: Doris核心概念