1. 项目概述:为什么“读取文件”是Spark入门第一道坎
刚接触Spark的人,十有八九卡在“怎么把数据搞进来”这一步。不是写不出代码,而是写出来跑不通——本地路径报FileNotFoundException,HDFS路径提示No FileSystem for scheme: hdfs,或者明明文件存在却返回空RDD。我带过不少刚从MapReduce或Pandas转过来的开发者,他们常以为“读个文件而已,能有多难”,结果在spark.read.text()这一行反复调试两小时,最后发现只是少配了一个JAR包,或者路径里多打了一个斜杠。这背后根本不是语法问题,而是Spark的数据加载机制和运行环境耦合度极高:它不直接操作磁盘,而是通过抽象的FileSystem接口委托给底层实现;本地模式用的是file://协议,集群模式默认走hdfs://,而这两套体系的认证、权限、路径解析逻辑完全不同。更关键的是,Spark的“本地”概念具有双重性——对Driver进程而言,/data/input.txt是本机文件系统路径;但对Executor而言,这个路径可能根本不存在,除非你提前把文件分发到所有节点。所以标题里这个看似简单的“读取本地与HDFS文件”,实际覆盖了Spark最核心的三个底层模块:DataSourceV2加载器、FileSystem抽象层、以及TaskScheduler对资源位置的感知逻辑。本文不讲API列表,而是带你从一次真实的报错出发,还原整个加载链路:从spark.read.csv("hdfs://nn:8020/user/data/test.csv")敲下回车开始,Spark内部究竟发生了什么?哪些环节可配置、哪些必须硬编码、哪些错误其实早有征兆?我会用某次线上任务失败的真实日志片段作为线索,逐层拆解Driver端的元数据解析、Executor端的文件切片策略、以及HDFS客户端连接池的初始化陷阱。适合正在搭建第一个Spark作业、被路径问题困扰超过30分钟,或者想真正理解“为什么Spark要这样设计”的从业者。
2. 核心设计思路:为什么不能只靠spark.read一行代码搞定
2.1 Spark文件加载的本质:三层抽象模型
很多人误以为spark.read.text()是直接打开文件读内容,实际上Spark构建了一套精密的三层抽象模型,每一层都决定了你能否成功读取:
第一层:DataSource抽象层
spark.read返回的DataFrameReader本质是DataSourceV2的门面。当你调用.csv()或.json()时,Spark并不立即解析文件,而是先根据后缀名匹配内置的FileSource(如TextDataSource、CsvDataSource),再由该Source决定如何将物理文件映射为逻辑Schema。比如读CSV时,如果未指定inferSchema=true,Spark会跳过首行扫描,直接按字符串列生成Schema——这解释了为什么有时读出来的数字列全是StringType。而HDFS路径和本地路径在此层完全透明,Spark只认path参数,不关心协议。第二层:FileSystem抽象层
真正区分“本地”和“HDFS”的是org.apache.hadoop.fs.FileSystem。Spark通过SparkContext.hadoopConfiguration获取Hadoop配置,再调用FileSystem.get(URI, conf)获取具体实现。这里埋着第一个坑:如果你在本地模式运行,hadoopConfiguration默认只加载core-default.xml,里面fs.defaultFS值是file:///,所以hdfs://nn:8020/xxx会被拒绝——因为没有注册hdfs协议处理器。解决方案不是改代码,而是补全Hadoop配置文件,让FileSystem能识别hdfsscheme。第三层:Executor资源定位层
最隐蔽的坑在这里。Driver解析完路径后,会生成InputSplit描述文件切片位置(如HDFS的Block ID范围),然后序列化发送给Executor。但Executor执行时,需要用自己的FileSystem实例去拉取数据。如果集群节点没配Hadoop客户端,或者core-site.xml里fs.defaultFS指向错误的NameNode,Executor就会报UnknownHostException。注意:这个错误和Driver端的错误完全不同,Driver可能成功解析路径,Executor却连NameNode都ping不通。
提示:判断问题在哪一层,看错误堆栈第一行。
java.io.FileNotFoundException大概率在DataSource层(路径不存在或权限不足);java.net.UnknownHostException在FileSystem层(DNS或配置问题);org.apache.spark.SparkException: Task not serializable则可能在Executor资源定位层(自定义函数引用了不可序列化对象)。
2.2 本地文件读取的三大陷阱与规避逻辑
本地文件看似简单,实则暗藏三重陷阱,每一种都会导致“明明文件在,就是读不到”:
陷阱一:Driver与Executor的“本地”语义割裂
在local[*]模式下,Driver和Executor同进程,file:///data/input.txt能正常读取。但一旦切换到yarn-client模式,Executor运行在YARN容器中,其“本地”指容器内的文件系统,而非Driver所在机器。此时若仍用绝对路径,Executor必然报错。解决方案是使用--files参数分发文件,或改用HDFS路径统一管理。陷阱二:Windows路径的反斜杠灾难
Windows用户常写spark.read.text("C:\data\test.txt"),结果报Invalid path。因为Java字符串中\t被解析为制表符。正确写法是"C:/data/test.txt"或"C:\\data\\test.txt"。更稳妥的做法是用Paths.get("C:", "data", "test.txt").toUri.toString()生成标准URI。陷阱三:相对路径的隐式依赖
spark.read.text("data/test.csv")在IDEA里能跑通,打包成JAR提交YARN就失败。因为相对路径基于JVM启动目录,而YARN容器的启动目录是随机的。必须明确指定file:///前缀,或改用--files分发后通过SparkFiles.get("test.csv")获取绝对路径。
我见过最典型的案例:某团队用Python脚本生成Spark作业,脚本里写spark.read.parquet("./output/"),本地测试完美,上线后所有任务失败。排查三天才发现,脚本在Docker容器里运行,./output/指向容器内一个空目录,而真实数据在宿主机/mnt/data/output/。最终方案是改用--conf spark.sql.warehouse.dir=hdfs://nn:8020/user/hive/warehouse,彻底放弃本地路径依赖。
2.3 HDFS读取的四重校验机制与配置优先级
HDFS路径读取失败,90%的问题出在配置链路上。Spark对HDFS的访问遵循严格的四重校验机制,且配置优先级逐级覆盖:
| 校验层级 | 配置来源 | 优先级 | 典型问题 |
|---|---|---|---|
| 1. 协议注册校验 | hadoop-common.jar中的META-INF/services/org.apache.hadoop.fs.FileSystem | 最高 | 缺少hadoop-hdfs.jar,hdfs协议未注册 |
| 2. NameNode地址校验 | core-site.xml中的fs.defaultFS | 高 | 值为file:///或hdfs://wrong-host:8020 |
| 3. 认证方式校验 | core-site.xml中的hadoop.security.authentication | 中 | Kerberos未启用但集群强制要求 |
| 4. 权限校验 | HDFS ACL与Linux文件权限 | 低 | 用户无/user/data目录的x权限 |
配置优先级规则是:代码中spark.conf.set("spark.hadoop.fs.defaultFS", "hdfs://nn:8020")>--conf参数 >core-site.xml> 默认值。但要注意,spark.conf.set()必须在SparkSession.builder()之后、任何read()操作之前调用,否则无效。某次我们遇到java.lang.RuntimeException: core-site.xml not found,查了半天发现是YARN容器里HADOOP_CONF_DIR环境变量没设,导致Spark找不到配置文件。最终在spark-submit命令里加了--conf spark.yarn.dist.files=$HADOOP_CONF_DIR/core-site.xml,$HADOOP_CONF_DIR/hdfs-site.xml才解决。
3. 核心细节解析:从代码到字节码的完整加载链路
3.1 本地文件读取的实操步骤与参数精解
以读取本地CSV文件为例,完整流程需覆盖五个关键环节,每个环节都有易错点:
第一步:确认Spark运行模式与路径协议
// 必须显式指定file://协议,避免相对路径歧义 val localPath = "file:///home/user/data/sales.csv" // 检查当前模式 println(s"Master: ${spark.sparkContext.master}") // local[*] or yarn注意:
file://后面必须是三个斜杠(file:///),前两个是协议分隔符,第三个是根目录。写成file://home/user/会解析为file://home主机上的/user/路径,导致UnknownHostException。
第二步:处理CSV头与Schema推断
val df = spark.read .option("header", "true") // 第一行作为列名,必须设为字符串"true" .option("inferSchema", "true") // 启用类型推断,耗时但必要 .option("delimiter", ",") // 显式指定分隔符,避免中文逗号干扰 .csv(localPath)inferSchema=true的原理是Spark先采样前100行(可通过spark.sql.files.maxPartitionBytes调整),逐列统计数据类型分布,再选择最宽泛的类型(如同时有"123"和"123.45",推断为DoubleType)。但采样行数太少会导致推断错误,建议生产环境显式定义Schema:
import org.apache.spark.sql.types._ val schema = StructType(Array( StructField("id", IntegerType, nullable = false), StructField("name", StringType, nullable = true), StructField("amount", DoubleType, nullable = false) )) val df = spark.read.schema(schema).csv(localPath)第三步:处理特殊字符与编码
中文CSV常因编码问题出现乱码。Spark默认用UTF-8,但Windows记事本保存的CSV可能是GBK。解决方案:
// 方案1:预处理文件(推荐) // 用iconv转换编码:iconv -f GBK -t UTF-8 sales.csv > sales_utf8.csv // 方案2:Spark中指定编码(仅Spark 3.0+支持) .option("encoding", "GBK")实测心得:
encoding参数在Spark 2.x中无效,必须用方案1。曾有个项目因忽略此点,导致订单金额列解析出?字符,财务对账差了几百万。
第四步:大文件分片优化
本地大文件(>1GB)读取慢?Spark默认按spark.sql.files.maxPartitionBytes=128MB切片,但本地文件无法像HDFS那样并行读取Block。优化方案:
// 增加分区数,提升并行度 spark.conf.set("spark.sql.files.maxPartitionBytes", "64MB") // 或手动重分区 val df = spark.read.csv(localPath).repartition(8)但注意:过度分区会增加Task调度开销,实测8-16个分区对10GB文件效果最佳。
第五步:错误处理与日志定位
当读取失败时,不要只看最后一行错误。关键日志在Driver端stderr中:
// 正确的日志线索 19/01/01 10:00:00 INFO FileSourceStrategy: Planning scan with bin packing 19/01/01 10:00:00 DEBUG TextBasedFileFormat: Listing file:///home/user/data/sales.csv // 如果这里报错,说明FileSystem层已失败 19/01/01 10:00:00 ERROR FileSourceStrategy: Failed to list files java.io.FileNotFoundException: File file:/home/user/data/sales.csv does not exist定位方法:在代码开头加spark.sparkContext.setLogLevel("DEBUG"),观察FileSourceStrategy日志。
3.2 HDFS文件读取的配置清单与验证脚本
HDFS读取不是配对hdfs://路径就行,必须完成五步验证。以下是我用过的标准化检查清单:
验证一:Hadoop客户端连通性
在Spark节点执行:
# 测试NameNode是否可达 hadoop fs -ls hdfs://nn:8020/ # 查看HDFS根目录,确认返回正常 # 若报错"Connection refused",检查NameNode是否运行、端口是否开放验证二:Spark配置完整性
确保core-site.xml和hdfs-site.xml在$SPARK_HOME/conf/或$HADOOP_CONF_DIR下,并包含:
<!-- core-site.xml --> <property> <name>fs.defaultFS</name> <value>hdfs://nn:8020</value> <!-- 必须与hadoop fs命令一致 --> </property> <!-- hdfs-site.xml --> <property> <name>dfs.namenode.http-address</name> <value>nn:50070</value> <!-- WebUI端口,用于调试 --> </property>验证三:JAR包依赖检查
Spark发行版默认不含HDFS客户端,需手动添加:
# 下载对应Hadoop版本的hadoop-client JAR # 例如Hadoop 3.2.1,下载 hadoop-client-3.2.1.jar # 放入 $SPARK_HOME/jars/ 目录 # 或提交时指定 --jars /path/to/hadoop-client-3.2.1.jar关键经验:JAR包版本必须与HDFS集群版本严格一致。曾因混用Hadoop 2.7和3.2的JAR,导致
java.lang.NoSuchMethodError: org.apache.hadoop.fs.FileSystem.createFileSystem。
验证四:权限与ACL检查
HDFS路径需满足三重权限:
- Linux文件权限:
/user/data目录需对Spark用户有r-x权限(x权限必需,否则无法进入目录) - HDFS ACL:
hdfs dfs -getfacl /user/data确认用户有r-x权限 - YARN队列权限:
yarn.scheduler.capacity.root.default.acl_submit_applications需包含Spark用户
验证五:SparkSession配置注入
在代码中显式注入HDFS配置,避免依赖外部XML:
val spark = SparkSession.builder() .appName("HDFSReader") .config("spark.hadoop.fs.defaultFS", "hdfs://nn:8020") .config("spark.hadoop.dfs.namenode.http-address", "nn:50070") .getOrCreate() // 验证配置是否生效 println(spark.sparkContext.hadoopConfiguration.get("fs.defaultFS")) // 应输出 hdfs://nn:8020自动化验证脚本(保存为hdfs_check.sh):
#!/bin/bash NN_HOST="nn" NN_PORT="8020" TEST_PATH="/tmp/spark_test" echo "=== Step 1: Check Hadoop CLI ===" hadoop fs -ls "hdfs://$NN_HOST:$NN_PORT/" >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "FAIL: Hadoop CLI cannot connect to $NN_HOST:$NN_PORT" exit 1 fi echo "=== Step 2: Check Spark HDFS config ===" spark-shell --conf "spark.hadoop.fs.defaultFS=hdfs://$NN_HOST:$NN_PORT" \ -e 'println(sc.hadoopConfiguration.get("fs.defaultFS"))' 2>/dev/null | grep -q "$NN_HOST" if [ $? -ne 0 ]; then echo "FAIL: Spark cannot load HDFS config" exit 1 fi echo "=== Step 3: Test read/write ===" hadoop fs -mkdir -p "$TEST_PATH" echo "test" | hadoop fs -put - "$TEST_PATH/test.txt" spark-shell --conf "spark.hadoop.fs.defaultFS=hdfs://$NN_HOST:$NN_PORT" \ -e 'println(spark.read.text("hdfs://'$NN_HOST':'$NN_PORT'$TEST_PATH/test.txt").count())' 2>/dev/null | grep -q "1" if [ $? -ne 0 ]; then echo "FAIL: Spark cannot read HDFS file" exit 1 fi echo "PASS: All HDFS checks passed" hadoop fs -rm -r "$TEST_PATH"3.3 混合读取场景:如何优雅地在本地开发与集群部署间切换
真实项目中,90%的Spark作业需要“本地调试+集群运行”双模式。硬编码路径会导致每次切换都要改代码,我的解决方案是三级路径抽象:
第一级:环境标识符
// 通过系统属性或环境变量识别环境 val env = System.getProperty("spark.env", sys.env.getOrElse("SPARK_ENV", "local")) // env取值:local / dev / prod第二级:路径工厂类
object PathFactory { def getDataPath(base: String): String = env match { case "local" => s"file:///$base" case "dev" => s"hdfs://dev-nn:8020$user/base" case "prod" => s"hdfs://prod-nn:8020$user/base" } }第三级:配置驱动的读取器
case class DataReader(spark: SparkSession) { def readCsv(path: String, options: Map[String, String] = Map()): DataFrame = { val fullPath = PathFactory.getDataPath(path) val reader = spark.read.options(options) // 根据环境自动适配选项 val finalOptions = options ++ env match { case "local" => Map("header" -> "true", "inferSchema" -> "true") case _ => Map("header" -> "true") // 生产环境禁用inferSchema } reader.options(finalOptions).csv(fullPath) } } // 使用 val reader = DataReader(spark) val df = reader.readCsv("sales/2023", Map("delimiter" -> "|"))实操心得:某电商项目用此方案后,开发人员本地用
-Dspark.env=local启动,测试环境用-Dspark.env=dev,上线时只需改一个参数,零代码修改。更重要的是,所有路径逻辑集中在PathFactory,审计时一眼看清数据流向。
4. 实操过程:一次完整的HDFS读取故障排查实录
4.1 故障现象与初始诊断
某天下午3点,监控告警:用户行为分析任务持续失败,错误日志显示:
Caused by: java.io.IOException: Failed on local exception: java.io.IOException: org.apache.hadoop.security.AccessControlException: Permission denied: user=spark, access=READ, inode="/user/hive/warehouse/behavior.db/click_log": hive:hive:drwx------表面看是权限问题,但hive用户显然不该限制spark用户读取。我登录集群,执行hdfs dfs -ls /user/hive/warehouse/behavior.db/,发现click_log目录属主是hive,权限drwx------,其他用户无任何权限。直觉告诉我这不是单纯权限问题——因为昨天同一任务还正常运行。
初步假设:
- HDFS ACL被意外修改?
- Spark提交用户变更?
- Hive Metastore权限同步异常?
4.2 深度排查:从Shell到源码的逐层验证
Step 1:Shell层面验证基础连通性
# 切换到spark用户 sudo -u spark bash # 测试HDFS读取 hdfs dfs -ls /user/hive/warehouse/behavior.db/click_log # 返回 Permission denied —— 确认是HDFS层问题 # 测试其他目录 hdfs dfs -ls /tmp # 正常返回 —— 说明spark用户有基本HDFS权限Step 2:检查HDFS ACL详情
hdfs dfs -getfacl /user/hive/warehouse/behavior.db/click_log # 输出: # # file: /user/hive/warehouse/behavior.db/click_log # # owner: hive # # group: hive # USER::rwx # GROUP::--- # OTHER::--- # DEFAULT:USER::rwx # DEFAULT:GROUP::--- # DEFAULT:OTHER::---果然,OTHER组权限为空,而spark用户不属于hive组。但问题来了:为什么昨天能读?查看HDFS操作日志:
hdfs dfsadmin -report | grep "Safe mode" # 发现 Safe mode is ON —— 安全模式开启!安全模式下,HDFS只允许超级用户操作。而hive用户是超级用户,spark不是。但任务昨天能跑,说明安全模式是今天刚开启的。
Step 3:溯源安全模式开启原因
查看NameNode日志:
tail -100 $HADOOP_HOME/logs/hadoop-*-namenode-*.log | grep "Safe mode" # 输出:Safe mode is ON. The reported blocks 12345 has reached the threshold 0.9990 of total blocks 12357.HDFS块报告率低于阈值(99.9%),触发安全模式。原因是昨天有DataNode宕机,部分Block未上报。运维已修复DataNode,但NameNode未自动退出安全模式。
Step 4:临时解决方案与长期规避
临时:让运维执行hdfs dfsadmin -safemode leave。
长期:修改HDFS配置,降低安全模式阈值(不推荐)或增加DataNode冗余(推荐)。但更关键的是,Spark任务不应依赖HDFS安全模式状态。于是我们改造了读取逻辑:
// 在DataReader中增加重试与降级 def readWithFallback(path: String): DataFrame = { try { // 首选HDFS读取 spark.read.parquet(path) } catch { case e: org.apache.hadoop.security.AccessControlException if e.getMessage.contains("Safe mode") => // 降级到本地缓存(HDFS快照已同步到本地) val localCache = s"/data/cache/${System.currentTimeMillis()}" spark.sparkContext.parallelize(Seq.empty[String]) .saveAsTextFile(localCache) // 触发缓存同步 spark.read.parquet(s"file://$localCache") case e => throw e } }4.3 排查工具箱:五个必装的诊断命令与脚本
工具一:HDFS路径解析器
# 解析任意路径的FileSystem实现类 hadoop org.apache.hadoop.fs.FsShell -ls hdfs://nn:8020/ | head -1 # 输出:Found filesystem: org.apache.hadoop.hdfs.DistributedFileSystem工具二:Spark配置快照
# 导出当前SparkSession所有配置 spark-shell -e 'spark.conf.getAll.foreach(println)' | grep -E "(fs|hdfs|core)"工具三:JAR包冲突检测
# 检查hadoop-client JAR是否重复 find $SPARK_HOME/jars -name "*hadoop*" | xargs -I {} sh -c 'echo {}; jar -tf {} | grep -i "filesystem"' # 若多个JAR都含DistributedFileSystem,必有冲突工具四:Executor端文件系统调试
在Executor日志中搜索:
# 关键日志关键词 "FileSystem.get" # 查看获取的FileSystem实例 "ListStatus" # 查看文件列表操作 "open" # 查看文件打开操作工具五:网络连通性一键检测脚本
#!/bin/bash # network_check.sh NN_HOST="nn" NN_PORT="8020" echo "Testing connection to $NN_HOST:$NN_PORT..." timeout 5 bash -c "echo > /dev/tcp/$NN_HOST/$NN_PORT" 2>/dev/null if [ $? -eq 0 ]; then echo "✓ TCP port open" else echo "✗ TCP port closed" exit 1 fi # 测试HDFS RPC hadoop fs -ls "hdfs://$NN_HOST:$NN_PORT/" >/dev/null 2>&1 if [ $? -eq 0 ]; then echo "✓ HDFS RPC working" else echo "✗ HDFS RPC failed" exit 1 fi5. 常见问题与排查技巧实录
5.1 本地文件读取高频问题速查表
| 问题现象 | 根本原因 | 解决方案 | 验证命令 |
|---|---|---|---|
java.io.FileNotFoundException: File file:/data/input.txt does not exist | 路径是相对路径,JVM工作目录非预期 | 改用file:///absolute/path或SparkFiles.get("input.txt") | pwd查看当前目录,ls -l /data/input.txt确认存在 |
org.apache.spark.sql.AnalysisException: Unable to infer schema... | CSV文件为空或首行无数据 | 显式传入Schema,或用option("header","false") | head -5 /data/input.txt检查文件内容 |
java.lang.IllegalArgumentException: Can't get JDBC URL for unknown scheme: file | 错误地将file://路径传给spark.read.jdbc() | jdbc()只接受JDBC URL,本地文件用text()/csv() | 检查API调用是否匹配数据源类型 |
Task not serializable | 自定义UDF引用了不可序列化的对象(如java.io.File) | 将文件路径作为参数传入UDF,UDF内新建File对象 | 在UDF开头加println("UDF running")确认执行位置 |
注意事项:本地文件读取时,
spark.sql.adaptive.enabled=true可能导致小文件合并失败。若读取大量小CSV,建议关闭:spark.conf.set("spark.sql.adaptive.enabled", "false")。
5.2 HDFS读取典型故障与独家避坑技巧
坑一:Kerberos认证的“静默失败”
现象:任务卡住无日志,10分钟后超时。
原因:Kerberos票据过期,但Spark未抛出AuthenticationException,而是无限重试。
避坑:在spark-submit中加--conf spark.kerberos.keytab=/path/to/keytab --conf spark.kerberos.principal=user@REALM,并设置票据刷新:
# 提交前刷新票据 kinit -kt /path/to/keytab user@REALM # 提交时指定票据缓存 --conf spark.yarn.keytab=/path/to/keytab \ --conf spark.yarn.principal=user@REALM坑二:HDFS高可用(HA)配置遗漏
现象:单NameNode正常,HA集群报java.net.ConnectException: Connection refused。
原因:未配置hdfs-site.xml中的dfs.nameservices和dfs.ha.namenodes.xxx。
避坑:HA配置必须成对出现:
<!-- hdfs-site.xml --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>nn1:8020</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>nn2:8020</value> </property>坑三:HDFS客户端版本与集群不兼容
现象:java.lang.NoClassDefFoundError: org/apache/hadoop/fs/CanSetDropBehind。
原因:Spark自带Hadoop 2.7,集群是Hadoop 3.2,API不兼容。
避坑:强制使用集群Hadoop版本:
# 提交时排除Spark自带Hadoop --conf spark.driver.userClassPathFirst=true \ --conf spark.executor.userClassPathFirst=true \ --jars /opt/cloudera/parcels/CDH/lib/hadoop/hadoop-common.jar,/opt/cloudera/parcels/CDH/lib/hadoop-hdfs/hadoop-hdfs.jar5.3 混合环境下的终极调试清单
当本地与HDFS读取都失败时,按此顺序排查(每步耗时<2分钟):
检查Spark版本与Hadoop兼容性
spark-shell --version | grep "Hadoop" # 输出应与集群Hadoop版本一致,如"Hadoop 3.2.1"验证Hadoop配置加载路径
spark.sparkContext.hadoopConfiguration.getResource("core-site.xml") // 应返回文件路径,若为null,说明配置未加载测试FileSystem实例创建
import org.apache.hadoop.fs.FileSystem val fs = FileSystem.get(new java.net.URI("hdfs://nn:8020"), spark.sparkContext.hadoopConfiguration) println(fs.getClass.getName) // 应为DistributedFileSystem检查Executor端类路径
在Executor日志中搜索hadoop-common.jar,确认JAR包已分发。启用详细日志
--conf spark.logConf=true \ --conf spark.sql.adaptive.enabled=false \ --conf spark.sql.adaptive.coalescePartitions.enabled=false
我个人在实际操作中的体会是:90%的“读取失败”问题,根源不在Spark代码,而在环境配置的微小偏差。与其花两小时调代码,不如用5分钟跑一遍
network_check.sh和hdfs_check.sh。真正的效率提升,来自于把重复排查动作固化为脚本,而不是依赖记忆。现在我的团队所有新成员入职,第一件事就是运行这两个脚本,通过即算环境配置合格。