第3章 Doris深度剖析:MPP架构、分区与分桶、Rollup与物化视图、Bitmap精确去重、Stream Load与Broker Load

Doris 这个项目,我关注了挺长时间。从早期的 Palo 到现在的 Apache Doris,它在大规模 OLAP 场景下的表现,确实让人印象深刻。这一章,咱们就把它拆开看看,聊聊几个最核心的设计。

核心观点:Doris 的成功,在于它把 MPP 架构、智能索引(Rollup/物化视图)和高效的导入机制,组合成了一个对用户非常友好的整体。你不需要像用某些系统那样,为了性能去手动调优底层存储格式。

3.1 MPP架构:不只是“分而治之”

Doris 的 MPP(大规模并行处理)架构,说白了就是让很多台机器一起干活。但它的设计里,有几个细节值得琢磨。

它把节点分成了两类:Frontend(FE) 和 Backend(BE)。FE 负责接收 SQL、解析、生成执行计划;BE 负责真正干活——存数据、算数据。

我个人觉得,Doris 的 MPP 做得好的地方在于它的向量化执行引擎。我记得在 1.0 版本之前,Doris 还是逐行处理的,性能瓶颈很明显。后来全面转向向量化,一次处理一批数据(比如 1024 行),CPU 的 Cache 命中率大幅提升。我在一个日活千万的报表场景里做过对比,同样的 SQL,向量化开启后查询耗时直接降了 60%。

为什么会这样?因为现代 CPU 的瓶颈往往不在计算,而在内存访问。向量化执行让数据在 CPU 流水线上跑得更顺畅,减少了分支预测失败和 Cache Miss。

我的建议:在部署 Doris 集群时,FE 节点建议至少 3 台,用奇数台保证选举高可用。BE 节点则根据数据量来,一般建议 10 台起步,单台 BE 的磁盘建议用 SSD,尤其是热数据。

3.2 分区与分桶:数据组织的艺术

分区和分桶,是 Doris 数据分布的核心。很多新手容易搞混,我简单说说我的理解。

  • 分区(Partition): 逻辑上的数据划分,通常按时间。比如按天分区,每天的数据放在一个独立的目录里。这主要是为了管理方便——删除旧数据直接 Drop 分区,比 Delete 快得多。
  • 分桶(Bucket): 物理上的数据分布,在每个分区内部,再按某个字段的 Hash 值,把数据打散到不同的 BE 节点上。这主要是为了并行计算——查询时可以多个 BE 同时扫描不同的桶。

我曾经遇到过一个案例:用户把一张 10 亿行的表只分了 3 个桶,结果查询时只有 3 个线程在跑,其他 BE 节点都在围观。这就是典型的“分桶不足”。

避坑指南:分桶数不是越多越好。我建议每个桶的数据量控制在 1GB ~ 10GB 之间。桶太多会导致元数据膨胀,而且小文件过多会影响导入性能。比如一张表 100GB,分 20 个桶就挺合适。

下面这张图,展示了 Doris 中分区与分桶的层级关系:

Doris 数据组织:分区 + 分桶 逻辑表 (Table) 分区 (Partition) - 按时间 2024-01-01 2024-01-02 2024-01-03 分桶 (Bucket) - 按 Hash(用户ID) 分布到 BE Bucket 0 BE-1 Bucket 1 BE-2 Bucket 2 BE-3 Bucket 3 BE-1 说明:每个分区独立管理自己的分桶。分桶数在建表时指定,后续不可修改。 查询时,Doris 会裁剪掉无关分区,并在每个桶内并行扫描。

3.3 Rollup与物化视图:空间换时间的智慧

Rollup 和物化视图,是 Doris 加速查询的两大利器。很多人觉得它们差不多,其实侧重点不同。

  • Rollup(上卷): 本质上是基表的一个预聚合副本。比如基表按“用户+时间+商品”存了明细,你可以建一个 Rollup 只按“用户+时间”做 SUM。查询时,如果 SQL 的粒度匹配 Rollup,Doris 会自动路由到 Rollup 上,扫描的数据量会少很多。
  • 物化视图: 比 Rollup 更灵活。它允许你定义任意的 SQL 查询(包括 JOIN、过滤),然后把结果物化下来。Doris 的物化视图是透明改写的——你查基表,系统自动判断能不能用物化视图替代。

我记得有一次,一个用户有个查询要跑 30 秒,里面有个复杂的 COUNT(DISTINCT user_id)。我建议他建了一个物化视图,把去重结果预计算好。改完之后,查询降到 0.5 秒。嗯,这就是物化视图的魅力。

我的经验:Rollup 适合“维度上卷”这种固定模式的加速。物化视图适合更复杂的场景,比如多表 JOIN 后的预计算。但注意,物化视图会占用额外的存储空间,而且导入时会有额外的计算开销。别建太多,挑最慢的几个查询来优化就行。

3.4 Bitmap精确去重:大基数场景的救星

说到 COUNT(DISTINCT),Doris 提供了两种方式:近似去重(HyperLogLog) 和 精确去重(Bitmap)。

近似去重速度快,但结果有误差(通常 1%~2%)。精确去重用 Bitmap,每个用户 ID 映射到一个 Bit 位上,然后做位运算。对于千万级甚至亿级的去重,Bitmap 的优势非常明显。

为什么?因为 Bitmap 的 COUNT 操作本质上是计算 1 的个数,现代 CPU 有专门的 POPCNT 指令,一个时钟周期就能算 64 位。你想想看,用传统方式去重,需要维护一个巨大的 HashSet,内存占用高,而且比较操作慢。

使用 Bitmap 去重,需要先把用户 ID 转换成整数(RoaringBitmap 要求整数类型)。Doris 提供了 BITMAP_HASH 函数来做这个转换。

-- 建表时定义 Bitmap 列
CREATE TABLE user_visit (
    dt DATE,
    user_id INT,
    visit_bitmap BITMAP BITMAP_UNION
)
AGGREGATE KEY(dt)
DISTRIBUTED BY HASH(dt) BUCKETS 10;

-- 导入数据时,用 BITMAP_HASH 转换
INSERT INTO user_visit
SELECT dt, BITMAP_HASH(user_id)
FROM source_table;

-- 查询精确 UV
SELECT dt, BITMAP_COUNT(visit_bitmap) AS uv
FROM user_visit
GROUP BY dt;

避坑指南:我曾经遇到一个坑——用户 ID 是字符串,直接用 BITMAP_HASH 转换后,发现去重结果比实际少了。原因是 BITMAP_HASH 返回的是 64 位整数,但 RoaringBitmap 内部用 32 位整数存储,高位被截断了。解决方案是先用 CRC32 或 MURMUR_HASH 把字符串转成 32 位整数,再存入 Bitmap。

3.5 Stream Load与Broker Load:导入的两种姿势

Doris 的导入方式很多,但最常用的就两个:Stream Load 和 Broker Load。

特性 Stream Load Broker Load
触发方式 HTTP 协议,客户端直接推送 SQL 命令,异步拉取
数据源 本地文件、内存数据流 HDFS、S3、OSS 等对象存储
适用场景 实时小批量(MB ~ GB 级) 离线大批量(GB ~ TB 级)
事务保证 单次导入原子性 单次导入原子性
导入速度 快(直接写入 BE) 中等(需要 Broker 中转)

我个人习惯:实时数据用 Stream Load,离线数据用 Broker Load。比如 Kafka 里的实时日志,我用 Flink 消费后,通过 Stream Load 批量写入 Doris(每 10 秒一次,每次几 MB)。而每天凌晨的 T+1 报表数据,几百 GB 存在 HDFS 上,就用 Broker Load 一次性导入。

Stream Load 的使用非常简单,一个 curl 命令就能搞定:

curl --location-trusted -u root: \
    -H "label:my_label_001" \
    -H "column_separator:," \
    -T /data/my_data.csv \
    http://fe_host:8030/api/my_db/my_table/_stream_load

这里有个关键参数 label,它保证了导入的幂等性。如果导入失败重试,Doris 会检查 label 是否已经存在,避免数据重复。嗯,这个设计很实用。

我的建议:Stream Load 的 label 建议用“业务名_时间戳_UUID”的格式,确保全局唯一。Broker Load 则建议用 SQL 方式提交,方便在调度系统里管理。

最后,我把这一章的核心知识点整理成了一张脑图,方便你回顾:

第3章:Doris 核心知识点脑图 Apache Doris MPP 架构 FE 解析 + BE 执行 向量化执行引擎 分区 & 分桶 分区按时间,分桶按 Hash 桶大小建议 1GB~10GB Rollup & 物化视图 Rollup:预聚合上卷 物化视图:透明改写 Bitmap 精确去重 RoaringBitmap 实现 需转换 ID 为整数 数据导入 Stream Load:实时小批量 Broker Load:离线大批量 总结:Doris 通过 MPP 实现高并发,通过分区/分桶管理数据分布, 通过 Rollup/物化视图加速查询,通过 Bitmap 实现精确去重,通过双导入模式覆盖实时与离线场景。