如果您的应用程序需要 Hadoop 本地 FileSystem 支持而不依赖 Winutils,请将最新的 com.globalmentor:hadoop-bare-naked-local-fs 库导入您的项目,例如在 v0.1.0 的 Maven 中:
<dependency>
<groupId>com.globalmentor</groupId>
<artifactId>hadoop-bare-naked-local-fs</artifactId>
<version>0.1.0</version>
</dependency>
- 然后指定您希望将裸本地文件系统实现
com.globalmentor.apache.hadoop.fs.BareLocalFileSystem 用于file 方案。 (BareLocalFileSystem 内部使用 NakedLocalFileSystem。)下面的示例在 Java 中为 Spark 执行此操作:
SparkSession spark = SparkSession.builder().appName("Foo Bar").master("local").getOrCreate();
spark.sparkContext().hadoopConfiguration().setClass("fs.file.impl", BareLocalFileSystem.class, FileSystem.class);
请注意,您可能仍会收到“HADOOP_HOME 和 hadoop.home.dir 未设置”和“未找到 winutils.exe”的警告。这是因为 Winutils 组件渗透到 Hadoop 代码中并且在低级别进行硬编码,在类加载时静态执行,即使对于与文件访问完全无关的代码也是如此。更多解释可以在project page on GitHub 上找到。另见HADOOP-13223: winutils.exe is a bug nexus and should be killed with an axe.)
Spark 如何使用 Hadoop FileSystem
Spark 使用 Hadoop FileSystem API 作为将输出写入磁盘的一种方式,例如用于本地 CSV 或 JSON 输出。它引入了整个 Hadoop 客户端库(当前为 org.apache.hadoop:hadoop-client-api:3.3.2),其中包含各种 FileSystem 实现。这些实现使用 Java service loader framework 为多个方案自动注册多个实现,其中包括:
org.apache.hadoop.fs.LocalFileSystem
org.apache.hadoop.fs.viewfs.ViewFileSystem
org.apache.hadoop.fs.http.HttpFileSystem
org.apache.hadoop.fs.http.HttpsFileSystem
org.apache.hadoop.hdfs.DistributedFileSystem
- …
这些文件系统中的每一个都指示它支持哪种方案。特别是org.apache.hadoop.fs.LocalFileSystem 表示它支持file 方案,并且默认使用它来访问本地文件系统。它又在内部使用org.apache.hadoop.fs.RawLocalFileSystem,这是最终负责需要Winutils 的FileSystem 实现。
但是可以覆盖 Hadoop 配置并指定另一个 FileSystem 实现。 Spark 在 org.apache.spark.sql.internal.SessionState.newHadoopConf(…) 中为 Hadoop 创建了一个特殊的 Configuration,最终结合所有源 core-default.xml、core-site.xml、mapred-default.xml、mapred-site.xml、yarn-default.xml、yarn-site.xml 和 __spark_hadoop_conf__.xml,如果有的话当下。然后 Hadoop 的 FileSystem.getFileSystemClass(String scheme, Configuration conf) 查找要使用的 FileSystem 实现,方法是查找 fs.${scheme}.impl 形式的方案配置(在本例中为 file)(即在本例中为 fs.file.impl)。
因此,如果您想指定要使用的另一个本地文件系统实现,您需要以某种方式将fs.file.impl 放入配置中。如果您以编程方式访问 Spark,则无需创建本地配置文件,而是可以通过 Spark 会话进行设置,如简介中所述。
为什么选择 Winutils
Hadoop FileSystem API 在很大程度上假设一个 *nix 文件系统。当前 Hadoop 本地 FileSystem 实现使用本机 *nix 库或打开 shell 进程并直接运行 *nix 命令。当前 Windows 的本地 FileSystem 实现与一个巨大的杂物一起出现:一组名为 Winutils 的二进制工件,由 Hadoop 贡献者创建,在 Windows 上提供了一个特殊的后门子系统,Hadoop 可以访问该后门子系统,而不是 *nix 库和外壳命令。 (见HADOOP-13223: winutils.exe is a bug nexus and should be killed with an axe.)
然而,Winutils 的检测和所需支持实际上是在 Hadoop 中的低级硬编码——即使在与文件系统无关的代码中!例如,当 Spark 启动时,即使是 Hadoop 代码中的一个简单的 Configuration 初始化也会调用 StringUtils.equalsIgnoreCase("true", valueString),而 StringUtils 类有一个对 Shell 的静态引用,它有一个静态初始化块,用于查找 Winutils 并生成如果未找到,则发出警告。 ?♂️(事实上,这就是警告的来源,而这正是 Stack Overflow 问题的最初动机。)
在没有 Winutils 的情况下使用 FileSystem 的解决方法
不管警告如何,更大的问题是让FileSystem 在不需要 Winutils 的情况下工作。自相矛盾的是,这既是一个比最初出现的更简单又复杂得多的项目。一方面,使用更新的 Java API 调用而不是 Winutils 来访问本地文件系统并不难;我已经在GlobalMentor Hadoop Bare Naked Local FileSystem 中做到了。但是完全淘汰 Winutils 更加复杂和困难。当前的LocalFileSystem 和RawLocalFileSystem 实现是随意发展的,中间实现的功能散布在各处,用于记录不良的极端情况的特殊情况代码,以及渗透到设计本身的实现特定假设。
上面已经给出了 Configuration 访问 Shell 并尝试在启动期间加载类时引入 Winutils 的示例。在FileSystem 级别,与 Winutils 相关的逻辑不包含在 RawLocalFileSystem 中,这将允许它轻松被覆盖,而是依赖于静态 FileUtil 类,它就像一个单独的文件系统实现,它依赖于Winutils 并且不能修改。例如这里是需要更新的FileUtil 代码,不幸的是独立于FileSystem 实现:
public static String readLink(File f) {
/* NB: Use readSymbolicLink in java.nio.file.Path once available. Could
* use getCanonicalPath in File to get the target of the symlink but that
* does not indicate if the given path refers to a symlink.
*/
…
try {
return Shell.execCommand(
Shell.getReadlinkCommand(f.toString())).trim();
} catch (IOException x) {
return "";
}
显然有许多方法的“基于Stat 的新实现”,但RawLocalFileSystem 反而使用了不推荐使用的实现,例如DeprecatedRawLocalFileStatus,它充满了变通方法和特殊情况,是包私有的,所以它不能可以被子类访问,但由于HADOOP-9652 而不能被删除。 useDeprecatedFileStatus 开关是硬编码的,因此它不能被子类修改,从而强制重新实现它所接触的所有内容。换句话说,即使是新的、不那么繁琐的方法在代码中也被关闭了,已经有好几年了,而且似乎没有人在意。
概括
总而言之,Winutils 在整个代码中都在低级别进行了硬编码,即使在与文件访问无关的逻辑中也是如此,并且当前的实现是由硬编码标志打开或关闭的已弃用和未弃用代码的大杂烩。当新更改出现错误时到位。这是一团糟,多年来一直如此。没有人真正关心,而是继续在不稳定的沙地上(ViewFs 任何人?)而不是回去修复基础。如果 Hadoop 甚至无法修复大量已弃用的文件访问代码合并在一个地方,您认为他们会修复在低级别渗透到多个类的 Winutils 混杂吗?
我没有屏住呼吸。相反,我会满足于我编写的解决方法,它通过 Java API 写入文件系统,尽可能绕过 Winutils。