4、Doris深度剖析(上):架构演进、FE/BE角色、数据模型与物化视图
Doris 这个项目,我接触得比较早。记得 2018 年那会儿,它还叫 Palo,是百度内部的一个 OLAP 引擎。后来捐给了 Apache 基金会,改名叫 Doris,社区一下子就活跃起来了。
说实话,Doris 给我的第一印象就是「实在」。它不像某些引擎那样吹得天花乱坠,但用起来顺手,性能也稳。今天咱们就好好聊聊它的核心设计。
4.1 架构演进:从 Palo 到 Doris
Doris 的前身 Palo,最初是为了解决百度内部广告报表的实时查询问题。那时候市面上没什么好用的开源 OLAP 引擎,百度就自己搞了一套。
Palo 的设计理念很纯粹:用 MPP 架构 + 列式存储,把 SQL 查询速度提上去。这个思路现在看来很常规,但在当时算是比较前沿的。
2017 年,百度把 Palo 捐给了 Apache 基金会。社区接手后做了几件大事:
- 重构了导入模块:支持了 Broker Load、Stream Load 等多种导入方式
- 优化了查询优化器:从 RBO 升级到了 CBO,复杂查询不再「瞎跑」
- 完善了生态:对接了 Spark、Flink、Kafka 等主流组件
我个人觉得,Doris 能火起来,核心原因就两个:一是架构简单清晰,二是运维成本低。你想想看,一个几千台节点的集群,如果架构复杂到没人能看懂,那谁敢用?
核心观点:Doris 的架构演进始终围绕「易用性」和「高性能」两个目标。它没有追求大而全,而是把基础功能做到极致。
4.2 FE 与 BE:分工明确的兄弟俩
Doris 的架构只有两个核心组件:FE(Frontend) 和 BE(Backend)。说白了,就是「大脑」和「肌肉」的关系。
4.2.1 FE:集群的大脑
FE 负责所有「动脑子」的活:
- SQL 解析与优化:把 SQL 变成执行计划
- 元数据管理:记录表结构、分区信息、副本位置等
- 查询调度:决定哪个 BE 去执行哪部分任务
- 负载均衡:把请求均匀分配到各个 BE
FE 可以部署多个,形成 Master-Slave 架构。Master 负责写操作,Slave 负责读操作。如果 Master 挂了,Slave 会自动选举一个新的 Master。
我的经验:FE 节点建议至少部署 3 个,形成高可用。我曾经见过一个客户只部署了 1 个 FE,结果 FE 宕机后整个集群都不可用了……嗯,那场面挺尴尬的。
4.2.2 BE:集群的肌肉
BE 负责所有「卖力气」的活:
- 数据存储:列式存储,按 Tablet 分片
- 数据计算:执行 FE 下发的查询任务
- 数据导入:处理 Stream Load、Broker Load 等导入请求
- 副本管理:保证数据的高可用
BE 之间没有主从关系,所有 BE 都是对等的。这种设计让 Doris 的扩展变得非常简单——加机器就行了,不需要做任何配置变更。
我画了一张图,帮你理解 FE 和 BE 的关系:
4.3 数据模型:三种选择,三种场景
Doris 提供了三种数据模型,分别对应不同的业务场景。选对了模型,查询性能能翻倍;选错了,那就等着被业务方吐槽吧。
4.3.1 Aggregate 模型
这个模型的核心思想是:提前聚合。数据在导入时就按照指定的聚合函数进行预计算,查询时直接读取聚合后的结果。
适用场景:
- 用户行为分析(PV/UV)
- 广告点击统计
- 财务汇总报表
举个例子:
-- 创建 Aggregate 模型表
CREATE TABLE user_visit_agg (
user_id BIGINT,
visit_date DATE,
visit_count BIGINT SUM,
total_time BIGINT SUM
)
AGGREGATE KEY (user_id, visit_date)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;
这里 visit_count 和 total_time 使用了 SUM 聚合函数。导入多条相同 Key 的数据时,Doris 会自动求和。
注意:Aggregate 模型一旦定义,聚合函数就不能改了。我曾经有个项目,一开始用了 SUM,后来业务要改成 AVG,结果只能重建表……所以设计阶段一定要想清楚。
4.3.2 Unique 模型
Unique 模型解决的是数据去重问题。它保证相同 Key 的数据只有一条,后导入的会覆盖先导入的。
适用场景:
- 订单表(每个订单只有一条记录)
- 用户信息表(每个用户只有一条记录)
- 实时更新场景
建表语句:
-- 创建 Unique 模型表
CREATE TABLE order_unique (
order_id BIGINT,
user_id BIGINT,
product_id BIGINT,
amount DECIMAL(10,2),
create_time DATETIME
)
UNIQUE KEY (order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 10;
Unique 模型底层其实是用 Aggregate 模型实现的——它把非 Key 列的聚合函数设为 REPLACE。所以性能上,Unique 和 Aggregate 差别不大。
4.3.3 Duplicate 模型
Duplicate 模型最简单,它不进行任何聚合或去重,数据原样存储。适合明细数据查询。
适用场景:
- 日志明细查询
- 原始数据存储
- 需要保留所有历史记录的场景
建表语句:
-- 创建 Duplicate 模型表
CREATE TABLE log_duplicate (
log_id BIGINT,
user_id BIGINT,
event_type VARCHAR(50),
event_time DATETIME,
event_data TEXT
)
DUPLICATE KEY (log_id, event_time)
DISTRIBUTED BY HASH(log_id) BUCKETS 10;
三种模型的对比:
| 模型 | 核心机制 | 适用场景 | 性能特点 |
|---|---|---|---|
| Aggregate | 预聚合 | 汇总报表、统计分析 | 查询快,导入稍慢 |
| Unique | 去重覆盖 | 订单、用户信息 | 查询快,更新友好 |
| Duplicate | 原样存储 | 日志、明细查询 | 导入最快,查询灵活 |
4.4 Rollup 与物化视图:加速查询的利器
Rollup 和物化视图,说白了就是用空间换时间。它们提前把一些常用的聚合结果存下来,查询时直接读,不用再现场算。
4.4.1 Rollup:多维度的预聚合
Rollup 是 Doris 早期就有的功能。它允许你在 Base 表上创建多个上卷的维度组合。
举个例子,Base 表有 user_id、date、city 三个维度,你可以创建以下 Rollup:
- Rollup 1:按
user_id+date聚合 - Rollup 2:按
date+city聚合 - Rollup 3:按
city聚合
查询时,Doris 会自动选择最合适的 Rollup。比如查询「某天各城市的 PV」,它会直接走 Rollup 2,不用扫描全表。
-- 创建 Rollup
ALTER TABLE user_visit_agg ADD ROLLUP rollup_city_date (city, visit_date, visit_count);
我的建议:Rollup 不是越多越好。每个 Rollup 都会占用存储空间,也会拖慢导入速度。我一般只给最常用的 2-3 个查询维度建 Rollup。
4.4.2 物化视图:更灵活的预计算
物化视图是 Doris 1.2 版本引入的新功能。相比 Rollup,它更灵活:
- 支持更复杂的聚合:比如 COUNT DISTINCT、AVG 等
- 支持过滤条件:可以只物化部分数据
- 支持 JOIN:虽然目前还比较有限
举个例子,创建一个物化视图来加速 UV 查询:
-- 创建物化视图
CREATE MATERIALIZED VIEW mv_uv AS
SELECT
visit_date,
city,
COUNT(DISTINCT user_id) AS uv
FROM user_visit_agg
GROUP BY visit_date, city;
查询时,Doris 会自动匹配物化视图:
-- 这个查询会自动走物化视图
SELECT visit_date, city, COUNT(DISTINCT user_id) AS uv
FROM user_visit_agg
GROUP BY visit_date, city;
Rollup 和物化视图的对比:
| 特性 | Rollup | 物化视图 |
|---|---|---|
| 支持的聚合函数 | SUM、MIN、MAX、COUNT | 更丰富(含 COUNT DISTINCT) |
| 过滤条件 | 不支持 | 支持 |
| 自动匹配 | 支持 | 支持 |
| 存储开销 | 较低 | 较高 |
| 适用版本 | 所有版本 | 1.2+ |
总结一下:如果你的查询模式比较固定,用 Rollup 就够了。如果查询比较复杂,或者需要 COUNT DISTINCT,那就上物化视图。两者可以共存,但要注意存储成本。