如果不使用 Hadoop,为什么 Windows 上的 Spark 会出现所有这些“HADOOP_HOME”和 Winutils 错误?

Gar*_*son 8 java hadoop apache-spark

我正在使用 Java 11 在 Windows 10 上运行 Spark 3.3.0。我没有使用 Hadoop。每次我运行某些东西时,都会出现这样的错误:

java.lang.RuntimeException: java.io.FileNotFoundException: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset. -see https://wiki.apache.org/hadoop/WindowsProblems
    at org.apache.hadoop.util.Shell.getWinUtilsPath(Shell.java:735)
    at org.apache.hadoop.util.Shell.getSetPermissionCommand(Shell.java:270)
    at org.apache.hadoop.util.Shell.getSetPermissionCommand(Shell.java:286)
    at org.apache.hadoop.fs.RawLocalFileSystem.setPermission(RawLocalFileSystem.java:978)
Run Code Online (Sandbox Code Playgroud)

首先,甚至错误消息中的链接https://wiki.apache.org/hadoop/WindowsProblems也已损坏。更新链接显然是https://cwiki.apache.org/confluence/display/HADOOP2/WindowsProblems,它基本上说 Hadoop 需要 Winutils。但我没有使用 Hadoop。我只是使用 Spark 在本地处理一些 CSV 文件。

其次,我希望我的项目使用 Maven 构建并使用纯 Java 运行,而不需要用户安装一些第三方软件。如果需要安装这个 Winutil 东西,它应该包含在某些 Maven 依赖项中。

如果我不使用 Hadoop,为什么需要所有这些 Hadoop/Winutils 东西?我该如何解决它,以便我的项目将在 Maven 中构建并像 Java 项目一样使用纯 Java 运行?

Gar*_*son 11

长话短说

\n

我创建了 Hadoop 的本地实现,FileSystem它绕过 Windows 上的 Winutils(实际上应该可以在任何 Java 平台上工作)。GlobalMentor Hadoop Bare Naked Local FileSystem源代码可在 GitHub 上获取,并且可以从Maven Central指定为依赖项依赖项。

\n
    \n
  1. 如果您有一个应用程序需要 Hadoop 本地FileSystem支持而不依赖 Winutils,请导入最新的com.globalmentor:hadoop-bare-naked-local-fs库导入到您的项目中,例如在 v0.1.0 的 Maven 中:
  2. \n
\n
<dependency>\n  <groupId>com.globalmentor</groupId>\n  <artifactId>hadoop-bare-naked-local-fs</artifactId>\n  <version>0.1.0</version>\n</dependency>\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 然后指定您要com.globalmentor.apache.hadoop.fs.BareLocalFileSystem为该file方案使用裸本地文件系统实现。(BareLocalFileSystem内部使用NakedLocalFileSystem。)以下示例针对 Java 中的 Spark 执行此操作:
  2. \n
\n
SparkSession spark = SparkSession.builder().appName("Foo Bar").master("local").getOrCreate();\nspark.sparkContext().hadoopConfiguration().setClass("fs.file.impl", BareLocalFileSystem.class, FileSystem.class);\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,您可能仍会收到“HADOOP_HOME 和 hadoop.home.dir 未设置”和“未找到 winutils.exe”的警告。这是因为 Winutils kludge 渗透到 Hadoop 代码中,并且在低级别进行硬编码,在类加载时静态执行,即使对于与文件访问完全无关的代码也是如此。更多解释可以在GitHub 上的项目页面上找到。另请参见HADOOP-13223:winutils.exe 是一个错误关联,应该用斧头杀死。

\n

Spark 如何使用 HadoopFileSystem

\n

Spark 使用 Hadoop FileSystemAPI 作为将输出写入磁盘的方法,例如本地 CSV 或 JSON 输出。它引入了整个 Hadoop 客户端库(当前org.apache.hadoop:hadoop-client-api:3.3.2),其中包含各种FileSystem实现。这些实现使用 Java服务加载器框架自动注册多种方案的多种实现,其中包括:

\n
    \n
  • org.apache.hadoop.fs.LocalFileSystem
  • \n
  • org.apache.hadoop.fs.viewfs.ViewFileSystem
  • \n
  • org.apache.hadoop.fs.http.HttpFileSystem
  • \n
  • org.apache.hadoop.fs.http.HttpsFileSystem
  • \n
  • org.apache.hadoop.hdfs.DistributedFileSystem
  • \n
  • \xe2\x80\xa6
  • \n
\n

这些文件系统中的每一个都表明它支持哪种方案。特别org.apache.hadoop.fs.LocalFileSystem表示支持该file方案,并且默认使用它来访问本地文件系统。它反过来在org.apache.hadoop.fs.RawLocalFileSystem内部使用,这是FileSystem最终负责需要 Winutils 的实现。

\n

但可以覆盖 Hadoop 配置并指定另一个FileSystem实现。ConfigurationSpark为 Hadoop创建了一个特殊功能,org.apache.spark.sql.internal.SessionState.newHadoopConf(\xe2\x80\xa6)最终组合所有源core-default.xmlcore-site.xmlmapred-default.xmlmapred-site.xmlyarn-default.xmlyarn-site.xml__spark_hadoop_conf__.xml(如果存在)。然后,Hadoop 通过在表单(即本例中)中查找方案(本例中)的配置来查找要使用的FileSystem.getFileSystemClass(String scheme, Configuration conf)实现。FileSystemfilefs.${scheme}.implfs.file.impl

\n

因此,如果您想指定要使用的另一个本地文件系统实现,您将需要以某种方式获取fs.file.impl配置。如果您以编程方式访问 Spark,则可以通过 Spark 会话进行设置,而不是创建本地配置文件,如简介中所述。

\n

为什么选择 Winutils

\n

Hadoop FileSystemAPI 很大程度上采用 *nix 文件系统。当前的 Hadoop 本地FileSystem实现使用本机 *nix 库或打开 shell 进程并直接运行 *nix 命令。当前FileSystemWindows 的本地实现存在巨大的缺陷:由 Hadoop 贡献者创建的一组名为Winutils的二进制工件,在 Windows 上提供了一个特殊的后门子系统,Hadoop 可以访问该后门子系统,而不是 *nix 库和 shell 命令。(请参阅HADOOP-13223:winutils.exe 是一个错误关联,应该用斧头杀死。

\n

然而,Winutils 的检测和所需支持实际上是在 Hadoop 的低层\xe2\x80\x94 中硬编码的,甚至是与文件系统无关的代码!例如,当 Spark 启动时,即使是ConfigurationHadoop 代码中的简单初始化也会调用StringUtils.equalsIgnoreCase("true", valueString),并且该类StringUtils具有对Shell,该类具有一个静态初始化块,用于查找 Winutils 并在未找到时生成警告。\xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f (事实上,这是警告的来源,也是这个堆栈溢出问题的最初动机。)

\n

FileSystem不使用 Winutils 的解决方法

\n

不管警告如何,更大的问题是FileSystem无需 Winutils 即可开始工作。矛盾的是,这既是一个比最初看起来更简单的项目,也是一个复杂得多的项目。一方面,使用更新的 Java API 调用而不是 Winutils 来访问本地文件系统并不太困难;我已经在GlobalMentor Hadoop Bare Naked Local FileSystem中做到了这一点。但完全清除 Winutils 则要复杂和困难得多。当前LocalFileSystemRawLocalFileSystem实现的发展是随意的,中间实现的功能分散,用于记录不详的极端情况的特殊情况代码,以及渗透到设计本身的特定于实现的假设。

\n

Configuration上面已经给出了在启动期间类加载时访问Shell并尝试引入 Winutils的示例。在级别上FileSystem,与 Winutils 相关的逻辑不包含在 中RawLocalFileSystem,这使得它可以轻松地被覆盖,而是依赖于静态FileUtil类,该类就像依赖于 Winutils 并且无法修改的单独文件系统实现。例如,这里是FileUtil需要更新的代码,不幸的是与FileSystem实现无关:

\n
  public static String readLink(File f) {\n    /* NB: Use readSymbolicLink in java.nio.file.Path once available. Could\n     * use getCanonicalPath in File to get the target of the symlink but that\n     * does not indicate if the given path refers to a symlink.\n     */\n    \xe2\x80\xa6\n    try {\n      return Shell.execCommand(\n          Shell.getReadlinkCommand(f.toString())).trim();\n    } catch (IOException x) {\n      return "";\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

Stat显然,许多方法有一个“基于新的实现”,但RawLocalFileSystem改为使用已弃用的实现,例如DeprecatedRawLocalFileStatus,它充满了变通方法和特殊情况,是包私有的,因此它不能被子类访问,但可以\不会因为HADOOP-9652 而被删除。这useDeprecatedFileStatus开关是硬编码的,因此它不能被子类修改,从而强制重新实现它所涉及的所有内容。换句话说,即使是新的、不那么笨拙的方法在代码中也被关闭了,已经很多年了,而且似乎没有人在意它。

\n

概括

\n

总之,Winutils 在整个代码中都在底层进行了硬编码,甚至在与文件访问无关的逻辑中也是如此,并且当前的实现是已弃用和未弃用代码的大杂烩,这些代码通过硬编码标志打开或关闭。当新的更改出现错误时落实到位。这是一团糟,而且多年来一直如此。没有人真正关心,而是继续在不稳定的沙子上建造(ViewFs有人吗?),而不是回去修复地基。如果 Hadoop 甚至无法修复整合在一个地方的大量已弃用的文件访问代码,您认为他们会在低级别修复渗透到多个类的 Winutils 组合吗?

\n

我没有屏住呼吸。相反,我会对我编写的解决方法感到满意,该解决方法通过 Java API 写入文件系统,在最常见的用例中绕过 Winutils(写入本地文件系统而不使用符号链接,也不需要粘滞位权限),这足以让 Spark 访问 Windows 上的本地文件系统。

\n