
1. 为什么你的Hive跑数越来越慢做离线数据开发的朋友应该都有这种体验Hive跑数在数据量小的时候完全没感觉T1报表跑个十几分钟能忍。但一旦事实表过了千万级、亿级JOIN一多、聚合一重跑个全量重算经常要一个小时起步。晚上调度稍微抖一下第二天早上报表就空着。我在接手网约车数据分析项目的时候就撞上过这个问题。订单明细表、轨迹表、司机画像表都在Hive里每天的调度链路从凌晨一点开始跑跑到早上七点都跑不完。上游稍微延迟十分钟下游全堵。最坑的是分析师临时要拉一个多维聚合指标Hive跑一次十几分钟我这边调优的成本比取数的成本还高。后来把Doris引入链路之后情况好了很多。Doris本质是MPP架构的OLAP数据库跟Hive这种批处理引擎是两种完全不同的路子。Hive核心是MapReduce把任务拆成Map和Reduce两个阶段中间结果落盘每一轮都要读写HDFSDoris则是一堆BE节点同时干活数据在内存里做Shuffle跑完不落盘。一个是档案馆里一个人翻档案一个是几十个人同时翻几万本档案再互相核对。这篇东西不聊空泛的概念只讲我自己实际落地时踩过的坑核心围绕两条线展开一条是Hive怎么为Doris做好数据准备另一条是Doris怎么把Hive的数据接过来做加速查询。适合正在做离线数仓提速、报表系统改造、或者想搞清楚Hive外表和MPP查询到底怎么配合的朋友。2. Hive端的准备与优化细节2.1 小文件问题必须当面解决把Doris接进Hive链路之前我建议你先把Hive的小文件问题处理掉。很多人一上来就建外表去读原始表结果Doris查询也没快多少查来查去瓶颈全在读取小文件上。Doris拉取Hive数据时每个文件都对应一个扫描任务1000个小文件就相当于1000个并发扫描BE节点再多也被拖垮。处理小文件我常用的办法有两种一种是重算时用INSERT OVERWRITE配合DISTRIBUTE BY重新分布数据另一种是定期用ALTER TABLE ... CONCATENATE做文件合并。第二种方法在Hive 3.x里支持得比较好不需要重跑MR任务对小文件比较多的分区表特别友好。-- 对指定分区做小文件合并 ALTER TABLE ods_order_detail PARTITION(dt2024-06-01) CONCATENATE; -- 重算时用DISTRIBUTE BY控制Reducer个数避免生成过多小文件 INSERT OVERWRITE TABLE dwd_order_detail PARTITION(dt2024-06-01) SELECT order_id, city_id, driver_id, amount, create_time FROM ods_order_detail WHERE dt2024-06-01 DISTRIBUTE BY city_id;Hive 3.x里有个hive.merge.mapredfiles参数设成true之后Map或Reduce结束时自动合并小文件。实测下来对日增几百万行的明细表合并之后文件数能从几百个降到几十个Doris外表扫描速度能提升一倍以上。2.2 分区裁剪与列裁剪要提前设计外表查询能不能快很大程度上取决于Hive表的物理组织。Doris通过外表查询时虽然也会尝试做分区裁剪但我建议你还是在Hive侧把分区策略设计好。按天分区是最常见的如果分析师经常看周报、月报可以再加一层月份分区或者直接用Doris的物化视图来承接周期聚合别等查询的时候临时去扫全表。列裁剪同样重要我在实际项目里见过有人用SELECT *直连外表拉数据把没用的几十个字段全拉过来IO开销远大于计算开销。建外表时只映射需要的字段查询时只SELECT需要的列这是最基本的外表优化手段。提示Hive表设计时适当做数据压缩也很关键ORC Snappy的组合在大多数场景下兼顾压缩率和查询性能Doris读取ORC外表的效率比读纯文本高一个量级。3. Doris集群部署与MPP核心设计3.1 部署架构怎么选Doris的部署并不复杂核心角色就两个FE和BE。FE负责元数据管理和查询解析BE负责数据存储和计算。小规模集群我建议至少3个BE节点起步FE节点2个或3个都行FE挂掉一个不影响查询服务。关于Doris安装部署官方推荐用Docker Compose或者直接解压二进制包。我自己更推荐二进制包部署因为生产环境用Docker时要额外处理端口映射、数据持久化和内核参数调优麻烦事不少。二进制包部署的核心步骤是下载官方Doris二进制包解压后分发给各节点修改FE的conf/fe.conf配置监听IP和内存修改BE的conf/be.conf配置存储目录和内存上限启动FE后通过MySQL协议连接添加BE节点确认BE状态为ALIVE后即可建表使用BE节点的数量和磁盘性能决定了查询的天花板。MPP引擎的核心理念就是把一个大查询拆成多个小任务分发到各个BE并行执行节点数量越少并发度越受限。我们生产环境里3个BE就能支撑起原来Hive 20个节点集群相当一部分的报表查询压力靠的就是并行度换时间。3.2 数据模型与分桶策略Doris建表时最常踩的坑就是分桶数量拍脑袋。热词里有个问题特别有意思如果Doris只有几MB数据是不是不需要分桶答案是仍然需要分桶虽然物理上数据量很小但分桶数决定了查询的并发度。一个分桶对应一个TabletTablet会均匀分布在BE节点上如果你只有一个Tablet查询时只有一个线程在处理性能提升无从谈起。分桶数量我建议按照集群规模和查询并发来定通常每个BE节点分2-4个Tablet比较合理3个BE节点可以设置6-12个分桶。对于几MB到几十GB的数据量建议分桶数不要少于集群BE数的两倍。桶内数据量在几百MB到1GB之间比较合适太小了查询调度开销大太大了单线程扫描耗时高。注意Doris分桶列的选择建议用高基数列比如订单ID、用户ID避免数据倾斜。如果用城市ID这种低基数列做分桶个别城市数据量巨大会导致某几个Tablet成为查询热点。3.3 Aggregate模型与分区策略Doris最常用的三种数据模型是Duplicate、Aggregate和Unique。做离线报表加速时Aggregate模型非常实用。比如从Hive同步过来的每日订单聚合数据可以按天分区、按城市分桶用Aggregate模型在Doris侧再固化一层汇总。这样分析师查城市维度、时间维度汇总时直接走Doris的物化结果不需要每次去扫Hive的原始明细。我在Doris中建表时一般把日期类型作为分区字段把高基数列作为分桶字段再用DISTRIBUTED BY HASH(city_id)控制数据分布。Doris的分区值可以手动指定也可以用动态分区功能自动创建生产环境强烈建议开启动态分区省去每天手工建分区的烦恼。4. Hive外表打通从联邦查询到性能调优4.1 两种Catalog的区分与选择Doris 1.2版本之后多源数据目录功能成熟了支持直接通过CREATE CATALOG接入Hive Metastore。这一步是整个整合链路的核心。创建Hive外表连接时有几个参数必须理解type指定为hms或者hadoophive.metastore.uris指向Hive Metastore服务地址这是Doris获取Hive表结构信息的来源。我第一次配置时误把hive.metastore.uris指向了HDFS NameNode地址结果外表怎么都建不成功后来排查半天才发现是地址配错了。如果你只有Hive表结构没有HDFS权限控制需求用hms类型的Catalog就够了。如果还需要Doris直接读写HDFS上的Parquet/ORC文件得用hadoop类型并配置dfs.nameservices和NameNode地址。-- 创建HMS Catalog关联已有Hive表 CREATE CATALOG hive_catalog PROPERTIES ( type hms, hive.metastore.uris thrift://hive-metastore:9083 ); -- 在Doris里查询Hive表 SELECT city_id, COUNT(*) AS order_cnt FROM hive_catalog.ods_db.ods_order_detail WHERE dt 2024-06-01 GROUP BY city_id;外表建立完成后Doris里执行查询时系统会自动判断哪些谓词可以下推到Hive的表扫描层。比如WHERE dt 2024-06-01这类分区过滤条件会下推到HDFS的文件扫描层面不会把整个表的数据拉回Doris再过滤。这就是典型的谓词下推能够极大减少跨引擎的数据传输量。4.2 Flink实时写入Hive的常见坑热词里有个“flink sink hive表数据不入表”的问题我在项目里也遇到过。Flink写入Hive的常见问题不是SQL写错而是提交模式配置不对。Flink写入Hive需要开启checkpoint否则算子不会触发文件提交。很多新手不开checkpoint数据一直在缓冲区里看起来任务在跑但Hive表里就是没有数据。-- 在Flink SQL中开启checkpoint和Hive写入模式 SET execution.checkpointing.interval 60s; SET table.exec.hive.fallback-mapred-writer true;另一个常见问题是Flink写入Hive后Hive外表立刻查不到数据这是因为Hive表默认的streaming模式没有开启。需要在建表时设置streamingtrue或者写入后手动MSCK REPAIR TABLE刷新分区。如果Flink持续写入建议在Doris侧直接读Hive的最新分区配合动态分区刷新不要频繁做REPAIR。4.3 关于Presto的“missing”类错误热词里提到“Presto Doris错误missing”这个问题的本质是Catalog或Schema在元数据服务中没有正确注册。无论是Presto连接Doris还是Doris通过Hive外表查数据一旦报missing错误大概率是以下几种情况目标表在元数据服务中不存在或名字拼错Catalog配置里的元数据地址不对或网络不通外表对应Hive库表路径不存在比如分区文件夹没建好列名在两端不一致查询时引用了元数据里不存在的字段排查这类问题最有效的办法是在Doris里先执行SHOW CATALOGS、SHOW TABLES FROM hive_catalog.ods_db确认元数据是否可见再逐级排查。我曾经折腾一晚上“missing”报错最后发现是Hive Metastore服务节点在维护后没有重新注册表重启服务就好了。5. 实操案例网约车分析链路如何跑起来5.1 完整链路设计与数据流转结合一个具体的项目来说会更好理解。网约车大数据分析这个场景里我设计了一条从Hive到Doris的完整数据链路原始订单数据通过Flume或者DataX落入HDFSHive做ODS层的清洗和标准化按天分区保存原始明细Hive DWD层做维度退化、拉宽、清洗输出高质量明细表Hive DWS层做轻度汇总输出城市、时段、司机维度的聚合指标表把DWS层结果通过INSERT INTO ... SELECT从Hive外表同步到Doris的Aggregate表分析师和报表系统直接查Doris报表秒级响应这条链路的好处是离线数仓的成熟体系不变ETL逻辑还是Hive那套但查询分析这块的体验完全变了。原来在Hive里跑一个城市维度月汇总需要三四分钟现在Doris里查已经物化好的Aggregate表毫秒级返回。5.2 Hive外表物化表的混合查询模式同步全量数据到Doris的方式有很多种可以用INSERT INTO直接从外表拉也可以用DataX或者Flink同步工具。但有一个取舍要考虑如果每次同步都是全量重刷数据量大了之后刷一次的时间也会成为瓶颈。我的做法是只把DWS层的汇总结果物化到Doris原始明细留在Hive只有需要下钻到明细时才通过Doris的外表去查Hive。-- 在Doris中建立目标表按城市维度聚合订单指标 CREATE TABLE if NOT EXISTS dws_order_city_agg ( dt DATE, city_id INT, order_cnt BIGINT, total_amount DECIMAL(12, 2) ) DUPLICATE KEY(dt, city_id) PARTITION BY RANGE(dt) () DISTRIBUTED BY HASH(city_id) BUCKETS 6 PROPERTIES (dynamic_partition.enable true, dynamic_partition.time_unit DAY, dynamic_partition.start -30, dynamic_partition.end 1);再写个定时任务每天从Hive外表把昨天的聚合数据同步进来。这样Doris的数据量始终可控查询速度极快同时Hive里的历史明细还留着用于深度分析和回刷。5.3 UDAF与自定义函数在Hive侧的实践在做网约车分析时我还用过Hive自定义UDAF来实现复杂指标计算比如订单取消率、司机在线时长等。UDAF的调试周期比较长而且每次改动都要重新打包上传效率不高。后来我干脆把复杂的指标计算放到了Doris的Aggregate模型中直接用Doris内置的聚合函数搞定Hive侧只做基础清洗和维度关联。我的个人体会是Hive适合做数据清洗和宽表构建Doris适合做指标的快速查询和多维分析。合理的分工比某个引擎单打独斗高效得多没必要在Hive里做所有事。6. 整合后的性能提升与经验沉淀6.1 实测效果与资源消耗对比我把自己负责的网约车订单分析场景做了个对比测试同样是一个城市×月度的订单量、GMV报表查询Hive跑一次任务约4分30秒消耗YARN资源若干遇到队列排队可能要等更久Doris直接查询物化好的表约200毫秒查询时BE节点CPU瞬时高一点返回后就释放Doris通过外表直接查Hive原始数据约15-20秒比Hive快一个数量级但比物化表查询慢很多这个结果很有参考意义如果只做一次性的探索性分析用Doris外表查Hive已经完全够快如果是线上报表和高频查询一定要把数据物化到Doris用空间换时间。另外Doris集群的资源消耗远低于Hive因为MPP架构下查询是常驻BE节点执行的不需要像YARN那样频繁启动Container。我那个3个BE节点的小集群日常跑十几个报表查询CPU负载大概在30%?0%之间磁盘IO也不会成为瓶颈。6.2 从小文件到全链路监控的避坑清单整个过程走下来我给自己总结了一个避坑清单这里直接分享给你Hive侧小文件合并要定期做或者设计时就控制Reducer数量和输出文件大小分区字段类型要统一避免Doris外表读取时类型转换失败表字段注释一定要写清楚Doris外表建完后注释是从Hive同步过来的方便后续维护Doris侧BE节点内存设置不要超过物理内存的80%预留空间给操作系统和PageCache分桶数宁可多一点也不要太少后期改分桶数需要重建表成本很高动态分区要配置好保留期避免分区无限增长导致元数据臃肿链路侧定时同步任务要设置失败重试和告警Flink实时写入Hive时确认checkpoint正常触发外表查询时尽量加时间分区条件避免全表扫描6.3 后续还能怎么扩展这套体系稳定运行之后还可以往几个方向扩展。一是把Kafka里的实时数据同时写入Doris形成离线实时两条路线的统一分析入口。二是用Doris的物化视图自动刷新关键指标减少人为调度的依赖。三是把更多Hive库表接入Catalog逐步把分析师的高频查询迁到Doris上。我也在实际使用中发现Doris对分析师非常友好因为它兼容MySQL协议BI工具、DataGrip、甚至Excel插件都能直接连上去查数。相比让分析师写HiveQL让他们用熟悉的SQL语法在Doris里查数沟通成本和学习成本都低很多。最后再分享一个小技巧如果你准备尝试HiveDoris的整合不要一开始就把所有表全迁过来。选一个业务上最痛、查询频率最高的主题比如订单主链路或者用户活跃分析先把这一条链路跑通摸清楚查询模式和数据量级再逐步扩大范围。这个思路比一次性铺开稳妥得多也更容易让团队看到效果、建立信心。