分享一个大数据应用因为和大数据集群服务端组件的版本冲突引起的问题
1 先说结论
大数据应用程序与普通微服务应用程序的一个显著区别是,两者对底层各种依赖项的版本管理要求不同:
- 普通微服务只需要确保应用底层的各种依赖项,包括应用本身引入的直接与间接依赖,以及应用底层框架引入的直接与间接依赖,没有版本冲突即可;
- 而大数据应用程序,除了需要确保应用底层的各种依赖项没有版本冲突,还需要确保这些依赖项的版本,跟大数据集群服务端的 hdfs/hive/yarn/spark/flink 等的版本是兼容的;
- 之所以要求大数据应用程序和大数据集群服务端的版本兼容性,其根本原因是,通过 hadoop jar/spark-submit/flink run 等命令提交大数据作业时,这些命令会把大数据集群服务端的特定路径下的 JAR 包添加到作业底层 JVM 的类加载路径中(大家可以从 spark-submit/flink run等命令对应的 bash 脚本中轻松发现这点);
- 大数据集群服务端的多个服务之间,也有版本兼容性的要求,因为这些服务之间可能也互为客户端和服务端,比如 spark/flink/hive 就是 hdfs/yarn 的客户端 (各厂商的大数据发行版比如 cdh/cdp/tdh 等,其底层整合的都是各个服务组件相互之间没有兼容性问题的版本;所以用户基于 apache 开源版本自行封装组合各个组件构建大数据集群时尤其需要注意各组件相互之间的兼容性);
- 当编写 HIVE UDF/UDAF 时,尤其需要注意底层 pom 依赖中引入的 hive-exec 的版本,需要跟大数据集群服务端的 hive(以及 hive 底层的 hdfs/yarn 等),在版本上没有兼容性问题;
- 对接 CDH 等大数据平台的业务系统,在升级对接 CDP 等新版大数据平台时,其业务代码底层的 hdfs/yarn/hive/spark/flink 等依赖的版本,就需要跟服务端对应组件,在版本上没有兼容性问题;
- 当大数据应用程序和和大数据集群服务端的 hadoop/hive/spark/flink 等版本不兼容时,应用程序在执行时就会报各种奇怪的问题,比如找不到类定义,找不到类方法等;
- 大数据应用程序和大数据集群服务端的版本的兼容性,不一定要求二者版本完全一致,但一般至少要求二者大版本一致(如 hadoop3.2.1,其大版本是3,小版本是2,修复版本是1),因为大数据组件在大版本变动时普遍可能会更改一些底层的公共接口;
2 问题现象
某大数据业务系统对接了 CDH 大数据平台,并在多个客户的 CDH 大数据平台中长期平稳运行。
由于近期不少客户在规划升级 CDH 到 CDP,故该业务系统也开始了针对 CDP 大数据平台的适配工作。
该业务系统中部分作业使用了 HIVE UDF,这些 UDF 在 CDP 平台中运行时报错了,通过 yarn logs -applicationId xxx 查看详细日志,部分报错信息如下:
2022-11-10 10:17:45,088 [INFO] [main] |service.AbstractService|: Service org.apache.tez.dag.app.DAGAppMaster failed in state INITED; cause: java.lang.NumberFormatException: For input string: "30s" java.lang.NumberFormatException: For input string: "30s" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.parseLong(Long.java:631) at org.apache.hadoop.conf.Configuration.getLong(Configuration.java:1319) at org.apache.hadoop.hdfs.client.impl.DfsClientConf.<init>(DfsClientConf.java:247) at org.apache.hadoop.hdfs.DFSClient.<init>(DFSClient.java:303) at org.apache.hadoop.hdfs.DFSClient.<init>(DFSClient.java:287) at org.apache.hadoop.hdfs.DistributedFileSystem.initialize(DistributedFileSystem.java:156) at org.apache.hadoop.fs.FileSystem.createFileSystem(FileSystem.java:2811) at org.apache.hadoop.fs.FileSystem.access$200(FileSystem.java:100) at org.apache.hadoop.fs.FileSystem$Cache.getInternal(FileSystem.java:2848) at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:2830) at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:389) at org.apache.hadoop.fs.Path.getFileSystem(Path.java:356) at org.apache.tez.common.TezCommonUtils.getTezBaseStagingPath(TezCommonUtils.java:87) at org.apache.tez.common.TezCommonUtils.getTezSystemStagingPath(TezCommonUtils.java:146) at org.apache.tez.dag.app.DAGAppMaster.serviceInit(DAGAppMaster.java:465) at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163) at org.apache.tez.dag.app.DAGAppMaster$9.run(DAGAppMaster.java:2616) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.Subject.doAs(Subject.java:422) at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1807) at org.apache.tez.dag.app.DAGAppMaster.initAndStartAppMaster(DAGAppMaster.java:2613) at org.apache.tez.dag.app.DAGAppMaster.main(DAGAppMaster.java:2419)
3 问题原因
- 该问题的直接原因,跟 hdfs 的 HDFS-12920 缺陷有关:”HDFS-12920:HDFS default value change (with adding time unit) breaks old version MR tarball work with Hadoop 3.x“:
- HDFS-12920 简述如下:
- HDFS 前期通过 HDFS-10845 对配置文件 hdfs-default.xml 中的时间相关参数的默认的参数值做了修改,增加了时间单位比如 ms/second 等,然而老版本的 hadoop 客户端显然不能正确识别这些带时间单位的参数值;
- 比如参数 dfs.client.datanode-restart.timeout,在老版本中其默认值是 30000,而在新版本中其默认值是 30s,所以如果用户将使用了老版本的 hadoop 依赖的作业,提交到包含了新版本的hadoop的大数据集群中时,就会因为作业底层老版本客户端代码解析不了大数据集群服务端该参数的新版本默认值而包错误,也就是作业错误信息中的“java.lang.NumberFormatException: For input string: "30s";
- hadoop 官方也给出了一种临时的解决方案,即用户可以在配置文件 hdfs-site.xml 中,对时间参数显示指定不带时间单位的值,从而覆盖 hdfs-default.xml中带时间单位的默认值,从而避免后续解析问题;
- 然而该问题的根本原因其实是,大数据应用程序底层通过pom依赖引入的组件的版本,跟大数据集群服务端的版本存在兼容性问题,如下图所示,应用程序底层依赖的是hive-exec-2.1.1,而 CDP 服务端使用的是 hive-exec-3.1.X;
4 问题解决方案
- 问题解决方案是,将 HIVE UDF 通过 pom引入的依赖项 hive-exec,从 2.1.x 版本升级为 3.1.x 版本(最好大小版本号都跟服务端保持一致),并重新编译打包即可(调整依赖版本并重新打包即可,一般不需要改动代码);
- 需要说明的是,对参数 dfs.client.datanode-restart.timeout 显示指定不带时间单位的值比如30000,从而覆盖 hdfs-default.xml 中带时间单位的默认值 30s,虽然可以解决上述报错,但是会出现新的其他问题,其根本原因还是大数据应用代码和大数据集群服务端组件的版本不兼容;
- 还有一点需要注意下,大数据应用程序引入的依赖项,在开发测试环境中有些依赖项需要指定 pom scope 为 “compile”(特别是通过IDE直接运行调试时),但在生产环境这些依赖项需要指定 pom scope 为 “provided”,因为在生产环境运行时是运行在大数据集群环境中,此时通过 hadoop jar/spark-submit/flink run 等命令提交大数据作业时,这些命令会把大数据集群服务端的特定路径下的 JAR 包添加到作业底层 JVM 的类加载路径中,这些 JAR 包中已经包含了对应的类,如果应用打包时也包含了这些类,二者反倒容易引起版本冲突;(比如 hive udf 依赖的 hive-exec, 就应该指定 scope 为 provided);