如何在Hadoop / Spark中重命名大量文件?

时间:2014-07-08 13:37:30

标签: hadoop parallel-processing bigdata apache-spark

我有一个包含+100,000个文件的输入文件夹。

我想对它们进行批量操作,即以某种方式重命名所有操作,或根据每个文件名称中的信息将它们移动到新路径。

我想使用Spark来做到这一点,但不幸的是,当我尝试下面这段代码时:

 final org.apache.hadoop.fs.FileSystem ghfs = org.apache.hadoop.fs.FileSystem.get(new java.net.URI(args[0]), new org.apache.hadoop.conf.Configuration());
        org.apache.hadoop.fs.FileStatus[] paths = ghfs.listStatus(new org.apache.hadoop.fs.Path(args[0]));
        List<String> pathsList = new ArrayList<>();
        for (FileStatus path : paths) {
            pathsList.add(path.getPath().toString());
        }
        JavaRDD<String> rddPaths = sc.parallelize(pathsList);

        rddPaths.foreach(new VoidFunction<String>() {
            @Override
            public void call(String path) throws Exception {
                Path origPath = new Path(path);
                Path newPath = new Path(path.replace("taboola","customer"));
                ghfs.rename(origPath,newPath);
            }
        });

我得到一个错误,hadoop.fs.FileSystem不可序列化(因此可能无法在并行操作中使用)

我知道如何解决它或以其他方式完成它吗?

3 个答案:

答案 0 :(得分:4)

您还需要FileSystem.get内的VoidFunction

驱动程序需要FileSystem来获取文件列表,但每个worker都需要一个FileSystem来进行重命名。驱动程序无法将其FileSystem传递给worker,因为它不是Serializable。但工人可以很好地获得他们自己的FileSystem。

在Scala API中,您可以使用RDD.foreachPartition编写代码,每个分区只执行一次FileSystem.get,而不是每行一次。它也可能在Java API中提供。

答案 1 :(得分:4)

问题是您正在尝试序列化ghfs对象。如果您使用mapPartitions并在每个分区中重新创建ghfs对象,您只需进行一些小的更改就可以运行代码。

答案 2 :(得分:3)

我建议只在非地图缩小上下文中使用文件系统类重命名它们(仅在驱动程序中),重命名100k文件并不是什么大不了的事情,它是它太慢了,那么你可以尝试多线程。做一些像

这样的事情
FileSystem fileSystem = new Path("").getFileSystem(new Configuration());
File [] files =  FileUtil.listFiles(directory)
for (File file : files) {
    fileSystem.rename(new Path(file.getAbsolutePath()),new Path("renamed"));
}

Btw你在spark中遇到的错误是因为spark需要它用来实现Serializable的对象,而FileSystem则没有。


我无法证实这一点,但似乎HDFS中的每个重命名都会涉及NameNode,因为它跟踪文件的完整目录结构和节点位置(confirmation link),这意味着它可以&#39;并行有效地完成。根据{{​​3}}重命名是一个仅元数据操作,因此它应该非常快速地串行运行。