Spark standalone模式在多用户环境下保存结果报错 java.io.ioexception: mkdirs failed to create file

0. 前情提要

在服务器上用standalone模式部署了Spark,使用本地文件系统(没有使用HDFS),在多用户提交Spark application计算任务并保存结果到本地文件系统时出现问题,保存时提示:

java.io.IOException: mkdirs failed to create file

java.io.IOException: Failed to rename DeprecatedRawLocalFileStatus

因为很少有像我这么“奇葩”的使用场景(单机Standalone+本地文件系统+多用户),所以花了两三个小时搜索研究才最终解决。

1. 解决方案

不想看完整问题分析的同学可以直接采用下面的解决方案:

  1. 关闭Spark
  2. 使root用户执行start-master.shstart-worker.sh启动Spark
  3. 在创建SparkSession时,将spark.hadoop.fs.permissions.umask-mode设置为000。可以在Spark安装目录的conf/spark-defaults.conf文件内设置;以pySpark为例,也可以通过这种方式在运行时设置:
spark = SparkSession \
    .builder \
    .master("spark://youraddress:7077") \
    .config("spark.hadoop.fs.permissions.umask-mode", "000") \
    .appName("yourname") \
    .getOrCreate()
  1. 如果你不存在多用户使用的场景,可以忽略第2、3步,使用相同的用户启动Spark和提交Spark任务即可

感兴趣的同学可以接着往下看前因后果,不感兴趣的可以右上角了

2. 系统环境

  • 单台服务器:68 vCPUs + 600GB RAM
  • CentOS 8
  • Spark 3.1.2 (Standalone模式,使用本地文件系统,无HDFS)
  • JupyterHub(支持多用户的Jupyter notebook)
  • pySpark

3. 问题描述

用户可以正常执行Spark计算,而一旦将Spark结果输出到本地文件系统时,就会报错,比如执行df.write.csv('result')。(注意: 这里的df是Spark DataFrame,不是pandas DataFrame,如果Spark driver有足够内存将结果转换为pandas DataFrame再保存,是不会出现这个问题的)

报错分为两种情况:

  • 启动Spark的为非root用户时:java.io.IOException: mkdirs failed to create file
  • 启动Spark的为root时:java.io.IOException: Failed to rename DeprecatedRawLocalFileStatus

会导致结果保存过程中止,无法导出计算结果

4. 问题分析

问题的根本原因在于启动Spark的用户和使用Spark进行计算的用户不同,其他用户创建的文件和文件夹无法被当前用户修改:

  • 在Spark保存结果时,首先会以提交计算任务的用户(driver)创建一个文件夹(比如上文提到的result),该文件夹的权限为755(rwxr-xr-x),其他非权限用户不具有写权限
  • 然后,各个executor会以启动Spark的用户在上述文件夹中写入临时结果,如果启动Spark的用户不是root,就无法写入,报第一个错误
  • 如果启动Spark的用户是root,那么临时结果可以被写入,最后需要由提交计算任务的用户(driver)来重新组织。由于这些临时文件的所有者是root,所以driver没有办法修改,报第二个错误

5. 解决思路

解决问题的关键在于,在启动和提交Spark计算用户不相同的前提下,让双方创建的文件、文件夹都能够被双方修改。问题似乎很简单,设置一下umask或者ACL不就好了吗?于是我开始了一些失败的尝试:

  • 将保存位置的默认ACL文件夹权限设置为所有人可读写
  • 将用户的umask设置为000(所有人可读写)

结果发现,在保存Spark结果的时候,这些设置都没有生效,生成的文件权限依然为755
这说明Spark有自己的一套配置覆盖了上面的默认配置,那么这个配置在哪呢?

答案是spark.hadoop.fs.permissions.umask-mode,其默认值为022,所以生成的文件权限依然为755(感谢这位答主)。

然而,我以为我的使用环境(本地文件系统)不适用这个配置项,所以刚开始看到这个答案的时候不以为然直接忽略了…… 后来又折腾了几个小时,抱着试一试的心态,发现居然成功了

6. 仍存在的问题

  • 首先肯定是安全性的问题,用户的文件理应只有用户自己能修改(或者至少是组成员)。因为这台服务器的用户都是可信的,所以图省事我把umask-mode改成了000,稍微安全一点的做法是改成002,然后建立一个(或多个)新组把Spark启动用户和计算用户加入进去
  • 即使umask-mode改成了000,如果启动Spark的用户不是root,依然会报第一个错误,原因不明
  • 有条件、不怕麻烦的话大家还是上HDFS或者其他文件系统吧,起码出了问题后能搜到的相关内容都更多些

谨以我昨晚的搜索记录纪念一下这次debug:
在这里插入图片描述

参考资料

  1. https://spark.apache.org/docs/latest/configuration.html
  2. https://stackoverflow.com/questions/43077881/spark-how-to-write-files-with-a-given-permission
  3. https://stackoverflow.com/questions/51769375/how-to-force-spark-hive-to-create-task-directories-with-custom-permissions
  4. https://www.mail-archive.com/user@spark.apache.org/msg28820.html
  5. https://stackoverflow.com/a/35987436/6059213
### Java中处理UT000036连接终止解析multipart数据错误 当遇到`java.io.IOException: UT000036: Connection terminated parsing multipart data` 错误时,这通常意味着服务器在尝试解析多部分表单数据(通常是文件上传操作的一部分)期间遇到了意外断开的情况[^1]。 #### 原因分析 此异常可能由多种因素引起: - 客户端与服务器之间的网络不稳定或中断。 - 请求体过大超出了服务器配置允许的最大大小。 - 文件上传过程中客户端取消了请求。 - Web容器内部Bug或者资源不足导致无法正常处理大文件或多部分消息。 #### 解决方案 为了有效应对上述情况并减少该类错误的发生频率,可以采取如下措施: ##### 配置调整 增加Web应用服务器对于HTTP POST请求以及Multipart/form-data类型的特殊设置。例如,在WildFly/JBoss EAP环境中可以通过修改standalone.xml中的`<http-listener>`标签来增大最大实体长度限制: ```xml <subsystem xmlns="urn:jboss:domain:undertow:..."> ... <server name="default-server"> <host name="default-host" alias="localhost"> <http-listener max-post-size="10485760" ... /> </host> </server> </subsystem> ``` 此处将 `max-post-size` 设置为更大的值以适应实际需求(单位字节),默认情况下可能是较小数值如2MB (2097152)。 ##### 优化前端实现 确保HTML页面上的form元素正确设置了编码方式,并且指定了合适的enctype属性值: ```html <form action="/upload" method="post" enctype="multipart/form-data"> <!-- form content --> </form> ``` 同时建议加入进度条显示功能以便于用户了解当前状态;另外还可以考虑通过JavaScript监听beforeunload事件提示未完成的操作防止意外关闭浏览器窗口造成传输失败。 ##### 启用日志记录 启用更详细的日志级别可以帮助定位具体发生位置和原因。可以在部署描述符(web.xml)里添加过滤器用于捕获所有Servlet API级别的IO异常信息: ```xml <filter> <filter-name>ExceptionLoggingFilter</filter-name> <filter-class>com.example.ExceptionLoggingFilter</filter-class> </filter> <filter-mapping> <filter-name>ExceptionLoggingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 配合自定义的日志处理器收集更多上下文环境下的诊断线索。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值