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 的关系:

Doris FE/BE 架构图 FE Master FE Slave 1 FE Slave 2 FE 职责 SQL 解析与优化 元数据管理 查询调度 负载均衡 查询计划 调度指令 BE 1 BE 2 BE 3 BE 职责 数据存储(列式) 数据计算 数据导入 副本管理 客户端 SQL 请求

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,那就上物化视图。两者可以共存,但要注意存储成本。