Hive 集成 Tez 引擎实战:编译部署、配置调优与避坑指南 1. 为什么要在 Hive 上挂 Tez 引擎Hive 这个工具用过的人都知道它把 SQL 翻译成 MapReduce 任务丢到集群上跑胜在稳定、生态成熟但慢也是真的慢。一个稍微复杂点的多表 JOIN中间结果反复落盘磁盘 I/O 直接成为瓶颈跑个十几分钟甚至半小时都是常事。我最早接触 Hive 的时候一个日报统计任务从凌晨两点跑到早上七点还没结束运维那边电话都打爆了。后来把执行引擎从 MapReduce 换成 Tez同样的 SQL同样的数据量时间直接砍到原来的三分之一甚至更少那种感觉就像给一辆老卡车换了个涡轮增压发动机。Tez 的核心价值在于它把 MapReduce 的“多阶段串行”改成了“DAG 有向无环图”执行模型。说人话就是MapReduce 每个阶段结束都要把数据写到 HDFS 上下一个阶段再读回来来回折腾而 Tez 允许把多个操作合并成一个 DAG中间数据尽量在内存或者本地磁盘流转减少了大量不必要的落盘和网络传输。对于 Hive 这种经常需要多轮 Map-Reduce 才能完成的查询收益非常明显。这篇文章适合谁看如果你手上有 Hive 环境跑批任务慢得让人抓狂想上 Tez 又担心踩坑那这篇就是写给你的。我会从版本选型、编译打包、配置修改、参数调优到问题排查把整个流程拆开揉碎讲一遍。需要说明的是下面涉及的具体版本号和路径是基于我实际环境总结的常见实践你的环境可能略有差异但思路和关键点是一致的。2. 动手之前的整体设计与版本选型2.1 先搞清楚 Tez 和 Hive 的版本对应关系Tez 不是随便下个包就能用的它和 Hadoop、Hive 之间有比较严格的版本兼容要求。我见过太多人直接拿最新版 Tez 往老 Hadoop 上怼结果启动就报各种 NoSuchMethodError 或者 ClassNotFoundException。核心原因在于 Tez 依赖 Hadoop 的很多底层 API版本错配就会导致运行时找不到方法。我的建议是Tez 的版本要跟 Hadoop 的版本对齐。比如 Hadoop 3.2.x 系列就选 Tez 0.10.x 里对应 Hadoop 3 的构建版本Hadoop 2.7.x 就选 Tez 0.9.x 对应 Hadoop 2.7 的版本。Hive 这边3.x 版本对 Tez 的支持已经比较成熟2.x 也能用但配置项略有不同。下面这张表是我整理的一个常见组合参考实际选型时以官方 release notes 为准组件推荐版本组合说明Hadoop3.2.1生产环境较稳定的一个版本Hive3.1.2对 Tez 支持完善配置项清晰Tez0.10.1需选 Hadoop 3 对应的构建包JDK1.8编译和运行都用 8别用高版本提示如果你的 Hadoop 是 CDH 或 HDP 这类发行版优先用发行版自带的 Tez 包兼容性最有保障自己编译反而容易出问题。2.2 为什么建议自己编译而不是直接下二进制包Apache 官方其实提供了 Tez 的二进制包但很多时候直接下下来的包跟你的 Hadoop 版本对不上尤其是 Hadoop 3 环境下官方预编译包可能还是基于某个特定小版本构建的。自己编译的好处是能精确指定 Hadoop 的版本和路径生成的 jar 包跟你的环境完全匹配省去后面一堆莫名其妙的类冲突。编译 Tez 需要 Maven 和 protoc。protoc 的版本也有讲究Tez 0.10.x 一般要求 protoc 2.5.0版本太高编译会报错。这个坑我踩过当时用了个 3.x 的 protoc编译到一半直接失败换成 2.5.0 就顺利通过了。2.3 整体部署思路整个部署分四步走第一步编译或者获取匹配的 Tez 包第二步把 Tez 的依赖 jar 上传到 HDFS 供集群共享第三步修改 Hive 配置指定 Tez 为执行引擎第四步调整 Tez 相关参数并验证。这四步里第二步和第三步最容易出问题后面会重点讲。3. 核心细节解析与实操要点3.1 编译 Tez 的关键参数和踩坑点编译命令本身不复杂但参数设置不对就会浪费大量时间。我常用的编译命令大概是这样mvn clean package -DskipTests \ -Dmaven.javadoc.skiptrue \ -Dhadoop.version3.2.1 \ -Dprotobuf.version2.5.0 \ -P hadoop3这里几个参数逐个说清楚。-DskipTests是跳过测试编译 Tez 的测试用例非常耗时生产部署没必要跑。-Dhadoop.version指定你的 Hadoop 版本这个必须准确。-Dprotobuf.version指定 protobuf 版本2.5.0 是 Tez 0.10.x 的标配。-P hadoop3是激活 Hadoop 3 的 profile如果你用 Hadoop 2这个 profile 名要换成对应的。编译过程中最常见的报错是 protoc 找不到或者版本不对。解决办法是提前装好 protoc 2.5.0并且确保protoc --version输出正确。另一个常见问题是 Maven 仓库下载慢导致超时可以配置国内镜像源加速。编译成功后在tez-dist/target/目录下会生成一个tez-0.10.1.tar.gz和对应的tez-0.10.1-minimal.tar.gz。前者是完整包包含所有依赖后者是精简包依赖需要另外提供。生产环境一般用完整包省事。3.2 Tez 包上传 HDFS 的正确姿势Tez 的 jar 包需要上传到 HDFS 上一个所有节点都能访问的路径通常是/tez/或者/user/tez/。这一步的目的是让集群里每个执行任务的节点都能从 HDFS 拉取到 Tez 的依赖而不是每个节点本地都放一份。操作步骤是这样的先把编译好的 tar 包解压然后把里面的lib目录和tez-0.10.1.jar一起上传。tar -zxvf tez-0.10.1.tar.gz hdfs dfs -mkdir -p /tez/tez-0.10.1 hdfs dfs -put tez-0.10.1/lib /tez/tez-0.10.1/ hdfs dfs -put tez-0.10.1/tez-0.10.1.jar /tez/tez-0.10.1/上传完之后务必用hdfs dfs -ls确认文件都在权限也要检查确保 Hive 运行用户有读权限。我遇到过因为权限不对导致任务启动时找不到 jar 包的情况报错信息很隐晦排查了半天才发现是 HDFS 文件权限问题。注意上传路径一旦确定后面 Hive 配置里的tez.lib.uris必须跟这个路径完全一致包括最后的斜杠和文件名差一个字符都不行。3.3 Hive 配置修改的核心项Hive 要切换到 Tez 引擎主要改hive-site.xml。有几个配置项是必须的缺一不可。第一个是执行引擎property namehive.execution.engine/name valuetez/value /property第二个是 Tez 的 jar 包路径property nametez.lib.uris/name value${fs.defaultFS}/tez/tez-0.10.1/tez-0.10.1.jar,${fs.defaultFS}/tez/tez-0.10.1/lib//value /property注意这里tez.lib.uris的写法jar 包和 lib 目录都要列出来中间用逗号分隔。很多人只写了 lib 目录结果启动时报找不到主类。第三个是 Tez 的配置目录如果 Tez 有自定义配置需要指定property nametez.conf.dir/name value/etc/tez/conf/value /property除了这几个必选项还有几个强烈建议加的配置。比如hive.tez.container.size控制每个 Tez 容器的大小默认值可能偏小复杂查询容易 OOM。还有hive.tez.java.opts可以设置 JVM 参数适当调大堆内存。3.4 环境变量和依赖检查配置改完之后别急着启动 Hive先做几项检查。第一确认TEZ_HOME环境变量是否设置虽然 Hive 主要通过tez.lib.uris找包但有些场景下还是会读环境变量。第二确认 Hadoop 的core-site.xml和hdfs-site.xml在 Hive 的 classpath 里能读到否则 Tez 启动时连不上 HDFS。我一般会用一个简单的命令验证 Tez 是否可用hive --service tez --help如果这个命令能正常输出帮助信息说明 Tez 的基本依赖没问题。如果报错根据错误信息定位是缺 jar 还是配置不对。4. 完整实操流程与关键环节实现4.1 从零开始的部署步骤假设你手上已经有一个正常运行的 Hadoop 集群和 Hive 环境下面是从零上 Tez 的完整流程。第一步准备编译环境。装好 JDK 1.8、Maven 3.6、protoc 2.5.0。protoc 的安装建议用源码编译虽然麻烦点但版本最可控。装完后protoc --version应该输出libprotoc 2.5.0。第二步下载 Tez 源码。从 Apache 官方仓库拉取对应版本的源码包解压到本地目录。第三步执行编译。进入源码目录运行前面提到的 Maven 命令。编译时间取决于机器性能一般十几分钟到半小时。编译过程中如果报错重点看是不是 protoc 版本问题或者依赖下载失败。第四步上传 HDFS。把编译产物解压按前面说的路径上传到 HDFS。上传后检查文件列表和权限。第五步修改 Hive 配置。编辑hive-site.xml加入执行引擎和 Tez 相关配置。如果 Hive 是集群模式确保所有节点的配置文件都同步更新。第六步重启 Hive 服务。如果是 HiveServer2重启服务让配置生效。如果是命令行客户端重新打开一个会话即可。第七步验证。启动 Hive 客户端执行一个简单的查询观察执行日志里是否出现 Tez 相关的信息。如果看到Tez session或者DAG字样说明 Tez 已经生效。4.2 参数调优的实操记录Tez 默认参数能跑但要想发挥最佳性能几个关键参数需要根据集群规模调整。下面是我在一个中等规模集群约 20 个节点每节点 64G 内存上的调优记录。容器大小方面hive.tez.container.size我设成了 4096MB也就是 4G。这个值不能超过 YARN 单容器最大内存限制否则容器申请不到资源。同时hive.tez.java.opts里设置-Xmx为容器大小的 80% 左右留一些给 JVM 自身开销。property namehive.tez.container.size/name value4096/value /property property namehive.tez.java.opts/name value-Xmx3276m -XX:UseParallelGC/value /property并行度方面tez.am.resource.memory.mb控制 ApplicationMaster 的内存一般设 2048MB 够用。hive.tez.auto.reducer.parallelism设为 true让 Tez 自动决定 reducer 数量比手动指定更省心。还有一个容易被忽略的参数是tez.grouping.max-size和tez.grouping.min-size它们控制输入分片的大小。默认值在数据量大且文件多的情况下可能导致分片过多任务启动开销大。我一般把 max-size 调到 256MB 左右min-size 保持默认。调优不是一蹴而就的建议每次只改一两个参数跑同样的查询对比耗时找到适合自己集群的配置。4.3 验证 Tez 是否真正生效怎么确认 Hive 真的在用 Tez 而不是 MapReduce最直接的方法是看执行日志。在 Hive 客户端执行查询时日志里会打印执行引擎信息。如果看到类似Executing on Tez或者Tez session opened的内容就说明生效了。另一个方法是看 YARN 的 ResourceManager 页面。Tez 任务在 YARN 上显示的应用类型是TEZ而 MapReduce 显示的是MAPREDUCE。如果应用类型是 TEZ那就没跑了。还可以通过 Hive 的explain命令查看执行计划。Tez 的执行计划会显示为 DAG 结构而 MapReduce 会显示成多个 Stage 串行。这个对比非常直观。5. 常见问题与排查技巧实录5.1 启动报错类问题速查下面这张表整理了我遇到过的典型报错和解决办法基本覆盖了 90% 的启动问题报错信息关键词可能原因解决办法ClassNotFoundException: Teztez.lib.uris 配置错误检查 HDFS 路径和文件名是否完全一致NoSuchMethodErrorTez 与 Hadoop 版本不匹配重新编译匹配版本的 TezContainer killed on request容器内存超限调大 hive.tez.container.sizeCould not find or load main classjar 包未正确上传确认 HDFS 上 jar 文件存在且完整Session timed outTez session 超时调大 tez.session.am.dag.submit.timeout.secs5.2 性能不升反降的情况Tez 不是万能药有些场景下用了反而更慢。我遇到过两种情况。一种是超小数据量的查询比如几 MB 的数据Tez 启动 session 的开销比 MapReduce 还大这时候用 MapReduce 反而快。另一种是数据严重倾斜的 JOINTez 的自动并行度调整可能不够智能导致某个 reducer 压力过大。针对小数据量场景可以设置hive.execution.engine在会话级别动态切换小查询用 MapReduce大查询用 Tez。针对数据倾斜可以开启hive.optimize.skewjoin或者手动调整 reducer 的 key 分布。5.3 内存溢出与 GC 问题Tez 任务跑着跑着报 OOM是最常见的问题之一。排查思路是这样的先看是 ApplicationMaster 的 OOM 还是 Task 容器的 OOM。AM 的 OOM 一般是tez.am.resource.memory.mb设小了调大即可。Task 容器的 OOM 则要调hive.tez.container.size和hive.tez.java.opts里的-Xmx。还有一个隐蔽的坑是 GC 问题。如果 JVM 堆设得太大但 GC 策略不合适会导致长时间的 Full GC任务卡住不动。我一般会在hive.tez.java.opts里加上-XX:UseParallelGC或者-XX:UseG1GC具体用哪个看 JDK 版本和集群特点。JDK 8 下 G1 表现通常更好但也不是绝对的需要实测。提示调大内存不是万能的有时候 OOM 的根因是数据倾斜或者代码逻辑问题光加内存治标不治本。遇到反复 OOM先分析数据分布和查询逻辑。5.4 独家避坑经验说几个文档里不会写但实际很要命的点。第一Tez 的 session 是有生命周期的长时间不用会自动关闭下次查询需要重新建立 session这个建立过程可能耗时几十秒。如果你们的查询是间歇性的建议调大 session 超时时间避免频繁重建。第二HDFS 上的 Tez jar 包如果被误删或者路径变更所有依赖 Tez 的任务都会失败而且报错信息不一定直接指向 jar 包缺失。建议把 Tez 的 HDFS 路径加入监控定期检查文件是否存在。第三多租户环境下不同用户可能用不同版本的 Tez这时候tez.lib.uris的隔离就很重要。可以通过 Hive 的 session 级配置让不同用户指定不同的 Tez 路径避免版本冲突。第四升级 Hadoop 或 Hive 版本后原来的 Tez 包可能不兼容了需要重新编译。这个升级窗口期要提前规划别等到生产任务失败了才手忙脚乱。6. 一些关于参数和场景的补充思考6.1 不同查询类型的参数侧重不是所有查询都适合同一套参数。对于 ETL 类的批量写入任务重点在吞吐量可以适当调大容器内存和并行度让任务尽快跑完。对于交互式的即席查询重点在响应速度Tez session 的预热和复用就很关键可以配置 session 常驻减少每次查询的启动开销。对于多表 JOIN 特别多的复杂查询tez.grouping相关参数和hive.auto.convert.join的配合很重要。小表广播、大表分片这些策略在 Tez 下依然适用但具体阈值需要根据数据特征调整。6.2 集群规模与参数的关系小集群10 节点以下和大集群50 节点以上的参数策略完全不同。小集群资源有限容器大小和并行度都要保守一些避免资源争抢导致任务互相拖累。大集群可以更激进地提高并行度充分利用资源。我的一般原则是容器大小不超过单节点可用内存的 1/4并行度不超过总可用容器数的 80%。留一些余量给系统和其他任务避免把集群压垮。6.3 监控与持续优化Tez 任务上线后不是就完事了需要持续监控。重点看几个指标任务平均耗时、失败率、资源利用率、GC 时间占比。这些指标可以通过 YARN 的监控页面和 Tez 自身的 UI 获取。Tez UI 是个好东西能看到每个 DAG 的详细执行情况包括每个 vertex 的耗时、数据量、并行度。通过分析这些数据能发现很多优化点。比如某个 vertex 耗时特别长可能是数据倾斜某个 vertex 并行度很低可能是分片策略不合理。优化是个持续的过程没有一劳永逸的配置。随着数据量增长和查询模式变化原来的参数可能就不再适用了。建议每隔一段时间回顾一下监控数据做针对性的调整。6.4 关于是否上 Tez 的个人判断最后说点实在的。Tez 确实能带来明显的性能提升但它也增加了系统的复杂度。多了一个组件要维护多了一层配置要管理出问题时排查链路也更长。如果你的 Hive 任务本身就不多或者对延迟不敏感那上 Tez 的收益可能有限。但如果你的集群每天跑着几十上百个批处理任务每个任务动辄几十分钟那 Tez 带来的时间节省是非常可观的。我自己的经验是一个日均跑批 8 小时的集群切到 Tez 后缩短到 3 小时左右省下来的时间可以用来跑更多任务或者更早出报表这个投入产出比是值得的。决定上 Tez 之前建议先在一个小范围做试点选几个典型的慢查询对比测试拿到实际数据后再决定是否全面推广。别一上来就全集群切换万一出问题影响面太大。试点跑稳了再逐步扩大范围这样风险可控。