前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住给大家分享一下。点击跳转到网站:https://www.captainai.net/dongkelun
前言
上一篇文章《Hadoop DistCp 使用详解与原理分析》介绍了 DistCp 的用法和基本原理。本文从源码出发,分析两个问题:
hadoop distcp命令从敲下回车到 Java 代码,经历了什么?- DistCp 的 13 个核心类之间是如何协作的?
基于 Hadoop 3.1.1 源码,所有引用均标注文件路径和方法名。
命令入口:从 Shell 到 Java
Linux Bash 分发(bin/hadoop)
Linux 的 bin/hadoop 脚本运行时,走到 hadoopcmd_case 函数。distcp 不在 case 列表中明确列出的子命令中,因此落到 *) 通配分支:
1 | # bin/hadoop : hadoopcmd_case() 中的 *) 分支 |
这里将 HADOOP_CLASSNAME 直接设为 "distcp"。然后脚本末尾调用 hadoop_generic_java_subcmd_handler,该函数拼接类路径并执行java命令。
不过 distcp 实际是一个 Java 类的工具,Linux 下更常见的做法是通过 hadoop_subcommand_distcp 动态函数来处理。但 Hadoop 3.1.1 的 bin/hadoop 中没有为 distcp 定义单独的子命令函数,因此最终会走到通用处理逻辑,通过 hadoop_add_classpath 把 TOOL_PATH 加入类路径后,以 org.apache.hadoop.tools.DistCp 启动。
Windows CMD 分发(bin/hadoop.cmd)
CMD 脚本的做法更直观,维护了一个 corecommands 列表,distcp 在其中:
1 | rem bin/hadoop.cmd corecommands 列表 |
匹配后跳转到 :distcp 标签,直接指定主类:
1 | rem bin/hadoop.cmd :distcp 标签 |
最终执行:
1 | java ... org.apache.hadoop.tools.DistCp <剩余参数> |
备选入口:mapred distcp
bin/mapred(Linux)和 bin/mapred.cmd(Windows)同样支持 mapred distcp,指向同一个类 org.apache.hadoop.tools.DistCp。
DistCp.main()
1 | // DistCp.java main() |
getDefaultConf() 加载 distcp-default.xml 和 distcp-site.xml 两个配置文件。
ToolRunner 回调
1 | // ToolRunner.java run() |
回到 DistCp.run():
1 | // DistCp.java run() |
参数解析
OptionsParser.parse()
OptionsParser 基于 Apache Commons CLI 实现。关键步骤:
1 | // OptionsParser.java parse() |
-D 参数在更早的 GenericOptionsParser 阶段已被过滤掉,不会到 OptionsParser 这一层。
DistCpOptions.Builder.validate()
参数校验规则(DistCpOptions.java Builder.validate()):
-overwrite与-update互斥-atomic不能与-update/-overwrite一起使用-delete与-diff/-rdiff互斥-append必须配合-update-append不能与-skipcrccheck一起使用-diff/-rdiff必须配合-update-v必须配合-log
校验通过后,才创建不可变的 DistCpOptions 对象。
DistCpContext
DistCpContext 包装了不可变的 DistCpOptions 和可变的运行时状态:
1 | // DistCpContext.java |
sourcePaths 是可变的——GlobbedCopyListing 展开通配符后、DistCpSync 处理快照后都会改写它。
核心调用链
总体流程
1 | DistCp.main() |
createJob() — 配置 MR 作业
1 | // DistCp.java createJob() |
InputFormat 选择策略
1 | // DistCpUtils.java getStrategy() |
context.getCopyStrategy() 默认值为 "uniformsize"(定义在 DistCpOptions.Builder 中),拼接后为 distcp.uniformsize.strategy.impl。
对比 distcp-default.xml 中的配置:
1 | <!-- distcp-default.xml --> |
注意:代码拼接的是 distcp.uniformsize.strategy.impl,而 XML 中定义的键是 distcp.static.strategy.impl。两者不匹配,因此代码中的 conf.getClass() 会查找不到,回退到默认值 UniformSizeInputFormat.class。这意味着即使想通过 distcp-default.xml 替换默认 InputFormat,配置键 distcp.static.strategy.impl 也不会被代码使用,需通过 distcp.uniformsize.strategy.impl 这个键来配置。
prepareFileListing() — 文件列表生成(核心分歧点)
1 | // DistCp.java prepareFileListing() |
路径 A:快照差异(-diff / -rdiff)
1 | // DistCp.java createInputFileListingWithDiff() |
路径 B:常规模式
1 | // DistCp.java createInputFileListing() |
CopyListing 工厂方法 — 三层选择
1 | // CopyListing.java getCopyListing() |
CopyListing 三层委托链
1 | FileBasedCopyListing — 从 -f 文件读取路径列表 |
每个委托层都会改写 context.setSourcePaths(),将展开后的路径传递给下一层。
SimpleCopyListing.doBuildListing() 是核心:
- 使用
ProducerConsumer模式多线程递归遍历目录 - 线程数由
-numListstatusThreads控制(默认 1,上限 40) - 每个文件/目录写一条 SequenceFile 记录
- key = 相对路径(
Text),value = 文件状态(CopyListingFileStatus)
CopyListing.buildListing() 作为模板方法,统一流程:
1 | // CopyListing.java buildListing() |
DistCpSync — 快照差异路径
当 -diff 或 -rdiff 启用时,prepareFileListing() 先调 DistCpSync.sync() 做同步:
1 | DistCpSync.sync() |
随后 prepareDiffListForCopyListing() 只返回 MODIFY 和 CREATE 条目(DELETE/RENAME 已由 sync 处理),给 SimpleCopyListing.doBuildListingWithSnapshotDiff() 使用。
UniformSizeInputFormat 与 DynamicInputFormat
UniformSizeInputFormat(默认)
按字节数均匀切分分片:
1 | // UniformSizeInputFormat.java getSplits() |
DynamicInputFormat
Worker 模式——文件列表拆成小 chunk,Mapper 处理完一个领下一个:
1 | splitCopyListingIntoChunksWithShuffle() |
适合文件大小差异大的场景,避免 UniformSize 中”大文件卡住一个 Mapper,其他空闲”的问题。
CopyMapper 端:实际文件拷贝
整体流程
1 | CopyMapper.setup() |
checkUpdate() — 拷贝决策
1 | // CopyMapper.java checkUpdate() |
canSkip() — 能否跳过
1 | // CopyMapper.java canSkip() |
RetriableFileCopyCommand — 带重试的文件拷贝
1 | RetriableFileCopyCommand.execute() |
失败后自动重试(继承 RetriableCommand,默认 3 次指数退避)。
ThrottledInputStream — 令牌桶限速
1 | // ThrottledInputStream.java throttle() |
速率计算为整个生命周期平均值,短期可能超出但长期趋近设定值。
CopyCommitter — 作业后处理
commitJob() 在 MapReduce 框架回调中执行:
1 | CopyCommitter.commitJob() |
类运行位置汇总
| 类 | 运行端 | 职责 |
|---|---|---|
DistCp |
Driver | 入口,配置作业,提交 |
DistCpOptions |
Driver | 不可变参数模型,通过 Configuration 传递到 Mapper |
OptionsParser |
Driver | 命令行解析 |
DistCpContext |
Driver | 运行时上下文 |
DistCpConstants |
Driver + Mapper | 常量定义 |
DistCpSync |
Driver | 快照差异同步 |
CopyListing (abstract) |
Driver | 文件列表抽象 |
SimpleCopyListing |
Driver | 递归遍历目录树,生成 SequenceFile |
GlobbedCopyListing |
Driver | 展开通配符,委托 SimpleCopyListing |
FileBasedCopyListing |
Driver | 从文件读路径,委托 GlobbedCopyListing |
CopyMapper |
Mapper | 执行文件拷贝 |
RetriableFileCopyCommand |
Mapper | 带重试的拷贝(包装限速流) |
ThrottledInputStream |
Mapper | 令牌桶带宽限速 |
UniformSizeInputFormat |
Driver + Mapper | 按字节数均匀分片 |
DynamicInputFormat |
Driver + Mapper | 动态 Worker 模式分片 |
CopyCommitter |
Driver | 作业后处理(MR 框架回调) |
CopyOutputFormat |
Driver + Mapper | OutputFormat 配置 |
DistCpUtils |
Driver + Mapper | 工具方法 |
DiffInfo |
Driver | 快照差异数据载体 |
调用流程图
1 | hadoop distcp -update src tgt |
设计要点
- 装饰器 + 模板方法:
FileBasedCopyListing→GlobbedCopyListing→SimpleCopyListing,每层只做一件事,CopyListing.buildListing()作为模板定义固定流程。 - Builder 模式:
DistCpOptions使用 Builder 构建,build()时做参数校验,构建后不可变。 - DistCpContext 可变性:
sourcePaths和targetPathExists可在运行时改写,适应 Glob 展开和快照处理。 - 配置键不对齐:代码按
distcp.{strategy}.strategy.impl查找,而distcp-default.xml中定义的是distcp.static.strategy.impl,实际上未被使用,依赖代码中的回退默认值UniformSizeInputFormat.class。 - 纯 Map 作业:
job.setNumReduceTasks(0),不设 Reduce 阶段,所有文件拷贝在 Mapper 中完成。