Apache Spark SQL 资源管理系列:LIST FILE 语法详解与源码实现
2026/9/19 18:41:06 网站建设 项目流程

Apache Spark SQL 资源管理系列:LIST FILE 语法详解与源码实现

【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark

LIST FILE是 Apache Spark SQL 中用于查看已添加文件资源(File Resource)的核心语法,它与ADD FILE配套使用,帮助开发者确认文件、目录是否成功进入当前会话的可用资源清单。读完本文,你将掌握LIST FILE的完整语法、带参过滤与不带参全量列举两种用法、底层执行原理,以及它与LIST JARLIST ARCHIVE在行为上的关键差异。

概述:Spark SQL 的资源管理语句家族

在 Spark SQL 中,运行任务前往往需要将辅助文件(如配置文件、UDF 依赖的本地数据)、JAR 包或归档压缩包分发给所有执行节点。为此 Spark 提供了一组面向会话(Session)级资源管理的 SQL 语句,它们在 SQL 语法上属于manageResource规则(见 SqlBaseParser.g4):

语句作用
ADD FILE/ADD FILES向会话添加文件或目录
ADD JAR/ADD JARS向会话添加 JAR 包(用于 UDF 或序列化器)
ADD ARCHIVE/ADD ARCHIVES向会话添加归档压缩包(.zip/.tar/.tar.gz/.tgz/.jar)
LIST FILE/LIST FILES列出已添加的文件资源
LIST JAR/LIST JARS列出已添加的 JAR 资源
LIST ARCHIVE/LIST ARCHIVES列出已添加的归档资源

本文聚焦LIST FILE。官方参考文档位于 sql-ref-syntax-aux-resource-mgmt-list-file.md,下文将结合其语法定义与仓库源码逐层展开。

LIST FILE 语法

LIST FILE的完整语法定义如下:

LIST { FILE | FILES } file_name [ ... ]

要点说明:

  • FILEFILES关键字等价,可互换使用;
  • file_name为可选参数,支持传入一个或多个文件路径;
  • 当不携带任何路径参数时,LIST FILE列出当前会话中所有已添加的文件资源
  • 当携带路径参数时,LIST FILE只返回参数中确实已添加成功的那些资源,未添加过的路径不会出现在结果集中。

注意:该语法对LIST FILE;(不带参数)与LIST FILE /path/a /path/b;(带参数)两种形态都有效。语法定义中file_name [ ... ]的方括号表示参数可省略,省略时即等价于"列出全部"。

使用示例

以下示例完整取自官方文档 sql-ref-syntax-aux-resource-mgmt-list-file.md,并在其基础上补充了注释说明。

示例一:不带参数,列出全部已添加文件

ADD FILE /tmp/test; ADD FILE /tmp/test_2; LIST FILE; -- output for LIST FILE file:/private/tmp/test file:/private/tmp/test_2

先后用ADD FILE添加两个文件后,直接执行LIST FILE;即可看到全部已注册资源的路径列表。输出中的file:前缀表明这些资源以file://URI 形式登记在资源清单中。

示例二:带参数,按路径过滤

LIST FILE /tmp/test /some/random/file /another/random/file; --output file:/private/tmp/test

传入多个路径时,Spark 会逐一对参数做 URI 规范化并判断其是否已加入资源清单,最终只输出命中的项。示例中仅/tmp/test之前被添加过,其余两个路径未命中,因此结果集中只有一行。

补充示例:使用引号包裹含空格路径

结合 ADD FILE 文档,路径支持使用单引号或双引号包裹,尤其适用于包含空格的路径:

ADD FILE "/path with space/abc.txt"; LIST FILE; -- 输出中包含 file:/path with space/abc.txt(路径空格被保留)

语法解析器在visitManageResource中会通过正则抽取字符串字面量并调用unescapeSQLString还原真实路径(见 SparkSqlParser.scala),因此带引号与不带引号的写法均被支持。

参数说明

LIST FILE的参数语义如下:

参数含义是否必填
file_name待查询的文件或目录路径,支持本地路径、file://、HDFS 等 Hadoop 文件系统路径,以及http(s)://ftp://等远程 URI否(省略时列出全部)

从源码实现看,file_name在查询时会先经过Utils.resolveURI(f)解析为 URI,再针对nulllocalfile三种 scheme 使用new File(uri).getCanonicalFile.toURI.toString做规范化(消除相对路径与符号链接差异),随后与SparkContext.listFiles()返回的已添加资源集合做精确匹配(见下文源码解析)。这意味着即使你添加时与查询时书写路径的格式略有差异,只要规范化后指向同一文件,通常也能命中。

源码级原理:LIST FILE 是如何执行的

1. 语法解析阶段

LIST FILE由 ANTLR 文法规则op=(ADD | LIST) simpleIdentifier .*? #manageResource捕获(见 SqlBaseParser.g4),随后SparkSqlParser.visitManageResource根据op类型分发:

  • opLIST且资源类型为files/file时:若携带路径则构建ListFilesCommand(maybePaths),否则构建无参的ListFilesCommand()(见 SparkSqlParser.scala)。

解析行为有对应的单元测试覆盖,例如LIST FILE abc.txt应解析为ListFilesCommand(Array("abc.txt")),多参数、带引号形式均有断言(见 SparkSqlParserSuite.scala)。

2. 执行阶段

LIST FILE最终落地的执行类是ListFilesCommand(见 resources.scala),其核心逻辑:

case class ListFilesCommand(files: Seq[String] = Seq.empty[String]) extends LeafRunnableCommand { override val output: Seq[Attribute] = { AttributeReference("Results", StringType, nullable = false)() :: Nil } override def run(sparkSession: SparkSession): Seq[Row] = { val fileList = sparkSession.sparkContext.listFiles() if (files.nonEmpty) { files.map { f => val uri = Utils.resolveURI(f) uri.getScheme match { case null | "local" | "file" => new File(uri).getCanonicalFile.toURI.toString case _ => f } }.collect { case f if fileList.contains(f) => f }.map(Row(_)) } else { fileList.map(Row(_)) } } }

关键行为可归纳为三点:

  1. 结果列固定为单列Results,类型为不可为空的字符串,每行对应一个已添加资源路径;
  2. 带参模式:先对每个输入路径做 URI 解析与本地路径规范化,再与listFiles()返回的集合做contains精确匹配,只保留命中的路径——这正是示例二中"未添加过的文件不出现在结果集"的原因;
  3. 无参模式:直接返回listFiles()的全部元素。

3. 底层数据来源:SparkContext 的资源登记表

listFiles()的实现位于 SparkContext.scala:

def listFiles(): Seq[String] = allAddedFiles.keySet.toSeq

其中allAddedFilesaddedFiles各任务集条目的展平合并结果(见 SparkContext.scala),本质是一个以资源 URI 为 key 的映射集合。ADD FILE调用addFile(path, recursive)时(见 SparkContext.scala),会:

  • 解析路径 scheme,对http/https/ftp/spark之外的 scheme 检查文件系统状态;
  • 将本地文件通过 RPC 文件服务器上传后登记 URI,远程 URI 则直接登记原始地址;
  • 同一路径只登记一次,重复添加会被忽略(注释明确说明 "A path can be added only once")。

因此LIST FILE展示的正是这一份会话级资源登记表,二者天然一致。

4. 目录递归添加与兼容开关

ADD FILE支持一次性添加目录(recursive为真),该递归行为由ListFilesCommand的兄弟类AddFilesCommand控制:

val recursive = !sparkSession.sessionState.conf.addSingleFileInAddFile paths.foreach(sparkSession.sparkContext.addFile(_, recursive))

(见 resources.scala)

是否允许目录递归由内部配置项spark.sql.legacy.addSingleFileInAddFile决定(见 SQLConf.scala):

  • 默认值false:允许通过ADD FILE添加目录,Spark 会递归分发目录内文件;
  • 设为true:恢复旧版行为,一次只能添加单个文件。

LIST FILE对目录同样有效——目录以规范化后的 URI 登记后,会被如实列在结果集中。相关行为在DDLSuite中有集成测试覆盖(见 DDLSuite.scala)。

LIST FILE 与 LIST JAR / LIST ARCHIVE 的差异

ListFilesCommand与同文件中的ListJarsCommandListArchivesCommand共享相似的"先取全集、再按参数过滤"模式,但过滤逻辑有细微差别(见 resources.scala):

语句无参行为带参过滤方式
LIST FILE返回全部已添加文件 URI路径规范化后与资源集合精确匹配contains全字符串比对)
LIST JAR返回全部已添加 JAR URI取参数的文件名部分(按/切分取最后一段)与 JAR 集合做包含匹配contains子串比对)
LIST ARCHIVE返回全部已添加归档 URILIST JAR,按文件名做包含匹配

由此可以总结出实践要点:

  • LIST FILE而言,参数必须能规范化到与登记时一致的完整 URI才会命中,因此查询时建议使用与ADD FILE时一致的路径写法(或直接省略参数列出全部再自行核对);
  • LIST JAR/LIST ARCHIVE而言,即使只记得文件名(例如my-lib.jar),也能通过子串匹配定位到已添加的完整路径。

典型使用场景

  1. 调试资源是否生效:执行ADD FILE后立刻执行LIST FILE;,确认文件(尤其是配置文件、字典数据)是否已进入资源清单,避免任务运行时出现"文件不存在"类错误;
  2. 多文件场景下的确认:批量添加后按路径过滤查询,快速确认个别文件是否添加成功,无需人工翻看全量列表;
  3. 配合 UDF/自定义逻辑使用ADD FILE添加的文件可在任务中通过SparkFiles.get(fileName)定位下载位置,LIST FILE则用于审计这份分发型资源清单。

相关语句

  • ADD FILE —— 向会话添加文件或目录,是LIST FILE的前置操作
  • ADD JAR —— 向会话添加 JAR 包
  • ADD ARCHIVE —— 向会话添加归档压缩包
  • LIST JAR —— 列出已添加的 JAR 资源
  • LIST ARCHIVE —— 列出已添加的归档资源

以上语句共同构成 Spark SQL 完整的会话级资源管理能力,LIST FILE在其中承担文件资源"审计员"的角色,是排查资源分发问题时的第一排查工具。

【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询