ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

x86 macOS 上 IDEA 编译 Hadoop 2.6-cdh-5.14 源码实战

x86 macOS 上 IDEA 编译 Hadoop 2.6-cdh-5.14 源码实战 先说句实在的在 x86 架构的 macOS 上用 IntelliJ IDEA 去编译一份 Hadoop 2.6-cdh-5.14 源码听起来就很“考古”。但真当你要接手维护一套基于 CDH 5.14 的老数据平台时这事儿不但不冷门反而是绕不过去的日常。这篇文章把我实际操作的全过程写下来包括 JDK 版本选择、Maven 配置、IDEA 导入细节、CDH 仓库依赖以及我踩过的 protoc、native 库、堆内存这些雷。适合两类读者一类是被公司存量 CDH 系统绑定需要本地改代码、加断点的工程师另一类是单纯想研究 Hadoop 老架构想用 IDEA 把整个源码跑起来看个明白的学习型选手。1. 先搞清楚背景为什么要编译 Hadoop 2.6-cdh-5.141.1 存量 CDH 系统的真实使用场景CDH 5.14.2 是 2018 年前后发布的 Cloudera 发行版核心组件就是 Apache Hadoop 2.6.0。放在今天这套东西确实老但企业里并不少见。很多公司的离线数仓还在用它跑 MapReduce 作业、HDFS 还存着几百 TB 的历史数据业务不敢随便动版本也就不敢升。我遇到的情况是上层需要改 HDFS 的某个逻辑或者要在 NameNode 的某个方法里打日志、加断点看看到底哪个环节慢。这时候你手上只有一个办法——在本地把源码编译出来跑一个能断点调试的环境。官方早就停更了网上关于这个版本的现成资料也少很多坑只能自己踩。我自己只编译了 hadoop-common、hadoop-hdfs 两个模块就已经踩到 protobuf 版本、Maven 仓库源、macOS native 编译等多个问题更别说全量源码一起编。所以这篇里的每个“坑警示”都是真金白银换来的经验认真看能省你不少时间。1.2 源码编译和直接引依赖是两码事很多人一开始会问我写 WordCount 不是只需要在 pom 里加一个 hadoop-client 的依赖就行了吗对但那只是“用” Hadoop不是“改” Hadoop。如果你只是写业务代码、跑 MR 作业直接引入org.apache.hadoop:hadoop-client:2.6.0-cdh5.14.2的 Maven 依赖就够了完全不需要本地编译源码。但如果你想修改 Hadoop 内部实现、排查框架自身的 Bug或者在类里面打断点看执行流程那就必须把源码拉到本地让 IDEA 里跑的 class 是你自己编译的结果而不是中央仓库拉的二进制 jar。我整理了一个简单的对比方便你判断自己到底需不需要走源码编译这条路使用方式适用场景是否修改框架源码需要做的事Maven 依赖引入写 MR、调 API、做数据开发否在 pom 加依赖配置 CDH 仓库源码编译二次开发、Bug 排查、框架学习是下载源码用 Maven/IDEA 编译源码编译部署替换线上 CDH 自带的 Hadoop jar是编译后替换到对应集群节点或应用目录我这次的目标很明确在公司老环境之外搭一套本地可调试的源码副本顺便验证改动效果。2. 环境准备锁死 JDK 8、Maven 3 和 IDEA 配置2.1 JDK 版本为什么必须卡在 Java 8Hadoop 2.6 那个年代主流就是 Java 7/Java 8代码里用了大量内部 API比如com.sun.tools.javac、sun.security.util这些包。后来 JDK 9 开始模块化这些内部类被移走或禁止访问你用 JDK 11、JDK 17 去编译这版源码会直接报“找不到 com.sun.tools 的符号”之类的错误。所以第一步就是装 JDK 8别的版本都不行。在 x86 架构的 macOS 上安装 JDK 8我推荐直接下载 Oracle JDK 8 的 macOS x64 安装包或者用 Homebrewbrew install openjdk8。装完以后需要确认环境变量真的指向了 1.8。macOS 上可以用/usr/libexec/java_home -v 1.8拿到 JDK 8 的 Home 路径再如export JAVA_HOME$(/usr/libexec/java_home -v 1.8)这样设置。有个坑提醒你如果你机器上同时装了 JDK 8 和 JDK 17IDEA 的 Project SDK 设置错了编译时会看到各种奇怪的语法报错或内部包报错。不要怀疑源码有问题先检查 SDK。2.2 Maven 与 IDEA工具链的隐性约束Maven 版本不能太新。我实测下来Maven 3.5 到 3.9 都能顺利编译但千万别用 Maven 4。Maven 4 对插件默认行为改动很大很多老插件的执行顺序和参数解析方式变了编译 CDH 老源码时会触发莫名其妙的插件报错排查起来非常浪费时间。建议直接固定一个 Maven 3.6.x足够稳。IDEA 的话我没有特意选版本2023、2024 版都能打开项目但要注意一个关键点IDEA 自带了一个内置的 Maven如果你之前在命令行用外部 Maven那就必须让 IDEA 用同一个 Maven。设置路径在 Settings - Build Tools - Maven - Maven home path指到你安装的外部 Maven 目录然后确保 Settings - Build Tools - Maven - User settings file 指向你配置过 CDH 仓库的 settings.xml。这个细节很多人忽略结果 IDEA 里编译时的仓库地址和命令行完全不一样。2.3 编译前先检查 macOS 本地限制macOS 上默认的进程文件句柄限制比较低全量编译 Hadoop 这种大项目时Maven 会同时开启大量并发任务容易碰到Too many open files错误。在 shell 里先执行一次ulimit -n大概率看到的是 256这个数字在编译大项目时不够用。我一般会先ulimit -n 1024再启动 IDEA 或执行 Maven 命令如果从 IDEA 里编译还需要在编译前对启动 IDEA 的终端执行这个命令。另外检查一下 Xcode 命令行工具是否安装。没有它后面的 native 编译肯定挂实际上即使你不专门编译 native 库某些模块的构建脚本也会探测本机编译器是否存在。在终端执行xcode-select --install这一步安装的是 Xcode Command Line Tools里面包含 git、clang、make 等基础工具。安装完再检查一下clang --version git --version都正常输出环境就算过关了。3. 源码与依赖CDH 仓库是绕不过的坎3.1 拉对源码分支确认版本坐标Hadoop 2.6-cdh-5.14 不是从 Apache 官方仓库直接拿的而是 Cloudera 在 Apache Hadoop 2.6.0 基础上打了自己补丁的发行版。所以源码也要从 Cloudera 的仓库拉不是 Apache 的 GitHub。我用的地址是https://github.com/cloudera/hadoop切换到对应的 tag 或分支名称通常类似cdh5.14.2。如果访问 GitHub 比较慢也可以找 CDH 源码包镜像下载后解压。拉下来以后先到根目录的 pom.xml 里确认版本号是否真的是2.6.0-cdh5.14.2。出现类似hadoop-maven-plugins这类模块版本不一致的情况大多是分支拉错了。注意看清楚别把 Apache Hadoop 2.6.x 的源码当成 CDH 来编译两者可用的依赖坐标都不一样。3.2 在 settings.xml 里加 CDH 仓库别用 mirrorOf 星号这是整个编译流程里最容易被卡住的地方中央仓库根本没有带-cdh5.14.2后缀的 artifact。比如org.apache.hadoop:hadoop-auth:2.6.0-cdh5.14.2这个坐标只能从 Cloudera 仓库下载。如果你直接在默认中央仓库下依赖Maven 一定会报Could not resolve dependency。正确做法是在 Maven 的 settings.xml 里配置 profile 并激活 CDH 仓库却不要用mirrorOf*/mirrorOf把所有请求都镜像到 Cloudera那样会把平时用的 Spring、Netty、Guava 等第三方依赖请求也带到 Cloudera 仓库下载慢而且经常超时。更推荐的做法是只加一个 profile 加 repository写法和作用域如下profiles profile idcloudera-cdh/id repositories repository idcloudera/id urlhttps://repository.cloudera.com/content/groups/cdh-releases-rcs//url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /repository /repositories pluginRepositories pluginRepository idcloudera-plugins/id urlhttps://repository.cloudera.com/content/groups/cdh-releases-rcs//url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /pluginRepository /pluginRepositories /profile /profiles activeProfiles activeProfilecloudera-cdh/activeProfile /activeProfiles我测试下来这个地址能稳定拉取所有 cdh5.14.2 相关构建产物。如果当前网络访问不了也可以换成https://repository.cloudera.com/artifactory/cloudera-repos/试试这两个域名指向的内容大部分是一致的。关键是仓库加错了后面每一步都在报错所以这一步务必先验证。3.3 搞懂模块编译顺序先装 hadoop-maven-pluginsHadoop 源码不是一个单一 jar它分成 30 多个 Maven 模块模块之间存在依赖关系。第一次编译如果直接跑到根 pom 下执行mvn clean install你会在日志里撞到各种各样“找不到当前项目构建产物”的错误。我的建议是先只编译安装hadoop-maven-plugins模块它是很多其他模块构建插件的一部分需要先放到本地仓库。进入源码目录后执行一次针对性构建mvn clean install -pl hadoop-maven-plugins -DskipTests -Dmaven.javadoc.skiptrue -Drat.skiptrue -Dcheckstyle.skiptrue这里解释几个参数的作用-DskipTests跳过测试执行但保留测试编译-Dmaven.javadoc.skiptrue跳过 javadoc 生成省时间也避免老项目的 javadoc 插件在新 JDK 上失败-Drat.skiptrue跳过 Apache RAT 许可证检查这个检查在 CDH 老代码上经常因为文件头缺失而中断-Dcheckstyle.skiptrue跳过代码风格检查编译免得不必要的报错。等 hadoop-maven-plugins 安装完成再回到根目录做全量编译会顺利非常多。这一步相当于先把地基打好不能跳过不看。4. IDEA 里实操一步步把源码跑起来4.1 导入项目的方式用 Maven 结构而不是直接 Open在 IDEA 里打开源码项目不要用 File - Open 直接选源码根目录那样 IDEA 可能只当成普通目录处理无法识别 Maven 模型。正确方式是 File - Open 选中根目录下的pom.xmlIDEA 会弹窗问你是作为 Project 打开还是作为 Maven Project 打开这时选“Open as Project”IDEA 会自动开始导入 Maven 模块。导入过程会比较久因为 Hadoop 模块多、依赖大。如果 IDEA 右下角一直提示 indexing耐心等。你可以顺手做两件事一是确认 Project SDK 已经是 1.8二是确认项目的 Language Level 没有被人为改成 11。在 Project Structure - Project 里检查SDK和Language level我一般把 language level 固定在 8确保编译语义一致。还有一个细节项目里有些模块在 mac 上不需要参与编译比如hadoop-hdfs-native-client这类需要本机 C/C 编译器协同的模块在 IDEA 右侧 Maven 面板里可以右键先 skip 掉后面需要时再单独打开能显著减少导入负担。4.2 在 IDEA 的 Maven 面板执行编译与跳过检查IDEA 打开右侧 Maven 面板能看到根项目下面的生命周期命令。理想的编译操作顺序是先在根项目上执行clean再执行install。但直接双击 install默认运行动作里没有加跳过参数所以我更建议在 IDEA 的 Run/Debug Configurations 里新建一个 Maven 配置手动填入参数clean install -DskipTests -Dmaven.javadoc.skiptrue -Drat.skiptrue -Dcheckstyle.skiptrue这样 IDEAR 会按照填入的参数去执行不会受面板默认动作影响。写入位置是 Settings - Build Tools - Maven - Runner - VM Options这里填 Maven 的 JVM 参数。我填的是-Xmx2048m -XX:MaxMetaspaceSize512m你以为这是在编译 Java 代码时给 javac 用的吗不完全是。Maven 本身运行在一个 JVM 进程里这个进程要加载插件、跑各种生成任务堆内存不够就会出现“Java heap space”“GC overhead limit exceeded”的报错。特别是maven-antrun-plugin或者protoc生成任务一旦内存不足失败点会出现在奇奇怪怪的脚本位置很容易误判成源码问题。4.3 验证成果在 IDEA 里启动一个 NameNode编译完成后最直观的验证方式就是直接在 IDEA 里启动一个 NameNode 单机实例。这里虽然属于运行阶段但能反向验证你编译出来的 jar 是否完整可用。先在源码的hadoop-hdfs-project/hadoop-hdfs/src/main/resources下面放一份基础配置或者在某个工作目录建一个conf目录。最小配置包含core-site.xml和hdfs-site.xml。我给出最简参考版本!-- core-site.xml -- configuration property namefs.defaultFS/name valuehdfs://localhost:8020/value /property /configuration!-- hdfs-site.xml -- configuration property namedfs.replication/name value1/value /property /configuration然后在 IDEA 里找到org.apache.hadoop.hdfs.server.namenode.NameNode这个类Run Configuration 里设置 Program arguments 为-format先格式化等格式化顺利结束后再改 Program arguments 为空或者直接点 Run 二次启动。VM options 里需要加一个参数让 Hadoop 知道配置目录在哪以及指定 Java 库路径-Dhadoop.home.dir/path/to/hadoop-source-dir -Djava.library.path/path/to/hadoop-source-dir/hadoop-hdfs-project/hadoop-hdfs/target/native如果你跳过了 native 编译java.library.path指向的目录不存在也没关系启动后会提示Unable to load native-hadoop library但 NameNode 进程本身能起来日志里看到 “STARTUP_MSG” 和端口监听输出就说明你的编译产物是真正可用的。5. 常见问题与排查实录5.1 下载依赖失败找不到 cdh5.14.2 坐标这是出现频率最高的问题。报错长这样Could not resolve dependencies for project org.apache.hadoop:hadoop-auth:2.6.0-cdh5.14.2 The following artifacts could not be resolved: org.apache.hadoop:hadoop-auth:jar:2.6.0-cdh5.14.2第一反应应该是检查 CDH 仓库是否真的被激活而不是怀疑网络断了。我踩过几次都因为 IDEA 中 Maven 的 User settings file 指错了路径导致 IDEA 里走的仓库配置和我命令行里用的不是同一个。排查步骤是先在 IDEA 终端或系统终端用mvn help:effective-settings看当前生效的 profile 里有没有 cloudera 仓库没有就检查 settings.xml 内容。如果仓库确实加上了但下载还是超时或 404可以先用浏览器打开https://repository.cloudera.com/content/groups/cdh-releases-rcs/org/apache/hadoop/hadoop-auth/2.6.0-cdh5.14.2/看目录是否存在。有可能是网络问题或者是镜像目录访问不到。本地仓库如果之前残留了部分损坏的 jar也可以直接删掉.m2/repository/org/apache/hadoop目录重新拉省得 Maven 认为“本地有”而拒绝更新。5.2 protoc 和 protobuf 版本不一致的解决Hadoop 2.6 的序列化层用 protobuf 2.5.0构建时会调用 protoc 编译器来生成*.pb.cc和*.pb.h文件。macOS 上很多人用 Homebrew 装了新版本 protobuf比如 3.x 甚至 21.x这下版本就错位了。报错经常是protoc: error while loading shared libraries: libprotobuf.so.9或者Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:exec (generate-sources)解决办法是用和 Hadoop 2.6 匹配的 protoc 2.5.0。网上能找到 protoc-2.5.0 的 macOS 编译包或者用 Homebrew 搜索旧版本安装方式。不同环境差异较大简单说思路先确认系统里which protoc指向哪里如果版本不匹配就单独下载一个 protoc 2.5.0 可执行文件放到本地目录然后把该目录加到 PATH 最前面或者设置环境变量PROTOC_EXECUTABLE/path/to/protoc。我的实际经验是不要依赖系统 protoc尽量让 Maven 插件自己去下载它内置的 protoc。但如果你所在网络环境访问不了 protoc 的下载地址才手动指定本地的可执行文件。这个坑在 Linux 上少在 macOS 上很容易出现因为 Homebrew 默认装的版本都偏新。5.3 javac 堆内存爆掉与 GC overhead编译过程中如果日志开始出现java.lang.OutOfMemoryError: Java heap space或者GC overhead limit exceeded这不是源码问题是 Maven 运行的 JVM 内存不够。上面我在 IDEA Runner 里设置了-Xmx2048m这是最小起点。如果项目里同时打开多个模块或者 IDE 本身也在跑索引可以给到-Xmx4096m。要注意在 IDEA 里 Maven 的 VM Options 和 Application 的 VM Options 是两个不同的地方很多人只在 Run Configuration 里加了内存参数但 Maven 编译时的 JVM 是独立进程不吃这个参数。另外命令行编译时可以用这种方式export MAVEN_OPTS-Xmx3072m -XX:MaxMetaspaceSize512m设置后再执行 Maven 命令所有插件的进程都会带上这个内存参数。老项目编译时还会启动很多 forked 的 javac 进程如果连 fork 进程的内存也爆了可以在对应模块的 pom 或编译配置里设置jvmArguments但一般调大 MAVEN_OPTS 就够了。5.4 macOS 上 native 库编译失败的应对在 macOS 上做 Hadoop native 编译非常痛苦典型的失败是 configure 阶段报错找不到libtool、automake、autoconf这些工具链或者 C 编译器无法创建设置文件。如果你不是为了在 macOS 上追求本地 IO 性能我建议直接跳过 native 编译不要和它纠缠。跳过的方式就是上面说的不要加-Pnative和相关 profile也不要在根 pom 里触发 native profile。编译 HDFS 模块时如果发现 Maven 还是尝试执行compile-native或类似目标说明模块内部的默认行为把你拉进去了可以找到对应的 pom.xml 片段把 native 相关的 profile 设置为activeByDefaultfalse或者干脆临时注释掉 native 编译的 plugin 执行。跳过之后运行时会有一个小代价日志里频繁出现Unable to load native-hadoop library的警告HDFS 在本地模式下性能会略差但功能完全不受影响。如果你后面要把编译好的 jar 放到 Linux 生产环境那就不存在这个问题Linux 上重新-Pnative编译一次即可macOS 上的产物不要直接搬到 Linux 用 native 库毕竟.dylib和.so不通用。6. 这个版本后续还能怎么玩6.1 用它搭一个本地伪分布式集群编译完成的 jar 完全可以支撑一个伪分布式 HDFS我自己就是这么干的。把hadoop-common和hadoop-hdfs的 target 目录整理出来配合源码里的hadoop-hdfs-project/hadoop-hdfs/src/main/conf示例配置写一个简单的启动脚本分别拉起 NameNode 和 DataNode。格式化、启动、上传文件、下载文件整个流程走通后你才算真正把“编译 Hadoop”这件事闭环了。伪分布式下最需要注意的还是配置文件路径和数据目录位置。尽量把dfs.namenode.name.dir和dfs.datanode.data.dir指向一个明确的本地目录比如/tmp/hadoop-name和/tmp/hadoop-data避免 macOS 上默认路径权限不一致导致 DataNode 起不来。这个坑让我白折腾过半天最后发现就是/tmp下目录权限不对。6.2 做 MapReduce 二次开发要注意的细节如果你准备在这套源码基础上改 MapReduce 逻辑建议不要直接改hadoop-mapreduce-project里的代码而是新建一个独立 Maven 模块依赖本地编译的 hadoop-common、hadoop-hdfs、hadoop-mapreduce-client-core 等模块的 jar然后用providedscope。这样改业务代码和改框架代码不混在一起后续提交补丁也方便。IDEA 里可以让新模块依赖本地的这几个模块源直接做到“改框架源码业务模块实时同步”。跑 MR 作业时Class-Path 里要显式包含hadoop-common、hadoop-hdfs、hadoop-mapreduce-client-core以及对应依赖。CDH 老版本对 classpath 的识别没有后来的版本那么智能漏一个依赖经常会抛ClassNotFoundException。6.3 我编译完之后的几个体会绕了这一大圈最大的体会就是老版本项目编译最重要的不是网上找各种现成答案而是把版本一致性锁死。JDK 8、Maven 3.6、CDH 仓库、protoc 2.5这四样东西只要有一个版本偏了后续的报错就会变得毫无逻辑。先把版本和环境固定住剩下的编译动作其实都是机械操作。另一个体会是不要一上来就全量编译。Hadoop 源码模块多一个模块失败会连带影响其他模块的判断。先编 hadoop-maven-plugins再编 hadoop-common然后按依赖关系逐级往上编。只编你关心的模块能节省一半以上的时间。我就是先在 IDEA 里花了一个多小时把整个项目 import 完发现全量编译在 native 那里挂了一次又一次后来改成轻量模块编译反而半小时就拿到了想要的 jar。如果你和我一样需要在一套老 CDH 环境里做二次开发和问题定位希望这份实操记录能帮你少走点弯路。最后一个小提醒编译成功后记得把本地编译生成的 jar 和源码位置记下来因为你后面调试时用的每一个断点、每一步堆栈都会反复用到它们。
返回列表