1. 项目概述从MPP数据库到现代数据仓库的演进如果你在过去几年里关注过大数据领域尤其是数据仓库和实时分析这个赛道那么“Apache Doris”这个名字你一定不会陌生。它最初以“百度 Palo”的名字在内部孵化后来开源并捐赠给了 Apache 软件基金会最终成为了今天的 Apache Doris。简单来说Doris 是一个基于 MPP大规模并行处理架构的、高性能、实时的分析型数据库。但如果你只把它理解为一个“数据库”那就有点小看它了。在我实际的项目经历中Doris 更像是一个“数据服务层”它承接了从 Kafka、Flink 等流处理系统过来的实时数据也对接了 Hive、HDFS 等离线数据湖然后以亚秒级的响应速度支撑着从业务报表、用户行为分析到实时大屏、即席查询等几乎所有对时效性有要求的场景。为什么 Doris 能火起来我觉得核心在于它在一个正确的时间点用一套相对简单的技术栈解决了一个非常普遍的痛点既要实时又要快还要能扛住高并发。在它之前很多团队可能用着 HBase 做点实时查询但复杂的 SQL 支持是个问题或者用着 Presto/Trino 做交互式分析但实时数据导入又得折腾一套链路。Doris 试图把这两件事都做好它内置了高效的列式存储引擎、向量化执行引擎以及一套自己的类 SQL 查询语言让你用 MySQL 协议就能直接连接和操作对业务开发来说几乎没有额外的学习成本。这听起来是不是有点像 ClickHouse没错它们经常被拿来比较但 Doris 在数据模型支持聚合模型和更新模型、物化视图的易用性以及 MySQL 协议的兼容性上有着自己鲜明的特色特别适合从 MySQL/OLTP 体系迁移过来的团队。2. 核心架构与设计哲学解析要真正用好 Doris不能只停留在“怎么建表、怎么写 SQL”的层面必须理解其底层的设计哲学。这决定了你在数据建模、集群规划和问题排查时的思路是否正确。2.1 MPP 架构与数据分布策略Doris 采用经典的 MPP 架构这意味着查询任务会被分解成多个子任务在多个节点上并行执行最后汇总结果。集群中的节点主要分为两类FrontendFE和 BackendBE。FE 负责元数据管理、查询解析与规划、集群协调BE 负责数据存储和计算。这种分离架构的好处是显而易见的扩展性强。当计算资源不足时可以单独扩容 BE当连接数或规划压力大时可以扩容 FE。这里有一个非常关键的设计点数据分布。Doris 的数据表必须指定分桶Bucket和分区Partition。分区通常按时间比如按天划分用于实现数据的生命周期管理TTL和查询时的分区裁剪。分桶则是将分区内的数据进一步打散到不同 BE 上的机制分桶键的选择至关重要它直接决定了数据倾斜和查询效率。注意分桶键的选择是建模的核心。一个常见的误区是直接使用用户ID这类高基数列。如果用户ID分布不均会导致数据严重倾斜某些BE负载过高。更优的做法是选择像“城市用户ID后几位”这样的组合键或者直接使用“随机分桶”。但随机分桶在涉及分桶键的等值或范围查询时无法进行桶裁剪会损失一部分性能。这需要根据查询模式做权衡。2.2 数据模型聚合、唯一与重复Doris 提供了三种数据模型这是它区别于很多同类产品的亮点也直接对应了不同的业务场景。聚合模型Aggregate Model这是为预聚合场景设计的。比如你有一张表字段是用户ID, 日期, 城市, 访问次数, 消费金额。你可以将用户ID, 日期, 城市设为聚合键Unique Key将访问次数和消费金额设为 SUM 聚合类型。当导入相同聚合键的数据时Doris 会自动在后台进行 SUM 操作。这非常适合报表类场景数据在入库时即完成聚合查询速度极快。但缺点是你无法查询到原始的、未聚合的明细数据。唯一模型Unique Model可以理解为聚合模型的一个特例它只为每个唯一键保留最新版本的数据。这本质上实现了主键更新的能力。比如用户属性表以用户ID为唯一键后续导入的新数据会自动覆盖旧数据。这在承接 CDCChange Data Capture数据构建实时用户维度表时非常有用。重复模型Duplicate Model就是普通的明细表没有唯一键约束存储所有导入的原始数据。适合存储日志、行为流水等需要完整追溯的场景。选择哪种模型是 Doris 应用设计的第一个重大决策。我个人的经验是优先考虑聚合模型因为它的性能收益最大。只有当业务明确需要点查最新状态如用户画像时才用唯一模型只有当业务需要全量明细如审计、溯源时才用重复模型。2.3 存储引擎列式、索引与物化视图Doris 的存储引擎是自研的采用列式存储。列存的好处对于分析查询不言而喻压缩率高IO效率高特别适合聚合查询。在此基础上Doris 提供了丰富的索引来加速查询前缀索引基于表指定的排序列默认是前36个字节自动构建。对于等值或范围查询过滤条件包含这些前列的查询效率极高。Bloom Filter 索引适用于高基数列的等值过滤能快速判断数据块中是否包含目标值减少不必要的IO。ZoneMap 索引自动为每列数据块记录 min/max 值对于范围查询过滤非常有效。除了索引物化视图Materialized View是 Doris 另一个“性能大杀器”。你可以基于一张基表创建若干张具有不同维度聚合和排序方式的物化视图。查询时优化器会自动判断是否能够路由到某个物化视图上执行从而避免对原始海量数据的扫描。比如你的基表是按秒的明细可以创建一个按小时、按省份聚合的物化视图。当查询每小时各省的统计时Doris 会自动从这个小得多的物化视图中读取数据查询速度可能有数量级的提升。实操心得物化视图虽好但不宜滥用。每创建一个物化视图就相当于增加了一张物理表会占用存储空间并在数据导入时增加计算开销。我的建议是根据最核心、最耗时的几个查询模式有针对性地创建物化视图。不要试图为所有可能的查询组合都创建视图。3. 从零到一集群部署与数据接入实战理论讲得再多不如动手搭一个。下面我将以一个典型的实时数仓场景为例带你走一遍从集群部署、数据建模到数据接入的完整流程。假设我们的业务是电商需要实时分析用户的点击流和订单数据。3.1 集群规划与部署要点假设我们规划一个用于生产环境的小规模集群3个FE1个Leader2个Follower5个BE。硬件配置FE对CPU和内存要求相对较高因为要处理查询规划和元数据。建议8核16GB内存以上。磁盘不需要很大主要用于存储元数据镜像和日志。BE这是计算和存储的主力。需要大量的CPU、内存和磁盘。建议16核64GB内存起步。磁盘推荐使用SSD因为随机读性能对列存引擎至关重要。网络带宽要足BE之间数据交换频繁。部署步骤精简版环境准备所有节点安装 JDK 8 或 11关闭防火墙或设置好端口规则FE: 8030, 9020, 9030; BE: 9060, 8040, 9050。下载解压从官网下载最新稳定版二进制包解压到/opt/doris之类目录。配置FE修改fe/conf/fe.conf重点设置priority_networks指定用于内部通信的IP段和meta_dir元数据路径。启动第一个FE./bin/start_fe.sh --daemon。通过MySQL客户端连接FEmysql -h FE_HOST -P 9030 -uroot。初始密码为空。执行ALTER SYSTEM ADD FOLLOWER follower1:9010;和ALTER SYSTEM ADD OBSERVER observer1:9010;添加其他FE节点在生产环境OBSERVER只用于扩展读连接不参与选举。配置并启动BE修改be/conf/be.conf同样设置priority_networks和storage_root_path数据存储路径可配置多个用分号隔开。启动BE./bin/start_be.sh --daemon。在FE中添加BE通过MySQL客户端连接FE执行ALTER SYSTEM ADD BACKEND be1:9050;。验证集群状态执行SHOW PROC /frontends;和SHOW PROC /backends;查看节点状态是否为Alive。3.2 数据建模与建表示例根据电商场景我们创建两张表一张用户行为明细表重复模型一张实时订单聚合表聚合模型。-- 1. 创建数据库 CREATE DATABASE IF NOT EXISTS realtime_ec; USE realtime_ec; -- 2. 用户点击流明细表重复模型用于详细行为分析 CREATE TABLE IF NOT EXISTS user_click_log ( user_id BIGINT NOT NULL COMMENT 用户ID, item_id BIGINT NOT NULL COMMENT 商品ID, category_id INT COMMENT 品类ID, action VARCHAR(20) COMMENT 行为类型如click, cart, buy, ts DATETIME NOT NULL COMMENT 行为时间, device VARCHAR(50) COMMENT 设备, province VARCHAR(20) COMMENT 省份 ) DUPLICATE KEY(user_id, item_id, ts) -- 重复模型指定排序列 PARTITION BY RANGE(ts) -- 按时间范围分区 ( PARTITION p202405 VALUES LESS THAN (2024-06-01), PARTITION p202406 VALUES LESS THAN (2024-07-01) ) DISTRIBUTED BY HASH(user_id) BUCKETS 10 -- 按user_id哈希分桶10个桶 PROPERTIES ( replication_num 3, -- 副本数通常与BE数量匹配或更少 storage_medium SSD ); -- 3. 实时订单聚合表聚合模型用于快速出报表 CREATE TABLE IF NOT EXISTS order_agg_daily ( dt DATE NOT NULL COMMENT 日期, province VARCHAR(20) NOT NULL COMMENT 省份, category_id INT NOT NULL COMMENT 品类ID, order_count BIGINT SUM DEFAULT 0 COMMENT 订单数, gmv DECIMAL(20, 2) SUM DEFAULT 0.0 COMMENT 总交易额, user_count BIGINT BITMAP_UNION COMMENT 下单用户数使用Bitmap去重计数 ) AGGREGATE KEY(dt, province, category_id) -- 聚合键 PARTITION BY RANGE(dt) ( PARTITION p202405 VALUES LESS THAN (2024-06-01), PARTITION p202406 VALUES LESS THAN (2024-07-01) ) DISTRIBUTED BY HASH(province, category_id) BUCKETS 8 PROPERTIES ( replication_num 3, storage_medium SSD );建表语句中有几个关键点DISTRIBUTED BY HASH(...) BUCKETS N这是数据分布的核心。桶数BUCKETS建议设置为 BE 节点数量的整数倍以保证数据均匀分布。桶数一旦确定后期修改非常麻烦需要重导数据所以初期规划要谨慎。replication_num副本数用于高可用。通常设置为3但如果你只有3个BE设置为3意味着每个数据块有3个副本会占用3倍存储。在小集群中可以设置为2作为权衡。BITMAP_UNION这是 Doris 提供的高级聚合函数用于精确去重计数比基于COUNT(DISTINCT)的查询性能高得多。3.3 数据导入流式与批量接入数据导入是 Doris 生产链路的关键一环。它支持多种方式这里介绍最常用的两种Routine Load流式和 Broker Load批量。1. Routine Load从 Kafka 实时接入这是实现实时数仓的核心。假设用户行为日志已经发往 Kafka 的user_click_topic。-- 创建例行导入作业 CREATE ROUTINE LOAD realtime_ec.user_click_load ON user_click_log COLUMNS(user_id, item_id, category_id, action, ts, device, province) PROPERTIES ( desired_concurrent_number3, -- 并发任务数 max_batch_interval 10, -- 最大间隔10秒消费一次 max_batch_rows 200000, -- 每批最多20万行 max_batch_size 104857600 -- 每批最大100MB ) FROM KAFKA ( kafka_broker_list kafka1:9092,kafka2:9092, kafka_topic user_click_topic, property.group.id doris_click_group, property.security.protocol SASL_PLAINTEXT, -- 如果有认证还需配置 property.sasl.mechanism, property.sasl.jaas.config 等 format json -- 假设数据是JSON格式 );创建后可以通过SHOW ROUTINE LOAD;和SHOW ROUTINE LOAD TASK;监控导入状态。Routine Load 会自动管理消费位点保证至少一次语义。2. Broker Load从 HDFS/S3 批量导入用于初始化历史数据导入或定时的批量数据补充。需要提前配置好 BrokerDoris 用于访问外部存储的组件。LOAD LABEL realtime_ec.label_order_20240501 -- 导入任务标签需唯一 ( DATA INFILE(hdfs://namenode:8020/path/to/order_data_20240501.parquet) INTO TABLE order_agg_daily FORMAT AS parquet (dt, province, category_id, order_count, gmv, user_id) -- 映射字段user_id用于BITMAP ) WITH BROKER broker_name PROPERTIES ( timeout 3600 );提交后通过SHOW LOAD WHERE LABEL label_order_20240501;查看导入进度和结果。4. 性能调优与运维核心要点Doris 开箱即用性能就不错但要发挥其最大潜力尤其是在数据量巨大、查询复杂的生产环境调优必不可少。4.1 查询优化执行计划解读与调优当遇到慢查询时第一反应应该是查看执行计划。使用EXPLAIN命令EXPLAIN SELECT province, category_id, SUM(gmv) as total_gmv FROM order_agg_daily WHERE dt 2024-05-01 AND dt 2024-05-07 GROUP BY province, category_id ORDER BY total_gmv DESC LIMIT 10;执行计划输出会很长关键看几点分区裁剪PartitionPrune是否有效过滤了分区。如果扫描的分区数远大于实际需要的说明分区键设置或查询条件有问题。桶裁剪BucketPrune是否利用了分桶键进行过滤。这取决于你的查询条件是否包含分桶键的等值条件。聚合节点AGGREGATE是单级聚合还是两级聚合AGGREGATE (merge finalize)。对于大数据集两级聚合先在BE本地聚合再在FE或单个BE上全局聚合效率更高。可以通过设置set parallel_fragment_exec_instance_num 4;来调整并行度促使两级聚合发生。物化视图选择计划中是否显示SCAN MATERIALIZED VIEW。如果没有可以尝试使用EXPLAIN命令查看是否命中了你期望的物化视图有时需要手动 Hint 或优化查询写法。4.2 资源隔离与并发控制在多人使用的分析平台资源争用是常态。Doris 提供了资源标签Resource Tag功能来实现物理资源的隔离。给 BE 打标签在 BE 配置文件be.conf中设置tag.location group_a。创建资源组CREATE RESOURCE GROUP group_high PROPERTIES ( cpu_share100, mem_limit30%, tag.locationgroup_a );将用户或查询绑定到资源组SET PROPERTY FOR analyst_user resource_tags group_high; -- 或者针对单个查询 SET resource_group group_high; SELECT ...这样来自analyst_user的查询只会调度到带有group_a标签的 BE 上执行避免影响其他关键任务。4.3 监控与告警体系搭建生产系统离不开监控。Doris 提供了丰富的 Metrics 接口通过 FE/BE 的/metricsAPI和系统表如information_schema下的BE_TABLETS,FE_PROC等。核心监控项集群健康度FE/BE 节点存活状态、副本健康度tablet_health。资源使用BE 的 CPU、内存、磁盘使用率、网络 IO。FE 的 JVM 内存和元数据数量。查询性能查询延迟query_latency、QPS、失败率。可以按用户、资源组细分。导入性能Routine Load/Broker Load 的导入速率、延迟、错误率。Compaction 状态Doris 后台通过 Compaction 合并数据文件如果积压cumulative_compaction_score,base_compaction_score过高会影响查询和导入性能。建议使用 Prometheus 抓取 Doris 的 Metrics用 Grafana 制作监控大盘并针对关键指标如 BE 宕机、磁盘使用率 85%、查询 P99 延迟 10s设置告警规则接入钉钉、企业微信等。5. 典型问题排查与实战经验最后分享几个我在运维 Doris 集群时踩过的坑和解决方法这些在官方文档里不一定找得到。5.1 问题一数据导入变慢Routine Load 出现堆积现象Kafka 中的数据产生速度正常但 Doris 消费延迟Lag越来越大BE 监控显示写盘 IO 很高。排查思路检查SHOW ROUTINE LOAD;看是否有错误信息。检查 BE 日志是否有大量的WARN或ERROR特别是与compaction相关的。使用SHOW PROC /backends\G查看各个 BE 的LastStreamLoadTime和LastStreamLoadTime判断是否某个 BE 特别慢。查看SHOW TABLET FROM table_name关注VersionCount列。如果这个值持续增长且很大比如超过100说明 Compaction 速度跟不上数据导入速度导致数据版本过多每次查询需要合并大量版本性能下降。解决方案短期适当调低 Routine Load 的max_batch_rows和max_batch_size降低单批数据量给 Compaction 喘息之机。长期增加 BE 节点的磁盘 IOPS换用更好的 SSD。调整 Compaction 参数需谨慎。在 BE 的be.conf中可以适当调高cumulative_compaction_num_threads_per_disk和base_compaction_num_threads_per_disk增加并发线程数。检查数据模型。如果使用了唯一模型或聚合模型且更新/聚合非常频繁会加剧 Compaction 压力。评估是否可以用更粗粒度的聚合或者将实时更新改为微批处理。5.2 问题二查询内存超限Memory Exceeded现象执行复杂聚合或包含COUNT(DISTINCT)的查询时报错Memory exceed limit。原因分析Doris 的查询内存控制是在 BE 端进行的。像GROUP BY、ORDER BY、COUNT(DISTINCT)、窗口函数这样的操作都需要在内存中维护中间状态哈希表、排序区等。如果数据量过大或分组键基数太高就容易爆内存。解决方案优化查询将COUNT(DISTINCT user_id)改为使用聚合表中的BITMAP_UNION预计算列。这是最有效的办法。增加过滤条件减少参与计算的数据量。尝试将大查询拆分成多个小查询。调整参数在会话级别设置更大的内存限制SET exec_mem_limit 8589934592;8GB。但这只是治标且可能影响其他查询。启用 Spill to Disk 功能如果版本支持。对于ORDER BY和AGGREGATE可以设置set enable_spilltrue;和set spill_mode“auto”;让中间结果在内存不足时溢写到磁盘。审视数据模型检查是否可以通过创建更合适的物化视图将计算提前完成从而避免在查询时进行重计算。5.3 问题三FE 元数据写入失败导致选主失败现象FE Follower 节点日志频繁报错无法与 Leader 同步元数据甚至整个集群出现不可用。排查这通常是底层存储FE 的meta_dir所在磁盘出现问题或者 FE JVM 内存不足导致 Full GC 频繁进程僵死。解决与预防确保元数据目录高可用meta_dir最好配置在可靠的共享存储上如基于 SSD 的云盘并做好快照备份或者至少确保有定期的元数据备份策略使用sh backup_meta.sh。监控 FE JVM为 FE 配置合适的堆内存-Xmx和-Xms通过监控观察 GC 情况。如果老年代使用率持续很高需要考虑升级 FE 机器配置。使用 Observer 节点对于读多写少的场景可以多用 Observer FE 来分担查询的请求解析和规划压力避免 Follower 节点过载影响元数据同步。5.4 关于数据备份与恢复虽然 Doris 通过多副本保证了数据的高可用一份数据损坏自动从其他副本恢复但无法防范逻辑错误如误删数据或机房级灾难。因此定期的全量备份是必须的。Doris 提供了BACKUP和RESTORE命令可以将指定数据库或表的数据和元数据备份到远端对象存储如 S3、HDFS。-- 创建备份 BACKUP SNAPSHOT realtime_ec.snapshot_20240520 TO hdfs_repo -- 需提前通过 CREATE REPOSITORY 创建 ON (order_agg_daily, user_click_log) PROPERTIES (type full); -- 查看备份任务 SHOW BACKUP FROM realtime_ec; -- 恢复数据到新表 RESTORE SNAPSHOT realtime_ec.snapshot_20240520 FROM hdfs_repo ON ( order_agg_daily AS order_agg_daily_restored) PROPERTIES ( backup_timestamp2024-05-20-15-30-00, replication_num 3 );备份恢复是最后一道防线建议结合业务重要性制定合理的备份周期如每日全备和保留策略。经过这些年的实践我的体会是Doris 的成功在于它在“性能”、“易用性”和“功能完备性”之间找到了一个很好的平衡点。它不像一些系统那样需要极其专业的调优才能用也不像另一些系统为了易用牺牲了极限性能。它的学习曲线相对平缓但上限很高足以支撑起一个中等规模公司核心的实时数据分析需求。当然它也不是银弹在超大规模PB级以上数据、极端复杂的多表关联场景下可能还是会遇到挑战。但在它所擅长的领域——即席查询、实时报表、用户行为分析——它无疑是一把趁手的好武器。最后一个小建议多关注社区的邮件列表和 GitHub Issues很多你遇到的坑可能已经有人踩过并给出了解决方案。