Hive 3.1.3与Tez 0.10.2源码编译适配Hadoop 3.3.4全指南
先说个我在实际运维里遇到的场景公司把 Hadoop 集群从 2.7 升级到 3.3.4顺手想用上 Hive 3.1.3 跑数仓任务结果拿官网现成的 apache-hive-3.1.3-bin.tar.gz 一部署HiveServer2 起来了跑一条简单的 select count(*) 直接报NoSuchMethodError后面跟着一堆org.apache.hadoop.fs.*的类冲突。查了半天根本原因就是官方预编译包默认绑定的 Hadoop 版本太老跟集群实际版本对不上。这个问题在 Hadoop 3.2.2 和 3.3.4 上都存在只是报错形式略有差异。好在那时候我已经把这一套源码编译摸熟了从 Hive 3.1.3顺带 3.0.0到 Tez 0.10.2从改 pom 到出 tar.gz再到适配 Hadoop 3.2.2 和 3.3.4 部署上线每一步都踩过坑。这篇就把完整过程写出来包括我编译好的安装包整理方式给后面要折腾同样事情的兄弟省点时间。1. 为什么官方二进制包会翻车Hive、Tez、Hadoop的版本错位1.1 官方包里到底捆绑了什么先搞清楚一个概念Hive 官方发布页提供的二进制包并不是“与所有 Hadoop 版本兼容”的万能包。它的编译产物里直接打进了 Hadoop Client 相关依赖这些依赖在编译时被固定成了某个默认版本。Hive 3.1.3 源码根目录下的pom.xml里有一个hadoop.version属性默认值是3.1.0。也就是说你用官网包跑在 Hadoop 3.1.0 上大概率没问题但放到 Hadoop 3.3.4 上运行时 ClassLoader 加载到的 Hadoop 类来自集群的hadoop-common-3.3.4.jar而 Hive 自己带的 lib 目录下又有hadoop-client-api-3.1.0.jar两个版本的 API 签名有细微变化于是出现NoSuchMethodError、ClassNotFoundException这类问题。Tez 的情况更直接。Tez 0.10.2 发布时主要适配 Hadoop 3.2.x它的 pom 里默认的 Hadoop 版本大概是3.2.0。如果你把 Tez 0.10.2 的包丢到 Hadoop 3.3.4 上报错往往不是立即出现而是在任务提交阶段Tez 的Session启动时会去校验 Hadoop RPC 协议、YARN 相关类版本不匹配直接导致TezSessionPoolManager初始化失败。1.2 哪些部署场景必须走源码编译很多人问我直接用 CDH、HDP 的发行版不行吗可以但发行版有自己的问题版本滞后、不再免费提供商业支持、和你们公司内部统一版本管理策略冲突。在下面几类场景里源码编译几乎是唯一可控的方案公司内部指定了统一的 Hadoop 版本比如 3.3.4不允许为了 Hive 单独降级 Hadoop。需要开启特定特性比如 Hive 3.x 的 LLAP、Tez 的本地模式调优这些特性对版本匹配更敏感。安全加固要求需要自行替换 Hive/Tez 中的某些依赖版本比如 Jackson、Guava。集群架构是跨版本混布部分节点用 Hadoop 3.2.2部分用 3.3.4需要一套能同时兼容的 Hive 和 Tez 包。我自己就是第三种和第四种的结合线下测试集群用的 3.2.2线上生产集群是 3.3.4。所以我编译了两套产物一套用-Dhadoop.version3.2.2一套用-Dhadoop.version3.3.4分别部署到对应集群。1.3 版本矩阵Hadoop 3.2.2 vs 3.3.4 的差异点Hadoop 3.2.2 和 3.3.4 之间大版本没变但有不少内部 API 调整。几个我实际遇到的差异Hadoop 3.3.x 开始把org.apache.hadoop.fs.FileSystem的get系列方法做了更多重载导致 Hive 里部分直接调用FileSystem.get(Configuration)的代码编译期没问题运行时却可能找到错误签名。Hadoop 3.3.x 引入了shaded之后的hadoop-client-api类路径下同时存在老版本和新版本的 client jar 时最容易出现NoSuchMethodError。Hadoop 3.3.4 对 YARN Timeline Service 的 client 端做了一些向后不兼容的调整Tez 0.10.2 里调用TimelineClient的代码就需要重新编译适配。这些差异用一句话总结就是“API 层面看着兼容字节码层面不一定兼容。”所以与其在线上排查各种莫名其妙的类冲突不如直接源码编译让 Hive 和 Tez 的字节码与目标 Hadoop 版本保持一致。2. 编译环境的三个关键选择JDK、Maven、Protobuf2.1 JDK 8 是唯一稳妥选项Hive 3.1.3 官方文档说的是支持 JDK 8 和 JDK 11但我在编译时用 JDK 11 踩过坑hive-exec模块里的org.apache.hive.common.util.HiveVersionInfo生成会失败还有一些依赖的 Java 字节码版本不符合某些插件预期。Tez 0.10.2 对 JDK 11 的支持也不完整尤其是tez-runtime-library里用到的javax.annotation相关类在 JDK 11 里被移除了编译期直接报找不到符号。所以最稳妥的选择是 JDK 8具体用1.8.0_202或更高的小版本都可以。建议用OpenJDK 8避免 Oracle JDK 的授权问题。JDK 8 的安装不多说但记得配置JAVA_HOME环境变量并且确保mvn -version输出的 Java 版本和/usr/bin/java一致。很多编译失败案例是自己配了多个 JDKshell 里用的和 Maven 里用的不是一个版本。2.2 Protobuf 2.5.0 是最大的“隐形门槛”这里一定要划重点Hive 3.1.3 和 Tez 0.10.2 都依赖 Protocol Buffers 2.5.0 来生成RecordIO、Tez的 RPC 协议相关的 Java 类。如果你直接用更高版本的 protobuf比如 3.x会在mvn generate-sources阶段报各种Unknown field或者生成的 Java 类无法通过编译。解决方案有两种在本地安装 protobuf 2.5.0 编译器也就是protoc然后把protoc加入PATH。使用 Maven 的protobuf-maven-plugin自动下载 protoc。Hive 源码里默认配置了它但国内网络环境经常从 Maven Central 下载 protoc 二进制失败所以建议还是手动安装一次。我之前在 CentOS 7 上手动装 protobuf 2.5.0 的命令记录如下可以作参考wget https://github.com/protocolbuffers/protobuf/releases/download/v2.5.0/protobuf-2.5.0.tar.gz tar -zxvf protobuf-2.5.0.tar.gz cd protobuf-2.5.0 ./configure --prefix/usr/local/protobuf-2.5.0 make -j8 make install echo export PATH/usr/local/protobuf-2.5.0/bin:$PATH ~/.bashrc source ~/.bashrc protoc --versionprotoc --version输出必须是libprotoc 2.5.0不是 2.5.0 的话后面大概率出问题。注意protobuf-2.5.0.tar.gz的下载源在 GitHub 上如果网络受限找内网镜像也可以。安装完成后最好把/usr/local/protobuf-2.5.0/bin加到 Maven 的PATH里确保 IDE 或命令行能识别。2.3 Maven 配置镜像、本地仓库、构建缓存Hive 和 Tez 都是大工程依赖数量巨大。不配置镜像直接跑 Maven光下载依赖就得俩小时而且大概率中途中断。建议在~/.m2/settings.xml里配置阿里云镜像mirrors mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/central/url /mirror /mirrors同时建议把MAVEN_OPTS调大一点避免编译到一半内存不足export MAVEN_OPTS-Xms2048m -Xmx4096m还有一个经验Hive 编译时如果~/.m2/repository已经缓存过 Hive 3.1.0 等旧版本的依赖建议先删掉org/apache/hive目录否则可能出现SNAPSHOT或者旧版本依赖被错误复用的问题。Tez 同理删掉org/apache/tez缓存目录再编译。3. Hive 3.1.3 编译全流程从改 pom 到拿到 tar.gz3.1 拉取源码与目录结构先拉源码git clone https://github.com/apache/hive.git cd hive git checkout rel/release-3.1.3如果你要的是 Hive 3.0.0就git checkout rel/release-3.0.0。Hive 3.0.0 和 3.1.3 的编译流程几乎一样主要差异在部分模块依赖版本和 bugfix。这次我以 3.1.3 为主讲。源码目录里需要关注的几个核心目录exec/Hive 执行引擎相关编译最重的模块。ql/HiveQL 解析、计划生成、优化器编译时间最长的模块。metastore/Metastore Server 和客户端。packaging/最终生成安装包的模块packaging/target下会产出apache-hive-3.1.3-bin.tar.gz。3.2 修改 Hadoop 版本属性打开根目录pom.xml找到这一段properties hive.version3.1.3/hive.version hadoop.version3.1.0/hadoop.version ... /properties把hadoop.version改成你要适配的版本。如果适配 Hadoop 3.3.4hadoop.version3.3.4/hadoop.version如果要适配 Hadoop 3.2.2hadoop.version3.2.2/hadoop.version这里有个细节Hive 3.1.3 源码里有些模块比如hive-storage-api有自己的 pom也引用了父 pom 的hadoop.version所以只要父 pom 里改对了大部分模块会跟着走。但个别模块里显式用了版本号比如hive-llap-common里可能写了hadoop-client-api的version建议全局搜一下3.1.0这个默认版本号把所有显式写死的 Hadoop 版本都替换成目标版本。我自己的做法是grep -r 3.1.0 --include*.xml | grep hadoop然后把找到的3.1.0统一替换成3.3.4或3.2.2。注意别把 Hive 自己版本号里的3.1.3和 Hadoop 默认版本混了替换前看好上下文。3.3 处理 Guava 和 protobuf 冲突Hive 编译过程中最常见的两个依赖冲突Guava 版本冲突Hive 3.1.3 默认用 Guava 19.0而 Hadoop 3.3.4 自带的 Guava 是 27.0。运行时如果 Hive 的 lib 下是 19.0集群 classpath 里是 27.0会导致NoSuchMethodError: com.google.common.base.Preconditions.checkArgument等诡异问题。编译层面把 Hive 的guava.version属性从19.0改成27.0并不能一劳永逸因为 Hive 某些模块的代码是依赖 Guava 19 API 写的。实际测试下来guava.version27.0配合 Hadoop 3.3.4 在 Hive on Tez 模式下可以稳定运行。但要注意改完版本号之后Hive 编译产物里的guava-27.0-jre.jar会和 Hadoop 自带的guava-27.0-jre.jar共存不是太大问题因为版本一致了。protobuf 版本冲突Hive 3.1.3 内部的hive-service-rpc、hive-metastore使用的是 protobuf 2.5.0。Hadoop 3.3.4 中部分模块通过hadoop-shaded-protobuf_3_7来隔离 protobuf 3.x 版本所以编译时尤其注意不要全局升级 protobuf 版本。如果某个模块非要你用 3.x请只看那一个模块不要动根 pom 里的protobuf.version。3.4 执行编译命令和 profile 选择Hive 编译命令有比较固定的组合。最常用的是mvn clean install -DskipTests -Phadoop-3 -Pdist解释一下关键参数-DskipTests跳过单元测试。Hive 的TestMiniLlapCluster之类的测试跑起来非常耗时间完全没必要。-Phadoop-3激活 Hadoop 3 的 profile。Hive 3.1.3 源码中专门有hadoop-3profile会调整某些依赖的版本和插件行为。-Pdist生成安装包。不加这个 profilepackaging/target下不会产出apache-hive-3.1.3-bin.tar.gz。如果你的网络环境不太好可以加-o离线模式来用本地仓库但首次编译不建议因为缓存不完整。编译时长取决于机器配置。8 核 16G 内存的机器大概需要 40~60 分钟ql模块和exec模块最耗时日志里会看到它们在反复执行shade和protoc。建议用nohup挂后台跑nohup mvn clean install -DskipTests -Phadoop-3 -Pdist hive_build.log 21 tail -f hive_build.log看到BUILD SUCCESS就说明编译完成。3.5 验证产物编译完后在packaging/target下会有apache-hive-3.1.3-bin.tar.gz apache-hive-3.1.3-bin/解压看看重点检查两个地方tar -zxvf apache-hive-3.1.3-bin.tar.gz ls apache-hive-3.1.3-bin/lib | grep hadoop-client-api ls apache-hive-3.1.3-bin/lib | grep guava如果你改的是 Hadoop 3.3.4这里应该看到hadoop-client-api-3.3.4.jarguava 也应该是你指定的版本。如果看到的是 3.1.0说明 pom 没改彻底回去继续查。4. Tez 0.10.2 编译二次适配比想象中麻烦4.1 Tez 在 Hive on Tez 中扮演的角色Tez 是 Hive on Tez 执行模式的底层 DAG 执行引擎。Hive 负责把 SQL 转成 Tez 能理解的 DAGTez 负责在 YARN 上调度执行。问题在于Hive 官方包里默认带了一个tez-0.10.1或相近版本这个包是适配 Hadoop 3.1.0 的直接跑在 Hadoop 3.3.4 上会在启动TezSession时失败。所以我从来不用 Hive 自带的 Tez都是自己单独编译 Tez 0.10.2再把 Hive 的tez.lib.path指向自己编译的版本。4.2 修改 Tez 的 Hadoop 版本属性从 GitHub 拉 Tez 源码git clone https://github.com/apache/tez.git cd tez git checkout rel/release-0.10.2打开根目录pom.xml找到properties hadoop.version3.2.0/hadoop.version ... /propertiesTez 0.10.2 默认的 Hadoop 版本是 3.2.0要适配 3.3.4 就改成3.3.4适配 3.2.2 就改成3.2.2。但光改这个还不太够。Tez 源码里有些地方显式引用了 Hadoop 模块版本比如tez-api、tez-mapreduce。有些模块里写死了hadoop-client的版本号建议全局搜一下grep -r 3.2.0 --include*.xml .把符合条件的 Hadoop 相关版本统一替换。注意tez-0.10.2里也有别的地方用到 3.2.0比如某个maven-bundle-plugin的配置改的时候要看清上下文避免误伤。4.3 关键构建命令Tez 的打包命令和 Hive 不太一样。要生成可直接部署的 tar.gz用mvn clean package -DskipTeststrue -Dmaven.javadoc.skiptrue -Dtar这里-Dtar会触发tez-dist模块生成tez-0.10.2-minimal.tar.gz和tez-0.10.2.tar.gz。如果你只想要跑 Hive on Tez 的最小包用minimal就够了里面只包含运行所需的 jar不包含源码和测试 jar。编译 Tez 的时间相对短一些通常 20~40 分钟。主要耗时在tez-runtime-library和tez-plugins模块的依赖解析和打包上。同样建议nohup后台跑并把日志留档。4.4 将 Tez 打包并上传 HDFSTez 编译完成后产物在tez-dist/target/tez-0.10.2-minimal.tar.gz tez-dist/target/tez-0.10.2.tar.gzTez 运行时需要把 jar 分发到 NodeManager 上常见做法是把 tar.gz 上传到 HDFS 指定目录然后通过tez.lib.uris指向它。比如hdfs dfs -mkdir -p /tez hdfs dfs -put tez-0.10.2-minimal.tar.gz /tez/这样 YARN 上的 Tez AM 才能通过 HDFS 获取依赖 jar。另外要注意Tez 0.10.2 编译后依赖的hadoop-client-api版本已经变成你指定的 Hadoop 版本所以部署后不会再和集群 Hadoop 冲突。如果实在不想编译 Tez也可以直接下载官方 Tez 0.10.2 包但要额外排查 Hadoop 版本是否一致我建议还是自己编译省得后面排查莫名其妙的问题。5. 部署和验证编译完了怎么用起来5.1 安装包整体结构编译完成后我们手里就有两套核心安装包组件安装包适配 HadoopHive Server/Clientapache-hive-3.1.3-bin.tar.gz3.3.4 / 3.2.2Teztez-0.10.2-minimal.tar.gz3.3.4 / 3.2.2如果是 Hive 3.0.0产物名会变成apache-hive-3.0.0-bin.tar.gz内容结构类似。Hive 安装包解压后主要目录bin/ # hive、hive-config.sh、beeline 等命令 conf/ # hive-site.xml、hive-env.sh 等配置模板 lib/ # 所有依赖 jar包括 hadoop-client-api、guava、tez相关Tez 安装包解压后是一个标准 jar 集合里面没有可执行脚本主要给 Hive 和 YARN 提供依赖。5.2 Hive 与 Tez 的配置联动把编译好的 Hive 安装包部署到/opt/hive然后改conf/hive-env.sh如果没有就基于模板复制export HADOOP_HOME/opt/hadoop export HIVE_HOME/opt/hive export TEZ_HOME/opt/tez export TEZ_JARS/opt/tez/*:/opt/tez/lib/* export HIVE_AUX_JARS_PATH/opt/tez/*:/opt/tez/lib/*注意HIVE_AUX_JARS_PATH必须包含 Tez 的 lib否则 Hive 在启动 Tez Session 时找不到 Tez 的类。再改conf/hive-site.xml设置执行引擎和执行模式property namehive.execution.engine/name valuetez/value /property property namehive.execution.mode/name valuecontainer/value /property property nametez.lib.uris/name valuehdfs:///tez/tez-0.10.2-minimal.tar.gz/value /propertytez.lib.uris里的 HDFS 路径要跟你第 4.4 节上传的路径保持一致。如果忘记配这个属性Tez 启动时会在本地找 jarNodeManager 上没有就会报Could not find tez jars。5.3 跑第一个 Hive on Tez 任务部署完之后先初始化 schema/opt/hive/bin/schematool -dbType mysql -initSchema注意Metastore 存储在 MySQL/PG 里需要提前建好库和账号。初始化完成后启动 Metastore 和 HiveServer2/opt/hive/bin/hive --service metastore /opt/hive/bin/hive --service hiveserver2 然后通过 beeline 连上去/opt/hive/bin/beeline -u jdbc:hive2://localhost:10000 -n root执行任务CREATE TABLE t1(id INT); INSERT INTO t1 VALUES (1), (2), (3); SELECT count(*) FROM t1;如果能看到 MapReduce 变成了 Tez 的 DAG并且最终输出 3说明 Hive on Tez 编译和部署全部正常。从 YARN ResourceManager 的 Web UI 里也可以看到TezTask类型的 Application。6. 编译实测中的高频坑位与排查思路6.1 protoc 报错与排查编译 Hive 时最常见的报错长这样[ERROR] Failed to execute goal org.apache.hadoop:hadoop-maven-plugins:3.1.0:protoc (compile-protoc) on project hive-service-rpc: org.apache.maven.plugin.MojoExecutionException: protoc is not recognized as an internal or external command这个就是系统找不到protoc命令。先确认which protoc protoc --version如果没有参考 2.2 节装好 protobuf 2.5.0然后重试。如果装了还是找不到检查 Maven 进程的PATH是不是被 shell 配置覆盖了最好把/usr/local/protobuf-2.5.0/bin写进/etc/profile里。还有一种情况是protoc版本不对比如系统里装了 protobuf 3.x。Hive 3.1.3 的hive-service-rpc模块用 2.5.0 生成代码用 3.x 的 protoc 生成的类会在编译期报cannot find symbol或者一堆unnecessary SuppressWarnings错误。这种只能把 2.5.0 之外的其他版本从 PATH 里移除。6.2 依赖下载超时与 mirror 调整国内网络直接访问 Maven Central 基本等于折磨。除了阿里云镜像可以再加一个huaweicloud镜像作为 backupmirror idhuaweicloud/id mirrorOfcentral/mirrorOf urlhttps://repo.huaweicloud.com/repository/maven//url /mirror另外Hive 编译时会下载 Hadoop 的native库和 protoc 相关二进制这些不在 Maven Central 上而是绑定在 Hadoop 的源码包或者特定插件里。如果卡在某个下载点可以手动把对应的 jar 放到本地仓库_remote.repositories目录里或者干脆在公司内网搭一个 Nexus 代理把外网仓库都代理一遍。我后面把编译好的包放在内网之后就再也没被下载超时折磨过。6.3 构建内存溢出Hive 的ql模块在打包时会对依赖做 shade这个阶段内存占用非常大。如果报java.lang.OutOfMemoryError: Java heap space或者 Maven 的Daemon线程被杀优先调整MAVEN_OPTSexport MAVEN_OPTS-Xms4g -Xmx8g如果机器内存只有 8G编译 Hive 可能比较勉强建议至少 16G 内存。还可以通过跳过部分模块来缓解比如不需要 LLAP 时可以mvn clean install -DskipTests -Phadoop-3 -Pdist -pl !llap/server -pl !llap/tez不过这种选择性编译可能导致最终安装包缺东西不是特别老练的情况下不建议用。6.4 常见报错速查表报错信息原因分析解决办法NoClassDefFoundError: org/apache/hadoop/fs/FileSystem运行期 Hadoop 版本不匹配重新编译 Hive/Tez指定匹配的 hadoop.versionNoSuchMethodError: com.google.common.base.Preconditions.checkArgumentGuava 版本冲突Hive 编译时把 guava.version 调整到与 Hadoop 一致protoc is not recognized缺少 protobuf 编译器安装 protobuf 2.5.0Failed to execute goal net.alchim31.maven:scala-maven-pluginScala 插件下载失败配置阿里云镜像重试Could not find artifact org.apache.tez:tez-api:jar:0.10.2本地仓库 Tez 依赖没装上先执行mvn install -DskipTests再打包TezSession has already stoppedtez.lib.uris 路径错误或 tar 包损坏检查 HDFS 路径和文件完整性最后补一句我自己的习惯编译前先记录当前的~/.m2/repository大小编译完对比新增了多少依赖这样能快速判断是不是有模块没命中本地缓存。我第二次编译 Tez 时因为漏了mvn install导致tez-plugins模块一直拉不到tez-api的 SNAPSHOT 包折腾了半小时才意识到是本地仓库里没有先安装父模块。我编译好的apache-hive-3.1.3-bin.tar.gz适配 Hadoop 3.3.4 和 3.2.2 两套、tez-0.10.2-minimal.tar.gz都整理到了网盘里连同编译时用的settings.xml和完整构建日志一起放了进去。你如果不想折腾编译流程直接拿去用也没问题但我还是建议至少跟着流程走一遍因为真实环境里的依赖冲突永远不会只出现在教科书上跑一遍编译后面排查问题的底气完全不一样。