我有一个包含+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不可序列化(因此可能无法在并行操作中使用)
我知道如何解决它或以其他方式完成它吗?
答案 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}}重命名是一个仅元数据操作,因此它应该非常快速地串行运行。