资讯动态

Apache Tez深度解析:从MapReduce痛点看DAG执行引擎的提速之道

发布时间:2026/9/7 19:42:00 来源:尧图企业网站定制
在大数据圈子里摸爬滚打久了你一定会听到一个名字Apache Tez。如果你觉得它陌生那换个说法——它就是让Hive从“跑个离线任务要等到天黑”变成“干等半小时终于能看看日志”的幕后功臣。很多新手在接触到Hive on Tez、或者排查Spark任务之外的执行引擎时都会对Tez产生一种既熟悉又模糊的感觉好像它就是MapReduce的改良版但又说不出它到底改了什么、升级在哪、该怎么上手调。这篇文章就是专门写给这些朋友的。我会从MapReduce最让人头疼的几个痛点讲起把Tez的设计思路、核心改动、实际配置、运行细节和排错技巧一次说透。内容不涉及特别深奥的源码但保证你能在看完后清楚理解Tez是什么、为什么比MapReduce快、在真实集群里该怎么用、遇到问题从哪儿下手。无论你是刚接触大数据的学生还是在公司里被Hive任务压得喘不过气的工程师这篇文章都值得你花十分钟读完。1. 先从MapReduce的老毛病说起为什么要升级1.1 堆叠Job时MapReduce的短板暴露无遗很多人最开始接触Hadoop学的就是MapReduce。它的模型确实简单清晰一个业务问题拆成Map阶段和Reduce阶段中间用Shuffle把数据从Map端传给Reduce端。对于单次聚合、排序、分组这类场景MapReduce写起来思路很顺畅跑起来也够用。但现实中哪有什么单阶段任务一个复杂的分析需求往往要拆成好几个步骤先过滤清洗再关联两张表然后做聚合最后还要排序输出。放在MapReduce里每做一步都要写一个单独的MapReduce作业上一个作业的输出落到HDFS下一个作业再从HDFS把数据读出来。作业之间串行排队每个作业都有自己启动、调度、容错的固定开销。结果就是逻辑上一个完整任务被砍成了七八个MapReduce小任务每个任务都有一遍完整的“启动-跑-写盘-读盘-写盘”流程。我印象很深的一次是刚接手一套旧数仓脚本一个每天凌晨跑的报表任务光看脚本逻辑其实只有四步取数、关联、汇总、输出。但底层的Hive执行计划硬生生生成了6个MapReduce Job。全流程跑下来要将近50分钟而且谁都不敢动一动就崩一崩就要从第一个Job重来。这就是MapReduce在复杂DAG场景下的真实处境。1.2 中间结果反复落盘IO开销扼住性能命门MapReduce最被诟病的一点就是中间结果强制写HDFS。每个Map任务结束后输出先写到本地磁盘Reduce拉取数据前再从各个Map节点把数据通过网络拷贝过来Reduce处理完又要把结果写到HDFS供下一个Job读取。一整套流程下来同一个数据集在磁盘和网络之间反复“搬家”。这种感觉就像你坐火车从北京去广州每到一个大站就必须把行李箱从架子上搬下来拖出站台再进站安检重新放上下一趟车的行李架。全程明明可以一趟直达但你被迫在每个枢纽站都重复一遍。数据量小的时候感觉不明显一旦到TB级别每一轮落盘都是实打实的分钟级开销。也正因为这样在上生产环境之后我在排查慢任务时第一反应就是去数这个任务拆成了几个Stage、每个Stage之间是不是都发生了HDFS读写。凡是Stage数量多、中间结果量又大的任务十有八九是慢在这里。1.3 资源与调度层面的“笨拙”除了磁盘IOMapReduce在资源利用上也比较粗放。每个MapReduce Job提交以后都有一个独立的ApplicationMasterAM去申请资源、分配任务、监控进度。任务结束AM也跟着退出释放资源。对于很多批量任务其实还好但如果系统里同时跑大量小型作业启动AM的开销就会堆积起来整个集群的资源利用率也会变得很碎。更麻烦的是容器复用机制在MapReduce年代并不理想。Map任务退出的Container往往直接释放掉留给下一个任务重新申请。每一个Task都要经历一次“申请Container→启动Java进程→加载依赖→拉取数据→执行→退出”的完整生命周期这里面有大量时间其实是在折腾运行时环境而不是真正在算数据。所以在生产环境里MapReduce任务慢很少是单一原因而是磁盘IO、调度开销、资源生命周期管理共同作用的结果。这才有了后续各种执行引擎来挑战它的动力Tez就是其中非常典型的一个。2. Apache Tez到底改了什么从“自动挡卡车”到“直达大巴”的换挡逻辑2.1 核心思想一张DAG到底中间结果不落盘Apache Tez的核心模型就是把之前一个接一个的MapReduce Job合并成一张完整的DAG有向无环图来执行。DAG里的每个节点叫Vertex对应一段计算逻辑节点之间的连线叫Edge对应数据流转方式。你说这跟MapReduce有什么本质区别区别在于作业的“编排粒度”。MapReduce把任务拆成多个独立Job每个Job自己管自己Tez则是把整个任务当作一个整体来计算由单个ApplicationMaster来控制整张图的调度和执行。Job与Job之间的中间结果不需要落到HDFS直接在内存或者本地磁盘上通过管道方式传给下一个Vertex。打个比方MapReduce是要你每次出门都把所有行李搬下楼、装车、到站卸下来、再重新搬到下一辆车上Tez直接调来一辆大巴车你所有行李从出发站放进车厢中间站只做短暂换乘行李在车厢内转移不需要出站。车还是那辆车但省掉了大量重复装卸的环节。2.2 没有Map/Reduce只有Vertex和Edge很多第一次看Tez运行日志的人会懵为什么看不到Map和Reduce了这是因为Tez刻意淡化了MapReduce的二元划分。在Tez里每个Vertex可以执行任意逻辑你可以把一个Vertex理解成“一个计算步骤”它内部具体是Filter、GroupBy、Join还是Sort由更上层的执行计划比如Hive优化后的AST来决定。这一个设计带来的直接好处是执行计划可以更灵活。MapReduce在阶段之间必须遵循严格的数据流方向Map端输出、Shuffle、Reduce端输入而Tez的Edge支持多种数据交付模式比如直接传递、每个分区单独传递、广播传递。Join场景里大表小表关联时就可以用小表广播减少不必要的Shuffle这在MapReduce年代很难一句话做到。我自己刚接触Tez的时候最大的一个认知转变就在这里不要再试图把每个任务用Map和Reduce去对齐而是把注意力放在“这个DAG有几条路径、每条路径的数据是怎么流转的、哪些Vertex可以并行跑”上。2.3 计算模型和调度上的三个核心改进Tez能比MapReduce快核心并不只是“不写HDFS”而是它在调度和资源管理层面动了不少刀子。真正值得记住的改进有三个第一AM常驻。Tez的ApplicationMaster在整个DAG执行期间一直活在同一批ResourceManager分配的容器里不会像多个MR Job那样每跑一个Job就启动一个新AM。AM可以持续接收来自RM的资源也可以复用一个容器执行多个计算任务。第二容器复用。同一个Container可以连续跑多个Vertex也就是说一个Worker进程起来之后可以完成好几个阶段的计算不需要每个阶段都重新申请Container、重新启动JVM。这么做的效果对大量秒级计算任务特别明显——省下的进程启动时间可能就是任务总缩短时间的百分之四五十。第三中间结果内存化。Tez用了一套基于内存的Shuffle机制把Map端或者更准确地说前一个Vertex端的输出尽量放在内存里由后续Vertex直接在内存中拉取。只有内存不够时才会溢出到本地磁盘。数据虽然还是要通过网络传输但省掉了写HDFS这个最重的环节。我把MapReduce和Tez在执行一个多阶段SQL时的关键差异整理成了下面这个表格方便你对照看对比项MapReduceApache Tez执行模式多个独立Job串行一张DAG整体调度阶段间数据写入HDFS再读回内存/本地磁盘直接流转ApplicationMaster每Job一个整个DAG一个容器使用每Task重新申请Task间容器复用Shuffle模型固定Map→Reduce多种Edge模式灵活配适合场景简单单阶段批处理复杂多阶段DAG任务搞懂了这张表你就明白为什么Hive默认引擎后来会切到Tez、为什么跑复杂SQL时Tez经常能比MR快两三倍甚至更多。因为你不是只省了一个环节而是把整个执行框架都在往“少搬运、少空转”的方向优化。3. 真正跑起来Hive on Tez从配置到案例实测3.1 一个完整SQL拆成五个Stage看Tez怎么调度说再多理论都不如实际跑一个SQL看得清楚。我们用一个非常典型的业务查询来举例三张表关联后做多级聚合逻辑上像一个金字塔。SELECT t2.category, t2.gmv, t2.rank FROM ( SELECT t1.category, t1.gmv, ROW_NUMBER() OVER (PARTITION BY t1.category ORDER BY t1.gmv DESC) AS rank FROM ( SELECT c.category_name AS category, sum(o.order_amount) AS gmv FROM orders o JOIN products p ON o.product_id p.product_id JOIN categories c ON p.category_id c.category_id WHERE o.order_date 2024-01-01 GROUP BY c.category_name ) t1 ) t2 WHERE t2.rank 10;如果用MapReduce引擎跑Hive会把它拆成4到5个MR Job。Job1做JOIN和初步聚合Job2做窗口函数的第一阶段排序Job3再做一次聚合和排行Job4负责最终过滤。每个Job结束都落地写一次Job之间串行排队。如果切到Tez你会发现执行计划完全不同整个查询变成了一张5个Vertex的DAG图。其中JOIN相关的两个Vertex可以并行启动聚合和窗口计算通过Edge直接传递不需要产生中间HDFS文件。实际跑下来仅仅是少了那4次中间落盘任务总时间就能缩短一大半。如果你在YARN的ResourceManager页面看任务日志会看到这个作业只有一个DAGDAG下面挂了若干个Vertex而不是一排串行的MR Job。当你打开Tez的UI界面时能看到每个Vertex的耗时进度条某个Vertex卡住或数据倾斜都能一目了然。这一点在定位慢查询时简直救命。3.2 手动切换MR和Tez引擎对比运行效率为了直观感受两种引擎的差距我们可以在Hive会话里手动切换执行引擎跑同一份数据同一个SQL对比一下时间。先切到MapReduceSET hive.execution.enginemr; SET mapreduce.job.reduces4; SELECT ...; -- 执行上面那条SQL一个10GB左右的订单事实表关联3个维表再做分组聚合和窗口排序MapReduce模式下总耗时大约在12分钟左右。期间你用yarn application -list能看到多个Job交替出现一个完了下一个才起来。接着切到TezSET hive.execution.enginetez; SET tez.queue.namedefault; SELECT ...; -- 执行同一条SQL同一个查询Tez模式下总耗时大约4分30秒左右而且能看到YARN上只出现了一个Application。UI里的DAG图和Vertex进度条一目了然什么时候在Shuffle、什么时候在计算都看得很清楚。当然这个差距会因为数据量、集群规格、SQL复杂度而变化不一定始终是3倍。但只要有多个StageTez的优势基本是稳定存在的。我实际的感受是复杂查询提速2到5倍很正常简单查询提速不明显因为Tez的启动开销放在那里。3.3 观察日志与运行计划的关键节点真正用Tez排障时不能光等结果要学会看执行计划和分析日志。Hive里可以用EXPLAIN看到Tez生成的执行计划EXPLAIN SELECT ...; -- 上面那条SQL你会发现计划里不再是Map/Reduce而是Vertex列表每个Vertex会写明是哪种操作比如Map Join、Reduce Join、Group By、Order By等。还会看到每个Vertex之间的Edge信息。作业跑完后去YARN日志里看Application日志重点看这几个位置DAG提交时的日志会显示DAG IDVertex数量边数量。AM日志里的Container申请日志能看出容器是否复用了、总共申请了多少个Container。Vertex 进度日志有时候能直接看到某个Vertex的输入数据量、输出的记录数和字节数从而判断是否存在数据倾斜。我自己最常用的一条命令是yarn logs -applicationId application_xxx -log_files stderr把日志拉下来以后集中搜一下“Vertex”和“DAG”关键词。如果某个Vertex的输入行数远大于其他Vertex比如其他Vertex处理100万行它处理了几个亿那不用猜倾斜点就在这个Vertex。4. 参数怎么调才不白升级tez-site.xml里那些关键键4.1 资源类参数Container大小和AM内存Tez整体跑得快不代表可以完全不管资源配置。在生产环境参数没调好Tez照样会卡、会OOM、会频繁GC。首先是最基础的几个property nametez.am.resource.memory.mb/name value2048/value /property property nametez.task.resource.memory.mb/name value2048/value /property property nametez.ampool.am-node.max.ratio/name value0.8/value /property第一个参数是Tez ApplicationMaster的堆内存大小默认值偏小如果DAG特别复杂、Vertex很多AM就会成为瓶颈。一般建议至少2GB以上任务重的可以给4GB。第二个参数是每个Tez Task Container的内存大小这个要结合你YARN上NodeManager的可用内存来定。通常建议和MapReduce时期的mapreduce.map.memory.mb保持一致避免有些历史任务因为内存习惯性配置而翻车。第三个参数是AM在NodeManager上占用的节点资源比例0.8的意思是最多占用节点80%的资源。默认值一般不用动但如果你一个节点上跑了很多AM可能会出现资源碎片需要警惕。4.2 性能类参数中间结果内存比例和分组阈值Tez的中间结果尽量走内存这个“尽量”是靠参数控制的。几个关键的键位如下property nametez.runtime.io.sort.mb/name value256/value /property property nametez.runtime.unordered.output.buffer.size-mb/name value64/value /property property nametez.grouping.min-size/name value16777216/value /property property nametez.grouping.max-size/name value1073741824/value /property我先解释一下这几个参数是干嘛的。tez.runtime.io.sort.mb控制Shuffle阶段排序缓冲区的内存大小太小会导致大量溢写磁盘太大又会增加GC压力。默认值通常是256MB如果你看到任务在Shuffle阶段大量发生本地磁盘写可以适当提高到384或512前提是Container内存足够。tez.runtime.unordered.output.buffer.size-mb是控制无序输出缓冲区大小也就是中间结果在内存里暂存多少才刷到磁盘。这个参数对没有全局排序需求的任务影响比较大调高一些能减少中间结果落盘。tez.grouping.min-size和max-size则是控制Input分片合并的阈值。Tez会把小的输入分片合并成更大的逻辑分片减少启动任务数。默认min是16MBmax是1GB。如果你发现DAG里生成了大量小Vertex每个只处理几MB数据说明分片合并没起作用可以把min-size调大一点比如32MB减少Vertex数量降低调度开销。这里有个实际操作心得调参不能一次全调。我在线上环境有个原则每轮只改一两个参数跑完看效果再动下一轮。全改完了出了问题你根本不知道是谁的锅。4.3 从MR迁到Tez最容易踩的坑从MapReduce切到Tez时有一个经常被忽略的“坑”UDF的兼容性。有些老的Hive自定义函数在MapReduce模式下能跑换成Tez执行引擎后可能因为序列化方式或者函数执行上下文的问题报错。遇到这类问题先稳住不要急着回退整个引擎可以先确认一下UDF是不是继承了GenericUDF是不是用了Tez不支持的API。另一个坑是MapJoin的自动优化。Hive在Tez引擎下对MapJoin的触发条件判断更激进有时候小表被广播得过多反而导致Task内存压力大。如果遇到Join任务OOM可以检查一下是否因为小表预加载把所有数据塞进了内存这时适当调大Container内存或者关闭自动MapJoin手工指定Join Hint来降级。另外还有一个很隐蔽的问题时区不一致。Tez的Shuffle和聚合过程中时间格式化相关的逻辑会从JVM默认时区取如果AM和Task所在节点时区不一致某些按时间分区的查询结果可能差8小时。我在一次跨机房集群上就踩过这个坑最后是统一了所有节点的时区和JVM的user.timezone参数才解决。下面这个表是我整理的Tez从MR迁移时的关键参数对照建议参数默认值推荐调整使用场景tez.am.resource.memory.mb10242048~4096DAG复杂、AM日志频繁GCtez.task.resource.memory.mb15362048~4096大聚合、大Join任务tez.runtime.io.sort.mb256256~512Shuffle阶段磁盘溢出明显tez.grouping.min-size16MB32MB小分片过多、Vertex数爆炸tez.runtime.unordered.output.buffer.size-mb6496~128中间结果写盘频繁5. 实战中的问题排查与避坑记录5.1 常见报错速查表用了Tez一年多我在实际生产环境里碰到过不少报错。我把最典型的几个整理成速查表如果你遇到类似问题可以直接对照着检查。报错信息原因分析处理建议Container killed on request. Exit code is 143内存超限Container被RM杀掉调大tez.task.resource.memory.mb或降低任务并发度Vertex failed, vertexNameMap_1, diagnosticsnullVertex内部任务全部失败一般伴随OOM或代码异常查具体Task日志优先看stderr和syslogIllegalStateException: Unexpected container state容器被NodeManager提前回收检查节点可用内存确认没有别的任务抢占Too many fetch failuresShuffle阶段拉取数据失败次数过多检查网络、NodeManager状态确认是否有节点宕机TezChild: Uncaught exception: java.lang.OutOfMemoryErrorTask内Java堆溢出或堆外内存不足调大容器内存同时检查tez.runtime.io.sort.mb是否过高DAG killed due to AM restartAM异常退出或被资源抢占调大AM内存检查AM与RM的通信是否正常这张表不能覆盖所有情况但能帮你节省很多排查时间。遇到这类问题第一步永远是去看日志而不是盲目改参数。5.2 一个真实案例数据倾斜导致Tez DAG整体卡死有一段时间我们有个每天跑报表的任务环境是Hive on Tez上游数据量大概每天200GB左右。某天这个任务突然从稳定运行的半小时涨到了两个小时都没跑完而且每天越拖越久。直觉告诉我是数据倾斜。于是我把Application日志拉下来发现Graph中有一个Vertex名字类似Aggregate_3处理的行数高达其他Vertex的30倍。点开这个Vertex的子任务详情发现只有两个Task的输入量特别大其他Task都闲着典型的Key分布不均匀。这种情况下改参数没用因为问题出在业务逻辑和数据分布GROUP BY的字段里有大量空字符串所有空值都集中到了一个Reducer上。后来我是在SQL层面做了处理把空字符串统一改成特殊前缀加随机值然后再做第二轮去空聚合。修改完再跑整个任务回到了25分钟左右。这个案例说明一个道理Tez再快也救不了逻辑上本身倾斜的任务。排障时要把“引擎是否快”和“数据是否均衡”分开看待。5.3 哪些场景其实不适合Tez虽然Tez在多数场景下都比MapReduce强但它也并非万能。我见过很多团队把引擎切到Tez后发现并没有期望中的加速效果甚至在个别场景变慢了。第一种不适合的场景是超简单的点查或小表查询。比如一条SQL只查一张几十万行的维度表过滤条件命中少量数据这种任务无论用MR还是Tez启动和调度开销都占大头Tez的AM启动甚至可能更重未必比MR快多少。这种场景想要极致查询体验应当走OLAP引擎而不是硬改用Tez。第二种是执行计划非常线性的场景比如只有一次MapReduce就能解决的简单聚合。中间没有多阶段传递Tez的DAG优势发挥不出来提速自然有限。第三种是内存极度紧张的小集群。Tez为了追求速度倾向于把更多数据留在内存如果节点内存本身就不够任务会频繁GC甚至OOM效果反而不如MapReduce的“写盘稳”路线。我个人的建议是如果你的集群已经跑着大量复杂Hive SQLTez值得优先试用如果你只是跑点简单的离线统计别急着切。引擎切不切换应该以业务SQL复杂度和资源余量为依据。5.4 从MR切Tez后的运维习惯调整从MapReduce切到Tez之后运维和排查习惯也要跟着变化。首先你不能再像以前那样看Job列表了。以前yarn application -list会看到一长串MR Job切到Tez后一个复杂SQL只会在YARN上显示一个Application但这个Application内部的DAG和Vertex信息才是关键。要习惯进入Tez UI界面去看DAG进度和数据流转图。其次日志定位思路要调整。MapReduce时代找Task日志常用mapred-site.xml配置的路径Tez的日志则统一在YARN的Container日志里。按applicationId去拉日志再按Vertex名字去过滤效率会高很多。最后监控指标要变。以前重点看mapreduce.job.map.count和reduce.count现在要改成看Tez的Vertex个数、Shuffle字节量、Container复用率。只有理解了Tez的运行特征出了问题时才能一眼锁定瓶颈点。6. 写在最后一点个人经验跑了一年多的Hive on Tez我最大的体会就是它其实是个非常“实在”的执行引擎——不吹特效、不搞花活就是把“別再反复写磁盘、别重复建进程”这件事做到极致。对我们这种天天和数据跑批打交道的人来说它的价值不是概念上多酷而是确确实实让凌晨的报表任务从跑不完变成跑得完、从天天超时变成余量充足。如果你准备在生产环境尝试Tez我最后给三条最朴素的建议第一不要一上来就大动干戈切换全局引擎。先在测试环境挑出几个复杂的多阶段SQL用EXPLAIN对比一下MR和Tez的执行计划确认提升效果后再逐步扩大范围。第二任何时候都不要忽略数据本身的问题。Tez能优化执行引擎但优化不了糟糕的过滤条件、不公平的Join顺序和严重的倾斜Key。SQL写得好换引擎才有意义。第三学会看Tez的DAG图和Container日志这是你后期排查所有问题的底气所在。别怕一开始看不明白多看几次你就能从Vertex的颜色和进度条里读出这个作业的健康情况。另外还有个小技巧在Hive里切换引擎时可以加一个Hive变量来控制同一个查询的引擎对比写成脚本批量跑两遍用时间戳自动记录耗时这样对比起来就省事多了。希望这篇通俗指南能帮你把Apache Tez这个“升级包”真正用起来在MapReduce的“高速路”上开得更快、更稳。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价