数据湖加速、实时数仓、统一查询层:Apache Doris 如何成为现代数据架构的“高性能中枢”?

Apache Doris:存算一体MPP分析型数据库的深度技术解析

1. 整体介绍

1.1 概要说明

Apache Doris 是一个由 Apache 软件基金会托管的顶级开源项目。其源代码仓库位于 GitHub: https://github.com/apache/doris。截至分析时点,该项目已获得超过 10,000 个 Star 和 2,900+ 个 Fork,社区活跃度很高,日均提交活跃,反映了其受关注程度和技术迭代速度。

本项目旨在提供一个高性能、易运维的实时分析型数据库。它采用 MPP (Massively Parallel Processing) 架构与 列式存储 ,核心设计目标是:在海量数据背景下,以简化的架构提供低延迟的实时查询与高吞吐的复杂分析能力

1.2 主要功能与技术特性

Apache Doris 的功能特性紧密围绕其设计目标展开:

  • 极简架构:仅包含 Frontend (FE) 和 Backend (BE) 两类进程。FE 负责元数据管理、查询协调与规划;BE 负责数据存储与分布式计算。这种存算一体的设计(尽管可独立扩缩容)极大地简化了部署与运维复杂度。
  • 高性能查询:通过列式存储、向量化执行引擎、预聚合(Aggregate Key 模型)及丰富的索引(前缀索引、Bloom Filter、倒排索引等)实现。其 Pipeline 执行模型能有效利用多核并避免线程膨胀。
  • 高兼容性与易用性:完全兼容 MySQL 网络协议与大部分语法,用户可直接使用 MySQL 客户端、各类 BI 工具(如 Tableau, Superset)进行连接,学习与迁移成本低。
  • 实时与批量数据接入:支持通过 Stream Load(HTTP)、Routine Load(Kafka)、Broker Load(HDFS/S3)以及 Flink/Spark Connector 等多种方式导入数据,满足流批一体需求。
  • 联邦查询 :支持通过 Multi-Catalog 功能直接查询外部数据源,如 Apache Hive, Iceberg, Hudi 以及 MySQL、Elasticsearch 等,无需数据迁移即可进行统一分析。

1.3 面临问题与目标场景

在传统大数据分析领域,常面临以下痛点,而 Doris 正致力于解决它们:

问题要素 传统解决方案的不足 Apache Doris 的对应场景与解决思路
实时性要求高 Hive/Spark 批处理延迟高;Kylin 预计算不灵活。 实时数仓与报表:支持秒级数据摄入与亚秒级查询,适用于实时监控、实时大屏。
架构复杂运维难 Lambda 架构需要维护实时和离线两套系统;存算分离引入网络开销与协调复杂性。 统一分析平台:通过存算一体架构和标准的 MySQL 接口,简化技术栈,降低运维成本。
即席查询响应慢 基于 MapReduce 或 Tez 的引擎启动开销大,无法快速响应无预计算的复杂查询。 交互式即席查询:基于 MPP 和向量化引擎,快速执行多表关联、复杂聚合等查询。
高并发点查询 HBase 等 KV 存储分析能力弱;传统数仓并发能力有限。 高并发数据服务:优化单行检索,支持高并发根据主键或索引的点查,可用于用户画像实时查询。
数据孤岛 数据分散在 HDFS、RDBMS、数据湖中,难以统一分析。 联邦查询与湖仓加速:通过外部表功能,对数据湖、关系库进行统一 SQL 查询,加速分析过程。

对应人群主要为:数据工程师、数据分析师、平台架构师。他们需要在保证数据准确性和系统稳定性的前提下,提升数据分析的时效性、降低系统复杂度和总体拥有成本。

1.4 解决方法演进

以往方式

  1. 混合架构:使用"Hadoop (Hive/Spark) + RDBMS (MySQL/Greenplum) + 流计算引擎 (Flink/Storm)"的混合架构,技术栈复杂,数据一致性、时效性、运维难度挑战大。
  2. 专用系统:使用 ClickHouse 做实时分析,Hive 做离线处理,两者之间数据同步与口径统一困难。
  3. 云数仓:商业解决方案如 Snowflake、Redshift 等,性能好但成本高昂,且存在供应商锁定风险。

Doris 新方式的优点

  1. 架构简化:存算一体(逻辑统一,物理可分离部署),仅两类进程,去除了对 HDFS、ZooKeeper 等外部系统的强依赖,降低了部署和故障排查的复杂度。
  2. 性价比:作为开源软件,在满足高性能实时分析需求的同时,避免了商业数仓的许可费用。其高效的列式压缩能节省存储成本。
  3. 生态友好:MySQL 协议兼容性使其无缝融入现有技术生态;支持多种数据源联邦查询,便于向数据湖演进或整合现有系统。
  4. 开源可控:代码开源,企业可根据自身需求进行定制化开发或深度优化,避免了供应商锁定。

1.5 商业价值预估(逻辑推演)

商业价值可从 成本节约效益提升 两个维度进行估算:

  • 成本侧估算逻辑

    • 软件许可成本:相较于同等处理能力的商业 MPP 数仓(如 Teradata, Greenplum 商业版,或云上按需付费的 Snowflake),采用 Doris 可直接节省每年数百万至数千万元的软件许可或服务费用。
    • 开发运维成本:简化架构减少了需要维护的组件数量,降低了系统集成、数据同步、故障诊断的复杂性,从而节省了相应的人力与时间成本。粗略估计,可减少约30%-50%与底层平台相关的开发和运维投入。
    • 硬件与存储成本:高效的列式压缩(通常可达5-20倍)直接降低了存储硬件开销。向量化执行引擎提升了 CPU 利用率,可能降低同等负载下的计算资源需求。
  • 效益侧估算逻辑

    • 决策时效性提升价值:将 T+1 的报表提速到分钟级甚至秒级,能使业务决策(如营销活动调整、风险监控)更快响应市场变化,其带来的业务增长或风险规避价值难以精确量化但至关重要。
    • 人力分析效能提升:即席查询响应从分钟级降至秒级,显著提升了数据分析师和数据科学家的探索效率,加速了从数据到洞察的进程。
    • 机会成本降低:统一的技术栈避免了多系统间重复开发ETL、数据口径不一致等问题,让团队更专注于业务逻辑而非底层平台差异。

综合估算 :对于一个中型互联网企业,引入 Doris 替换或优化原有混合架构,在 1-2 年 内,通过节省的软硬件采购、云服务费用以及提升的运营效率,其总体拥有成本(TCO)的降低和业务效能的提升,完全有可能覆盖迁移和实施成本,并产生持续的正向收益。

2. 详细功能拆解

从产品与技术融合的视角,Doris 的核心功能可拆解如下:

功能模块 产品视角(用户价值) 技术视角(核心设计)
数据建模与导入 提供多种数据模型(Duplicate/Aggregate/Unique)适应不同场景;提供简单易用的 Web UI 和多种导入方式(HTTP/Kafka/HDFS),降低数据接入门槛。 实现内存物化视图(预聚合)、Delete-and-Insert 的更新语义(Unique 模型)、高效的批量与流式数据导入框架,保证数据最终一致性或事务性。
高性能查询 用户获得亚秒级查询响应,支持高并发;兼容标准 SQL 和主流 BI 工具,分析师无需学习新语法。 MPP 分布式执行框架、全链路向量化计算引擎(基于 Apache Arrow 格式)、CBO/RBO 优化器、Pipeline 异步执行模型、局部性感知的任务调度。
联邦查询 分析师可用一套 SQL 语句同时查询 Doris 本地表与外部 Hive/Iceberg/MySQL 表,打破数据孤岛。 实现 Multi-Catalog 元数据管理层,为不同外部数据源开发对应的 ScannerRuntime Filter 下推能力,优化跨源查询性能。
可观测与运维 提供详细的 Web 监控指标(集群负载、查询统计、导入任务)、简单的扩缩容操作。 在 FE/BE 中埋点采集 Metrics,通过 HTTP API 暴露;实现基于元数据一致性协议(BDB JE)的节点管理、副本均衡与自动修复机制。

3. 技术难点挖掘

作为分布式分析型数据库,Apache Doris 攻克了诸多技术难点,这些难点是其技术深度的体现:

  1. 查询优化器的有效性:如何在海量可能的执行计划中,基于有限的统计信息,为复杂的多表关联、子查询、窗口函数等场景,选出接近最优的分布式执行计划?这是 Doris CBO 面临的核心挑战。
  2. 向量化引擎的全链路实现:不仅要在算子和表达式计算层面实现向量化,还需要在存储层(列式读取)、网络传输层(列式数据传输)乃至 UDF 接口上保持列式内存布局,这对整个系统的架构设计是颠覆性的。
  3. 高并发下的稳定性与性能平衡:如何在高并发点查询(短平快)与复杂大查询(长耗时、占资源)同时存在时,进行有效的资源隔离、排队与调度,避免相互影响?这涉及到精细化的资源组(Resource Group)管理与查询排队策略。
  4. 实时数据更新的效率:在支持 Unique Key 模型实现行级更新的同时,如何避免传统 Merge-on-Read 带来的查询性能抖动?Doris 的"标记删除 + 异步 compaction"机制需要在更新频率、查询性能和存储放大之间取得平衡。
  5. 分布式事务与一致性:在支持 Stream Load 等导入方式时,如何保证跨多个 Tablet(数据分片)的写入操作的原子性和一致性?这依赖于两阶段提交与全局事务管理器的设计。

4. 详细设计图

4.1 核心架构图

图:Apache Doris 核心架构图。FE 负责元数据管理与查询协调,BE 负责分布式存储与计算,通过 MySQL 协议对外提供服务,并支持丰富的外部数据源集成。

4.2 核心查询链路序列图(以简单聚合查询为例)

BE3 BE2 BE1 FE (Coordinator) Client BE3 BE2 BE1 FE (Coordinator) Client Fragment M1 (Scan + Local Agg) Fragment M2 (Shuffle/Data Exchange) Fragment M3 (Merge Agg) SELECT user_id, SUM(amount) FROM orders GROUP BY user_id SQL 解析、验证、CBO/RBO优化 生成分布式查询计划 (Fragment) 执行 Fragment M1 (扫描部分 orders 数据) 执行 Fragment M1 (扫描部分 orders 数据) 执行 Fragment M1 (扫描部分 orders 数据) 本地扫描、预聚合 本地扫描、预聚合 本地扫描、预聚合 发送预聚合结果 (按 user_id 分区) 发送预聚合结果 (按 user_id 分区) 全局聚合 (Merge) 返回最终聚合结果 返回查询结果集

图:一个分布式聚合查询的执行序列。展示了 MPP 架构下,查询如何被拆分为多个 Fragment 在不同的 BE 节点上并行执行,并通过数据交换(Shuffle)完成最终计算。

4.3 核心类图(简化版,聚焦查询执行上下文)

图:简化核心类图,展示了查询执行过程中,从 PlanFragmentFragmentExecutor 再到 PlanFragmentInstance 的创建与管理关系,以及用于向量化执行的上下文。

5. 核心函数解析

以下以向量化聚合算子的关键函数为例进行解析。这是 Doris 高性能查询的基石之一。

场景 :在 SELECT city, SUM(sales) FROM t GROUP BY city 查询中,BE 节点上本地聚合的执行过程。

cpp 复制代码
// 文件:be/src/vec/exec/vaggregation_node.cpp
// 类:VAggregationNode (向量化聚合节点)

// 核心函数:`open` 和 `get_next`
Status VAggregationNode::open(RuntimeState* state) {
    START_AND_SCOPE_SPAN(state->get_tracer(), span, "VAggregationNode::open");
    RETURN_IF_ERROR(ExecNode::open(state));
    // 初始化所有聚合函数:例如,为 SUM(sales) 初始化一个 `SumAggregateFunction` 对象
    RETURN_IF_ERROR(VExpr::open(_aggregate_evaluators, state));
    // 初始化哈希表,用于存储分组键(city)和聚合状态(sum值)的映射
    _hash_table_variants = std::make_unique<AggregatedDataVariants>();
    // 根据分组键的数量和类型,选择最优的哈希表实现(如:针对单数字键的键值对,或针对多列键的PHMap)
    init_agg_hash_variants(*_hash_table_variants, _probe_expr_ctxs);
    // 预分配内存,减少运行时开销
    _agg_arena_pool = std::make_unique<Arena>();
    return Status::OK();
}

Status VAggregationNode::get_next(RuntimeState* state, vectorized::Block* block, bool* eos) {
    INIT_AND_SCOPE_GET_NEXT_SPAN(state->get_tracer(), _get_next_span, "VAggregationNode::get_next");
    SCOPED_TIMER(_exec_timer);
    // 1. 从子节点(通常是 ScanNode)获取一批向量化数据(Block)
    RETURN_IF_ERROR(_children[0]->get_next(state, &_input_block, &_input_eos));
    
    if (!_input_block.rows() > 0 && _input_eos) {
        // 2. 如果所有输入数据已处理完,则进入"输出结果"阶段
        return _output_result(state, block, eos);
    }
    
    // 3. 核心:处理输入 Block,进行聚合计算
    RETURN_IF_ERROR(_execute(state, _input_block));
    // 4. 清空输入块,准备接收下一批数据
    _input_block.clear();
    // 5. 如果哈希表大小超过阈值,可能触发"溢写到磁盘"(外部聚合),此处简化
    // 6. 设置 eos 为 false,表示可能还有数据
    *eos = false;
    return Status::OK();
}

// 关键子函数 `_execute` 的简化逻辑
Status VAggregationNode::_execute(RuntimeState* state, const vectorized::Block& block) {
    // 1. 为当前 Block 中的分组列(city)计算哈希值,得到一个哈希向量
    auto& places = _hash_table_variants->places; // 存储哈希表槽位
    // 2. 批量查找/插入:根据哈希值,在哈希表中为每一行找到或创建对应的聚合数据位置
    //    这是一个向量化操作,比行式逐行处理效率高得多。
    _emplace_into_hash_table(_hash_table_variants.get(), places.data());
    
    // 3. 批量聚合:对找到的每一行,更新其聚合状态(如 sum 值)
    //    for (每个聚合函数 evaluator) {
    //        evaluator->function->add_batch( places, 列数据, 行数 );
    //    }
    //    这里的 add_batch 内部会利用 SIMD 指令进行循环展开,高效累加。
    for (int i = 0; i < _aggregate_evaluators.size(); ++i) {
        RETURN_IF_ERROR(_aggregate_evaluators[i]->execute_batch_add(block, _offsets_of_aggregate_states[i], places.data(), block.rows(), _agg_arena_pool.get()));
    }
    return Status::OK();
}

// 辅助函数:选择哈希表变体,体现性能优化
void init_agg_hash_variants(AggregatedDataVariants& variants, const VExprContextSPtrs& probe_exprs) {
    if (probe_exprs.size() == 1) {
        // 单列分组键
        switch (probe_exprs[0]->root()->result_type()) {
            case TYPE_TINYINT:
                variants.type = AggregatedDataVariants::Type::int8_key; // 使用 int8_t 作为键的哈希表
                break;
            case TYPE_INT:
                variants.type = AggregatedDataVariants::Type::int32_key;
                break;
            case TYPE_VARCHAR:
                variants.type = AggregatedDataVariants::Type::string_key; // 使用 StringRef 作为键的哈希表
                break;
            // ... 其他类型
        }
    } else {
        // 多列分组键,使用组合键的哈希表
        variants.type = AggregatedDataVariants::Type::serialized;
    }
}

代码解析

  1. 向量化批量处理get_next_execute 函数操作的最小单位是 Block(一批列式数据),而非单行。这减少了循环次数和虚函数调用。
  2. 高效的哈希聚合_execute 函数是性能核心。它先批量计算哈希值(_emplace_into_hash_table),然后批量更新聚合状态(execute_batch_add)。这两个步骤都针对批量数据进行了优化。
  3. 多态哈希表init_agg_hash_variants 函数根据分组键的类型和数量,选择内存布局和算法最优的哈希表实现(如 int32_key, string_key)。这种"特化"避免了通用哈希表带来的类型转换和性能开销,是向量化引擎深入优化的典型体现。
  4. 内存管理 :使用 Arena (_agg_arena_pool) 进行聚合状态内存的集中分配与释放,提升内存局部性并降低碎片。
  5. 状态跟踪 :通过 RuntimeStateSCOPED_TIMERRuntimeProfile 等机制,方便进行性能剖析和问题诊断。

通过以上对 Apache Doris 的架构、核心难点、设计图及关键代码的剖析,我们可以看到,它并非简单的组件堆砌,而是一个在 MPP 并行框架、列式存储管理、向量化执行引擎、查询优化器、分布式事务 等多个深水区技术领域都有深入设计和持续优化的系统。其 极简架构极致性能 的目标背后,是大量复杂而精巧的工程技术实现。对于寻求构建高性能、易运维、一体化实时数据分析平台的技术团队而言,深入理解 Apache Doris 的这些技术细节,是进行选型评估、性能调优和二次开发的基础。

相关推荐
hetao173383714 小时前
2026-01-06 hetao1733837 的刷题笔记
c++·笔记·算法
LeenixP14 小时前
RK3576-Debian12删除userdata分区
linux·运维·服务器·数据库·debian·开发板
a努力。14 小时前
国家电网Java面试被问:最小生成树的Kruskal和Prim算法
java·后端·算法·postgresql·面试·linq
知行合一。。。14 小时前
Python--03--函数入门
android·数据库·python
洛生&14 小时前
Counting Towers
算法
X***078814 小时前
理解 MySQL 的索引设计逻辑:从数据结构到实际查询性能的系统分析
数据库·mysql·sqlite
Evand J14 小时前
【MATLAB例程,附代码下载链接】基于累积概率的三维轨迹,概率计算与定位,由轨迹匹配和滤波带来高精度位置,带测试结果演示
开发语言·算法·matlab·csdn·轨迹匹配·候选轨迹·完整代码
爬山算法14 小时前
Hibernate(31)Hibernate的原生SQL查询是什么?
数据库·sql·hibernate
Yuiiii__14 小时前
一次并不简单的 Spring 循环依赖排查
java·开发语言·数据库