ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Hadoop 3.3.1 与 Hive 3.1.2 下重新编译 Tez 0.10.1 Minimal 解决兼容性

Hadoop 3.3.1 与 Hive 3.1.2 下重新编译 Tez 0.10.1 Minimal 解决兼容性 这个问题我印象挺深。当时我负责维护一套 Hadoop 3.3.1 Hive 3.1.2 的离线数仓Hive 自带的执行引擎还是默认的 Tez 0.9.x。结果一跑大查询YARN 上要么卡在 INITIALIZING要么 ApplicationMaster 反复重试日志一翻全是 NoSuchMethodError。查下来才知道是 Tez 0.9.x 和 Hadoop 3.3.1 的 API 不兼容。后面自己动手把 Tez 0.10.1 minimal 包重新编译了一遍替换掉 Hive 自带的旧 Tez才彻底解决。这中间踩了不少坑今天把完整过程和经验写出来给正在搭 Hadoop 3.3.1 Hive 3.1.2 环境的朋友做个参考。1. 项目背景为什么 Hive 3.1.2 要配 Tez 0.10.1 而不是默认的 Tez 0.9.x1.1 Hive 3.1.2 自带的 Tez 版本与 Hadoop 3.x 存在代沟Hive 3.1.2 这个版本在发布时对应编译好的 Tez 依赖是 0.9.2。Tez 0.9.2 的年代比较早那会儿 Hadoop 生态还集中在 2.x很多内部 API、RPC 协议、YARN ApplicationMaster 的接口都是按照 Hadoop 2.x 的规矩来的。等 Hadoop 3.3.1 出来之后YARN 和 HDFS 这边改动不小最典型的就是FileSystem接口的存储策略变化、YARN 的 Application ClassLoader 行为调整以及一些 Protobuf 相关的依赖包路径变化。Tez 0.9.2 编译时锁死的老接口在 Hadoop 3.3.1 的运行时环境里经常找不到对应方法结果就是作业提交流程各种报错。Hive 3.1.2 的 lib 目录里其实带着一套 Tez 的 jar 包但那套 jar 是 Hive 官方在编译期绑定的 Tez 0.9.2。有些人可能觉得“既然 Hive 自带了 Tez直接拿来用不就行了”但如果你把它放到 Hadoop 3.3.1 上面跑很快就会发现它跟新的 YARN 环境合不来。最典型的现象我已经在开头说了DAG 提交到 YARN 后一直 PENDING或者 AppMaster 起来之后立刻异常退出日志里全是NoSuchMethodError: org.apache.hadoop.yarn.api.records.ContainerLaunchContext之类的错误。这些报错指向的其实就是 Tez 0.9.x 编译时用的 YARN 接口在 Hadoop 3.3.1 里已经被替换掉了所以光靠 Hive 自带的那套老 Tez 根本撑不住。1.2 Tez 0.10.1 是适配 Hadoop 3.x 的关键版本Tez 0.10.x 是 Apache Tez 专门为 Hadoop 3.x 适配而推出的版本线。0.10.0 解决了大部分 Hadoop 3.x 的接口兼容问题0.10.1 则在 0.10.0 基础上修了一批 Bug包括一些在 HDFS 上读取资源时的异常、DAG Scheduling 时的边界条件等。所以对于 Hadoop 3.3.1 来说选用 Tez 0.10.1 是比 0.9.2 稳妥得多、也正规得多的选择。不过 Tez 0.10.1 的官方发布包默认编译目标并不一定就是 Hadoop 3.3.1。如果你直接下载一个不带任何定制信息的 Tez 0.10.1 minimal 包然后把它丢到 Hadoop 3.3.1 环境里大概率还会遇到小版本之间的兼容问题。原因在于 Tez 0.10.1 源码的 pom.xml 里默认声明的hadoop.version可能低于 3.3.1比如 3.2.0 或者 3.0.0 这种。这种小版本差异虽然不如 2.x 到 3.x 的差距那么大但为了确保运行时不出现奇怪的ClassNotFoundException最好还是用源码自己重新编译把hadoop.version指到 3.3.1。1.3 Minimal 包和 Full 包的区别为什么我选 MinimalTez 官方在源码构建后会在tez-dist/target目录下生成两种发布包一种是tez-0.10.1-full.tar.gz另一种是tez-0.10.1-minimal.tar.gz。Full 包里包含 Tez 运行所需的全部第三方依赖比如 Jetty、Slf4j、Guava 等等好处是部署环境可以跟 Hadoop 离线隔离缺点是很庞大而且容易跟 Hadoop/Hive 自带的相同依赖版本冲突。Minimal 包则只包含 Tez 自己编译出来的核心 jar 和少量必要资源运行时不携带那些 Hadoop 客户端里本来就有的大路货依赖因此更轻量冲突也更少。我在生产环境里做方案时选择了 Minimal 包。一方面集群的各个节点上本来就有完整的 Hadoop 3.3.1 客户端依赖Tez 运行期间需要的 Hadoop 相关类可以直接从节点上拿另一方面 Hive 3.1.2 的 lib 里也有不少 Hive 自身的依赖如果再用 Full 包把 Guava、Protobuf 等第三方库强行带到 JVM 里很容易把 Hive 的 classpath 搅乱。Minimal 包干净、体积小上传 HDFS 之后分发速度也快所以除非你真的要把 Tez 跑在完全没有 Hadoop 客户端的环境里否则我建议一律选 Minimal。2. 打包前的环境准备与版本核对2.1 编译环境清单JDK 8、Maven 3.6、GitTez 0.10.1 这个版本对编译环境的要求不算苛刻但有几个硬性条件需要先满足。第一是 JDK 必须用 8 或以上我当时用的是 JDK 8u202没有继续往上升因为 Tez 的 pom.xml 里对 JDK 9、JDK 11 的支持还不完善某些模块可能会有编译告警但 JDK 8 编译出来的 class 文件放到 JDK 8 运行环境里最保险。Hadoop 3.3.1 本身支持 JDK 8所以这套组合在编译和运行层面都能对得上。第二是 Maven 版本。Tez 0.10.1 的构建脚本里有很多插件版本绑定过老的 Maven 可能因为插件语法问题直接失败太新的 Maven 也可能因为maven-shade-plugin或maven-compiler-plugin的兼容性出现问题。我本地用的是 Maven 3.6.3这个版本在 Tez 官方 CI 里是常见选择实测构建全程没有报过插件兼容错误。第三是 Git这个没什么好说的直接从 Apache Tez 官方仓库拉源码。需要注意的是Tez 0.10.1 的版本 tag 是rel/release-0.10.1所以拉完代码后要显式 checkout 这个 tag不要拉 master 分支否则你编译出来的版本号和预期对不上。2.2 确认 Hadoop 3.3.1 与 Tez 0.10.1 的兼容面在真正编译之前我建议你先做一次版本核对避免后续反复返工。Tez 0.10.1 源码里有个pom.xml其中hadoop.version这个属性定义了 Tez 编译时使用的 Hadoop API 版本。你可以先用下面的命令看一下当前源码默认的 Hadoop 版本grep -n hadoop.version pom.xml | head -20Tez 0.10.1 的默认值可能是3.2.0或3.0.0具体要看发布时锁定的版本。如果默认值已经等于 3.3.1那你可以省掉修改属性这一步如果低于 3.3.1编译时就必须用-Dhadoop.version3.3.1来覆盖或者直接编辑 pom.xml 里的属性值。我实际编译时选择在命令行里通过-D传参覆盖这样不需要动源码文件后续如果想重新编译其他版本也更方便。还有一个容易被忽略的地方是 Protobuf。Hadoop 3.3.1 使用的是hadoop-shaded-protobuf而 Tez 0.10.1 自身对 Protobuf 的依赖管理已经处理得比较成熟所以正常情况下你只要把hadoop.version指到 3.3.1Maven 会自动解析对应的 Hadoop 客户端 jar把 Proto 相关的包也带过来。这里不需要额外手工指定 Protobuf 版本反而改了容易出问题。2.3 关于 Maven 仓库镜像和依赖下载速度Tez 0.10.1 编译要拉非常多的依赖包括 Hadoop 各模块、Avro、Jetty、Netty 等如果直接用 Maven 中央仓库在国内网络环境下会非常煎熬。我的做法是在~/.m2/settings.xml里配置一个国内可用的镜像源。这里有一个经验不要只配一个镜像可以多配几个让 Maven 在下载失败时自动切换。比如你可以配置 AliYun 的中央仓库镜像但注意 Aliyun 镜像有时对某些刚发布的依赖同步不及时这时候最好再留一个默认的中央仓库作为 fallback。编译过程中如果频繁出现Cannot resolve dependency你可以先检查~/.m2/repository下对应目录是否只有.lastUpdated文件如果是说明拉包失败过。处理方法就是把对应目录删掉重新编译通常第二次就能成功。3. Tez 0.10.1 minimal 打包实操3.1 下载 Tez 0.10.1 源码并切换到 release tag先从 Apache Tez 官方仓库拉取源码这里我建议直接 clone而不是下载 zip 包因为 clone 之后切换 tag 和查看提交记录都比较方便。操作如下git clone https://github.com/apache/tez.git cd tez git checkout rel/release-0.10.1切换 tag 之后确认当前版本git describe --tags如果输出rel/release-0.10.1说明切换成功。这个步骤一定要做因为 Tez 的master分支长期处于开发状态编译出来的版本号可能带 SNAPSHOT跟 Hadoop 3.3.1 的组合没有经过足够验证。3.2 通过 Maven 覆盖 Hadoop 版本并执行构建进入 Tez 源码根目录后直接执行 Maven 构建命令。这里有几个关键参数需要注意mvn clean package -DskipTests -Dhadoop.version3.3.1 -Dmaven.javadoc.skiptrue-DskipTests用于跳过单元测试Tez 的测试模块有相当一部分需要启动 MiniYARNCluster既耗时又容易因为本地环境问题挂掉编译阶段没必要跑。-Dhadoop.version3.3.1是覆盖 Hadoop 依赖版本的核心参数。-Dmaven.javadoc.skiptrue跳过 javadoc 生成否则构建末尾会卡在 javadoc 插件上。如果你不想每次都在命令行里敲-Dhadoop.version3.3.1也可以直接修改pom.xml里的hadoop.version3.3.1/hadoop.version属性二选一即可。我自己的习惯是用命令行传参因为这样源码保持原样后续如果 Hadoop 小版本升级到 3.3.2只要换一个参数重新打包就行。构建过程耗时较长视网络状况在 10 到 30 分钟之间。等 Maven 命令跑完看到BUILD SUCCESS之后进入tez-dist/target目录检查产物ls -lh tez-dist/target/正常情况下你会看到两个发布包分别是tez-0.10.1-minimal.tar.gz和tez-0.10.1-full.tar.gz。我们需要的是 minimal 包。3.3 验证 minimal 包内容确认编译目标版本拿到tez-0.10.1-minimal.tar.gz之后先别急着部署先解压检查一下里面的内容重点看两个地方。第一是 lib 目录下 Tez 核心 jar 是否存在通常包括tez-api-0.10.1.jar、tez-runtime-library-0.10.1.jar、tez-dag-0.10.1.jar等。第二是确认这些 jar 的 Manifest 或 pom 属性中记录的 Hadoop 版本确实是 3.3.1。你可以在解压后执行tar -tzf tez-0.10.1-minimal.tar.gz mkdir -p /tmp/tez-minimal tar -xzf tez-0.10.1-minimal.tar.gz -C /tmp/tez-minimal find /tmp/tez-minimal -name *.jar | head -20再用jar tf或unzip -p查看某个核心 jar 的 pom 属性unzip -p /tmp/tez-minimal/lib/tez-api-0.10.1.jar META-INF/maven/org.apache.tez/tez-api/pom.properties如果输出里能看到hadoop.version3.3.1相关的编译信息说明这次编译确实把 Hadoop 版本对齐了。这一步虽然看起来多余但能避免“明明打了包上线却还是报老接口错误”的尴尬情况。4. 部署到 Hadoop 3.3.1 集群并与 Hive 3.1.2 集成4.1 上传 Tez minimal 包到 HDFS 并初始化目录Tez 运行时要由 YARN 的 ApplicationMaster 动态下载对应的 jar 到节点本地所以 tez 包必须放到 HDFS 上并且要保证所有 NodeManager 节点都有权限读取。我这边统一放在/apps/tez目录下操作如下hdfs dfs -mkdir -p /apps/tez hdfs dfs -put /tmp/tez-minimal/tez-0.10.1-minimal.tar.gz /apps/tez/ hdfs dfs -chmod -R 755 /apps/tez这里有一个细节你上传的如果是.tar.gz压缩包那么在tez-site.xml里配置tez.lib.uris时可以直接指向这个压缩包路径你上传的如果是解压后的整个目录那么tez.lib.uris需要写目录路径且以/结尾而且 Tez 会尝试读取目录下的具体 jar。我在实践中发现直接上传.tar.gz是最省事的方式Tez 会自动把它分发到各个节点并解压不容易出现 jar 路径对不上的问题。记得给/apps/tez目录设置可读权限因为运行 Tez 作业的 YARN 用户不一定是上传文件的用户如果权限不足AppMaster 在拉取资源时会直接报Permission denied而且这种报错在 Hive 日志里往往看不到明细只能去 YARN 的 ResourceManager 日志里翻。4.2 配置本地 tez-site.xml 文件Tez 的配置可以放到 Hive 的 classpath 中也可以单独放在一个本地目录然后用TEZ_CONF_DIR指向它。我采用的是单独目录方式这样 Hive 的配置和 Tez 的配置互不干扰后续 Tez 调参也方便。在本地比如/opt/module/tez-conf下创建tez-site.xml内容参考如下?xml version1.0 encodingUTF-8? configuration property nametez.lib.uris/name valuehdfs:///apps/tez/tez-0.10.1-minimal.tar.gz/value /property property nametez.am.resource.memory.mb/name value2048/value /property property nametez.am.resource.cpu.vcores/name value2/value /property property nametez.task.resource.memory.mb/name value1024/value /property property nametez.task.resource.cpu.vcores/name value1/value /property /configurationtez.lib.uris是最核心的配置指向第 4.1 步上传到 HDFS 的压缩包。下面那四个资源参数是 Tez 在 YARN 上运行时分配给 ApplicationMaster 和 Task 的资源你可以根据集群物理内存适当调大调小但刚开始建议保守一点确保作业能正常跑起来再说。4.3 配置 hive-env.sh 使 Hive 找到 Tez接下来要改 Hive 的启动脚本。在$HIVE_HOME/conf/hive-env.sh中添加以下内容export TEZ_HOME/opt/module/tez export TEZ_CONF_DIR/opt/module/tez-conf export TEZ_JARS/opt/module/tez/lib/*:/opt/module/tez/*.jar export HADOOP_CLASSPATH$HADOOP_CLASSPATH:$TEZ_CONF_DIR:$TEZ_JARS这里的TEZ_HOME是 Tez 包的本地解压路径也就是你在第 3.3 步解压/tmp/tez-minimal对应的目录建议把它放到/opt/module/tez这种标准路径下方便各节点统一。TEZ_CONF_DIR指向刚才创建的tez-site.xml所在目录。TEZ_JARS会把 Tez 本地的所有 jar 加到 Hadoop classpath这样 Hive 客户端启动 TezSession 时就能在本地找到需要的 Tez 类。需要注意HADOOP_CLASSPATH的追加顺序有讲究。如果你发现 Hive 启动时加载的还是旧版 Tez 类可以把$TEZ_CONF_DIR和$TEZ_JARS放到HADOOP_CLASSPATH的最前面确保它优先于 Hive 自带的 Tez jar。4.4 修改 hive-site.xml 切换执行引擎并验证Hive 3.1.2 默认的执行引擎是 MapReduce要切换到 Tez需要在hive-site.xml中设置property namehive.execution.engine/name valuetez/value /property这个参数也可以在 Hive CLI 里用set hive.execution.enginetez;临时生效但如果你希望每次启动都默认走 Tez建议写进配置文件。除此之外我建议顺手设置以下两个参数避免启动阶段额外报错property namehive.tez.container.size/name value1024/value /property property namehive.server2.tez.sessions.per.default.queue/name value1/value /property改完配置后进入 Hive CLI先确认执行引擎set hive.execution.engine;如果输出是tez说明配置生效。接着跑一个简单的 SQLselect count(*) from target_table;正常的流程是Hive 客户端收到 SQL 后生成 Tez DAG提交给 YARNYARN 上会出现一个名为TezSession的 Application。你可以去 YARN ResourceManager 的 Web UI 里看这个作业是否在 RUNNING或者直接看日志里有没有Executing on Tez这类输出。如果这些都对得上那么 Tez 0.10.1 minimal 就成功接管了 Hive 的执行引擎。5. 常见报错与实测避坑指南5.1 高频问题速查表下面这个表格是我在部署和后续使用中碰到频率比较高的几个问题以及对应的排查方向。不一定每个集群都会遇到但提前有个心理准备能省不少事。现象可能原因排查与解决作业一直 PENDING 不跑YARN 资源不足或 Tez AM 资源请求过大检查集群可用资源调小tez.am.resource.memory.mbAppMaster 启动后立刻退出Tez 与 Hadoop 版本接口不匹配确认编译时-Dhadoop.version3.3.1并检查 classpath 混乱NoClassDefFoundError: org/apache/tezu/dag/api/...Hive 客户端 classpath 里没有 Tez jar检查TEZ_JARS路径和HADOOP_CLASSPATH是否正确导出Missing application resourcetez.lib.uris配置错误或 HDFS 路径不可读确认tez-site.xml中路径存在且权限可读java.lang.OutOfMemoryError: Java heap space容器或 AM 堆内存不足调大hive.tez.container.size和tez.am.resource.memory.mb并同步调优hive.tez.java.opts这些是我在实际运维环境里面对过的问题每个人的集群规模和数据量不同可能会有些差异但排查思路基本上是一致的。下面我挑两个典型问题展开说一下。5.2 排查案例Hive 作业一直卡在 INITIALIZING这应该是大家最容易碰到的问题。Hive 的 SQL 已经提交到 YARN日志里显示Substituting input paths之后就再也不动了YARN 上能看到一个TezSession的 Application但状态一直是 RUNNING 或者反复重试。我遇到过两次第一次是tez.lib.uris里写成了解压后的目录路径但目录末尾没有加/导致 Tez 无法拼接出完整的 jar 地址第二次是集群上 NodeManager 的本地目录空间不足Tez 从 HDFS 拉取 tar.gz 后无法完成解压AppMaster 一直在等本地资源造成死循环。排查这种问题时不要只盯着 Hive 的日志要去 NodeManager 的日志里搜DistributedCache或LocalResource相关报错。Tez 的tez.lib.uris机制本质上就是利用了 YARN 的 LocalResource 能力任何一环出错表现都是作业卡在 INITIALIZING。解决办法是先把tez.lib.uris换成 tar.gz 路径并确认 HDFS 上有 755 权限然后再检查 NodeManager 的yarn.nodemanager.local-dirs所在磁盘的剩余空间。5.3 排查案例NoClassDefFoundError 与 jar 冲突问题另一个高频问题是 Hive 客户端或 Tez AppMaster 报NoClassDefFoundError。这里重点说一下类冲突判断思路。Hive 3.1.2 的 lib 目录下自带了一部分 Tez 0.9.2 的 jar如果你直接在hive-env.sh里把TEZ_JARS加到了HADOOP_CLASSPATH但HADOOP_CLASSPATH是在 Hive 启动脚本之后才加载的那么 JVM 很可能先加载了 Hive 自带的旧版 Tez 类导致运行到新版 API 时找不到方法。我在解决时用了两步。第一步是把TEZ_CONF_DIR和TEZ_JARS的导出语句放到hive-env.sh的最前面确保它在 Hive 自身脚本初始化之前就进入环境变量。第二步是检查$HIVE_HOME/lib下面是否有和 Tez 0.10.1 同路径冲突的旧 jar如果有把旧的 Tez 相关 jar 改名或移除再恢复 Hive 目录的可写权限。实际操作中Hive 3.1.2 自带的hive-exec-3.1.2.jar里面也嵌了一些与 Tez 相关的类这个不用动需要处理的是独立的tez-api、tez-dag等 jar。5.4 性能调优经验Tez 参数不只是越大越好Tez 跑起来之后下一步自然是调优。很多新手一上来就把hive.tez.container.size调得特别大结果 JVM 堆内存经常 OOM或者 YARN 队列直接被占满其他作业全部排队。我个人的经验是先按物理内存的 1/4 到 1/2 规划 Tez 容器总资源再根据 DAG 的并发度反推单个容器的大小。比如节点有 64GB 内存YARN 给该队列分配 32GB如果期望同时跑 8 个 Task那么hive.tez.container.size设为 2GB 到 3GB 左右比较合理。tez.task.resource.memory.mb和hive.tez.container.size的关系很多人容易搞混。简单说tez.task.resource.memory.mb控制的是每个 Tez Task 向 YARN 申请的物理内存上限hive.tez.container.size控制的是 Hive 编译 SQL 后生成的 Tez Task 默认容器大小两者最好保持一致否则 YARN 的调度分配会出现内存碎片。此外hive.tez.java.opts里的-Xmx参数要略低于容器内存比如容器是 2GB-Xmx可以设成 1536m给堆外内存和元空间留点余地。我在这套环境上跑几百 GB 的离线任务时还把tez.runtime.io.sort.mb从默认的 256 提到了 512对 reduce 阶段的数据倾斜和排序效率有明显改善。但这个参数不是所有任务都适合调大如果你的任务本来就是小文件多、并发小的场景调大排序内存反而浪费。建议先用默认参数跑一轮观察 YARN 的 Container 内存使用曲线再针对耗时最长的 Stage 定向调整不要一次性把 Tez 参数全部拉满。5.5 Hive 与 Tez 联合调优的启动预热技巧还有一个小技巧使用 Hive on Tez 时TezSession 的启动是有固定开销的即使是一条select 1也要申请一个 ApplicationMaster耗时可能十几秒。如果业务上有很多短查询建议开启 HiveServer2 的会话复用让同一个用户提交的多个查询在同一个 TezSession 上执行避免每个查询都重新拉起 Tez AM。相关参数是property namehive.server2.tez.initialize.default.sessions/name valuetrue/value /property这样 HiveServer2 启动时就会预建一个 TezSession客户端请求到达后可以直接复用查询响应时间能缩短不少。但要注意预建的 session 会占用 YARN 资源所以这个功能只适合 HiveServer2 长期运行、短查询多的场景。如果只是跑批处理的定时任务每次单独启动 TezSession 反而更可控。另外Tez 0.10.1 在 Hadoop 3.3.1 环境下还支持tez.task.scale.memory.reserve-fraction等高级调优项但这类参数对大多数场景来说并不需要调整改多了反而容易让任务失败率上升。我的建议是先保证作业稳定运行再逐步尝试调优每改一个参数就要对比同一份 SQL 在 YARN 上的耗时和资源使用率不要一口气改五个参数然后不知道是哪个产生了效果。这套 Tez 0.10.1 minimal 打包和部署方案到现在我还在生产环境上用着。最近一次排查问题时我又把这个流程完整走了一遍发现最关键的还是编译时对齐 Hadoop 版本以及部署时理顺 classpath。跟我一样从 Hadoop 2.x 时代过来的人早期用惯了 Hive on Tez 的丝滑往往不太愿意退回 MapReduce所以把 Tez 0.10.1 跑在 Hadoop 3.3.1 和 Hive 3.1.2 上这件事值得认真对待。如果你已经在这条路上踩了坑回头看看编译参数和tez.lib.uris配置大概率能找到答案。
返回列表