在HDFS中,我们经常会碰到跨集群数据拷贝的场景,例如某些任务数据结果计算生成,然后问你启动任务把结果数据导出到另外一个集群中以作后续的分析等这样的用途。Hadoop作为一套成熟完善的系统,也为我们提供了专门的拷贝工具,Distcp,全称Distributed copy,意为分布式的拷贝。说到Distcp工具本身,很多同学估计不会陌生,不过本文笔者来聊聊Distcp基于HDFS fastcopy原理下的性能提升改造。在某些特定拷贝场景下,distcp+fastcopy模式能大大缩短大数据量拷贝的时间开销。
首先,第一个问题,何为HDFS的fastcopy?HDFS fastcopy指的是一种不实际拷贝物理数据的数据拷贝方式,我们可以理解为其为一种元数据的拷贝,物理数据不动的数据拷贝方式。HDFS fastcopy目前并不是HDFS社区已经合入的一个功能,但是有对应的基本功能的patch实现,相关JIRA为HDFS-2139:Fast copy for HDFS。
Fastcopy的拷贝过程区别于原始数据拷贝方式的最大区别在于物理数据是通过建立hardlink的方式来做,省去了实际数据拷贝的过程,以此大大缩短数据拷贝的时间,原理图如下所示:
因为fastcopy是依赖于本地数据来建hardlink的,因此它在使用上有以下的限制:
DataNode需要是Federation模式,需要是被source NN, target NN所share。
所以我们不能使用fastcopy来做完全独立的集群间的数据拷贝。但是如果我们处于HDFS Federation模式的话,我们就能很好的利用这个功能了。
对比同样为数据拷贝工具distcp来说,目前fastcopy的实现比较的简单,在实际使用的时候存在着一些不足:
所以一个绝妙的改进想法自然的衍生而来:能否让Distcp的单个文件拷贝步骤通过fastcopy来做,同时保留distcp原来的功能行为,例如diff,update等功能。文件的拷贝走fastcopy的方式将会大大缩短distcp job的执行时间。
于是,我们进行了distcp支持fastcopy的改造。
这部分的改造其实没有想象中那么复杂,改造关键点在于以下两点:
OK,基于以上两点的改造原则,我们做了下面的主要改动。
改动类RetriableFileCopyCommand的copyToFile方法
private long copyToFile(Path targetPath, FileSystem targetFS,
FileStatus sourceFileStatus, long sourceOffset, Mapper.Context context,
EnumSet<FileAttribute> fileAttributes, final FileChecksum sourceChecksum)
throws IOException {
final OutputStream outStream;
if (action == FileAction.OVERWRITE) {
final short repl = getReplicationFactor(fileAttributes, sourceFileStatus,
targetFS, targetPath);
final long blockSize = getBlockSize(fileAttributes, sourceFileStatus,
targetFS, targetPath);
FSDataOutputStream out = null;
if(useFastCopy(context.getConfiguration())) {
FastCopy fastCopy = null;
try {
// 使用fastcopy的方式做拷贝
fastCopy = new FastCopy(context.getConfiguration(), sourceFileStatus.getPath(),
targetPath, EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE));
return fastCopy.copy();
} catch (UnsupportedFastCopyException e) {
LOG.warn("Fast copy on the file " + sourceFileStatus.getPath()
+ " is not supported, will fall back to use copy",
e);
} catch (Exception e) {
throw new IOException("Failed to fast copy the file " + sourceFileStatus.getPath(), e);
} finally {
if (fastCopy != null) {
try {
fastCopy.shutdown();
} catch (Throwable t) {
LOG.warn("Failed to shut down fast copy", t);
}
}
}
}
// 如果fastcopy失败,则退回执行原始数据拷贝逻辑
FsPermission permission = FsPermission.getFileDefault().applyUMask(
FsPermission.getUMask(targetFS.getConf()));
out = targetFS.create(targetPath, permission,
Distcp参数使用检查校验方法DistCpOptions的validate方法,
public void validate(DistCpOptionSwitch option, boolean value) {
boolean syncFolder = (option == DistCpOptionSwitch.SYNC_FOLDERS ?
value : this.syncFolder);
boolean overwrite = (option == DistCpOptionSwitch.OVERWRITE ?
value : this.overwrite);
...
boolean shouldVerboseLog = (option == DistCpOptionSwitch.VERBOSE_LOG ?
value : this.verboseLog);
if (syncFolder && atomicCommit) {
throw new IllegalArgumentException("Atomic commit can't be used with " +
"sync folder or overwrite options");
}
...
// 新增fastcopy option使用条件判断
if (fastCopy && append) {
throw new IllegalArgumentException("Fast copy can't be used with append");
}
if (fastCopy && useDiff) {
throw new IllegalArgumentException("Fast copy can't be used with diff");
}
}
做完上述改动后,笔者分别进行了以下两种情况的性能测试:
第一种情况,用于Hadoop自带ut造出总量10T大小的文件,命令使用如下
/apache/hadoop/bin/hadoop jar hadoop-*test*.jar TestDFSIO -write -nrFiles 10000 -fileSize 1000
然后分别使用原生distcp拷贝以及distcp+fastcopy的模式以1w个task的初始设定去拷贝,原生拷贝时间在15分钟到20分钟之间,换成fastcopy的方式,时间缩短到只有2~3分钟的级别。而且在distcp+fastcopy拷贝的过程中,这2分钟一部分时间是花在了等待map task调度执行的阶段,实际单个map的执行只有几秒钟的时间。
第二种情况,模拟百万级文件数量,测试distcp+fastcopy的性能极限,笔者创建出了100w的文件量,命令如下:
/apache/hadoop/bin/hadoop jar hadoop-*test*.jar TestDFSIO -write -nrFiles 1000000 -fileSize 10
随后笔者用带fastcopy的distcopy去拷贝,大约花了12分钟的时间,这个数据只是在笔者设定最大1w个task执行情况下的结果,并不是实际的极限值。
凡是硬币都有正方面,尽管distcp结合fastcopy能给我们带来巨大的性能提升,但鉴于其采用的是hardlink这种虚拷贝的方式,如有操作不当,会存在破坏数据准确性的情况。
我们不能允许同时在source,target同时修改数据文件,这会造成数据混乱的情况。同时我们建议在拷贝完数据后,确认数据无误后,尽快将source端的数据进行删除。但如果我们需要保持source端的数据,那么还是得使用原生distcp拷贝的方式。
另外在拷贝实际数据时,笔者不建议通过初试建snapshot的方式,然后去拷贝snapshot目录。因为数据在append操作的情况会修改snapshot下维护的同样的block块。然后snapshot上记录的文件长度和实际block的长度将会不一致,此时采用fastcopy采用的hardlink的时候会发现数据length不一致导致的拷贝失败的情况。原生基于snapshot拷贝只会拷贝出对应snapshot记录长度size的block数据,所以不会有问题。
以上就是本文所阐述的主要内容,主要给大家介绍了一种在HDFS Federation模式下可以进行加速拷贝的distcp+fastcopy的数据拷贝方式,以及此方法存在的一些局限点。
[1].https://issues.apache.org/jira/browse/HDFS-2139