Spark 写入 S3 V4 SignatureDoesNotMatch 错误

Posted

技术标签:

【中文标题】Spark 写入 S3 V4 SignatureDoesNotMatch 错误【英文标题】:Spark Write to S3 V4 SignatureDoesNotMatch Error 【发布时间】:2017-04-30 15:48:41 【问题描述】:

我在尝试使用 Spark 将 Dataframe 写入 S3 时遇到 S3 SignatureDoesNotMatch

已经尝试过的症状/事情:

代码失败有时但工作有时; 代码可以读取 S3没有任何问题,并且可以不时写入S3,这排除了像S3A / enableV4 / Wrong Key / Region这样的错误配置设置端点等 S3A 端点已根据 S3 文档S3 Endpoint 设置; 按照建议的here,确保AWS_SECRETY_KEY 不包含任何非字母数字; 使用 NTP 确保服务器时间同步; 以下内容已在 EC2 m3.xlarge 上测试,spark-2.0.2-bin-hadoop2.7 在本地模式下运行; 文件写入本地 fs 后问题消失; 现在的解决方法是使用 s3fs 挂载存储桶并写入那里;但这并不理想,因为 s3fs 经常因 Spark 施加的压力而死;

代码可以归结为:

spark-submit\
    --verbose\
    --conf spark.hadoop.fs.s3n.impl=org.apache.hadoop.fs.s3native.NativeS3FileSystem \
    --conf spark.hadoop.fs.s3.impl=org.apache.hadoop.fs.s3.S3FileSystem \
    --conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem\
    --packages org.apache.hadoop:hadoop-aws:2.7.3\
    --driver-java-options '-Dcom.amazonaws.services.s3.enableV4'\
    foobar.py


# foobar.py
sc = SparkContext.getOrCreate()
sc._jsc.hadoopConfiguration().set("fs.s3a.access.key", 'xxx')
sc._jsc.hadoopConfiguration().set("fs.s3a.secret.key", 'xxx')
sc._jsc.hadoopConfiguration().set("fs.s3a.endpoint", 's3.dualstack.ap-southeast-2.amazonaws.com')

hc = SparkSession.builder.enableHiveSupport().getOrCreate()
dataframe = hc.read.parquet(in_file_path)

dataframe.write.csv(
    path=out_file_path,
    mode='overwrite',
    compression='gzip',
    sep=',',
    quote='"',
    escape='\\',
    escapeQuotes='true',
)

Spark 溢出以下error。


将 log4j 设置为详细,似乎发生了以下情况:

每个人都将被输出到 S3 /_temporary/foorbar.part-xxx 上的驻留位置; PUT 调用会将分区移动到最终位置; 成功PUT调用几次后,所有后续PUT调用均因403而失败; 由于 reuqets 是由 aws-java-sdk 制作的,因此不确定在应用程序级别应该做什么; -- 以下日志来自另一个具有完全相同错误的事件;
 >> PUT XXX/part-r-00025-ae3d5235-932f-4b7d-ae55-b159d1c1343d.gz.parquet HTTP/1.1
 >> Host: XXX.s3-ap-southeast-2.amazonaws.com
 >> x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
 >> X-Amz-Date: 20161104T005749Z
 >> x-amz-metadata-directive: REPLACE
 >> Connection: close
 >> User-Agent: aws-sdk-java/1.10.11 Linux/3.13.0-100-generic OpenJDK_64-Bit_Server_VM/25.91-b14/1.8.0_91 com.amazonaws.services.s3.transfer.TransferManager/1.10.11
 >> x-amz-server-side-encryption-aws-kms-key-id: 5f88a222-715c-4a46-a64c-9323d2d9418c
 >> x-amz-server-side-encryption: aws:kms
 >> x-amz-copy-source: /XXX/_temporary/0/task_201611040057_0001_m_000025/part-r-00025-ae3d5235-932f-4b7d-ae55-b159d1c1343d.gz.parquet
 >> Accept-Ranges: bytes
 >> Authorization: AWS4-HMAC-SHA256 Credential=AKIAJZCSOJPB5VX2B6NA/20161104/ap-southeast-2/s3/aws4_request, SignedHeaders=accept-ranges;connection;content-length;content-type;etag;host;last-modified;user-agent;x-amz-content-sha256;x-amz-copy-source;x-amz-date;x-amz-metadata-directive;x-amz-server-side-encryption;x-amz-server-side-encryption-aws-kms-key-id, Signature=48e5fe2f9e771dc07a9c98c7fd98972a99b53bfad3b653151f2fcba67cff2f8d
 >> ETag: 31436915380783143f00299ca6c09253
 >> Content-Type: application/octet-stream
 >> Content-Length: 0
DEBUG wire:  << "HTTP/1.1 403 Forbidden[\r][\n]"
DEBUG wire:  << "x-amz-request-id: 849F990DDC1F3684[\r][\n]"
DEBUG wire:  << "x-amz-id-2: 6y16TuQeV7CDrXs5s7eHwhrpa1Ymf5zX3IrSuogAqz9N+UN2XdYGL2FCmveqKM2jpGiaek5rUkM=[\r][\n]"
DEBUG wire:  << "Content-Type: application/xml[\r][\n]"
DEBUG wire:  << "Transfer-Encoding: chunked[\r][\n]"
DEBUG wire:  << "Date: Fri, 04 Nov 2016 00:57:48 GMT[\r][\n]"
DEBUG wire:  << "Server: AmazonS3[\r][\n]"
DEBUG wire:  << "Connection: close[\r][\n]"
DEBUG wire:  << "[\r][\n]"
DEBUG DefaultClientConnection: Receiving response: HTTP/1.1 403 Forbidden
 << HTTP/1.1 403 Forbidden
 << x-amz-request-id: 849F990DDC1F3684
 << x-amz-id-2: 6y16TuQeV7CDrXs5s7eHwhrpa1Ymf5zX3IrSuogAqz9N+UN2XdYGL2FCmveqKM2jpGiaek5rUkM=
 << Content-Type: application/xml
 << Transfer-Encoding: chunked
 << Date: Fri, 04 Nov 2016 00:57:48 GMT
 << Server: AmazonS3
 << Connection: close
DEBUG requestId: x-amzn-RequestId: not available

【问题讨论】:

X-Amz-Date: 20161104T005749Z 是 1 个多月前。这个日志条目也那么旧吗? @Michael-sqlbot 是的,我们之前遇到过这个问题,当时(11 月初)我们通过将分区大小降低到某个幻数(该实例为 11)来避开它,并且(aws-java-sdk verbose) 日志是从那个时候开始的。由于从未确定根本原因,现在问题重新浮出水面,我在这里挖掘这些日志作为示例 我可以确认通过df.repartition(10).write.parquet("s3a://" + s3_bucket_out + "/", mode="overwrite", compression="snappy")) 将分区数减少到10 之类的低值似乎可以避免这个问题。 df.repartition(1).write.parquet(... 似乎没有出现问题。分区数越多,问题发生的频率越高。我从未见过单个分区会发生这种情况,有时会发生 10 个分区,而且经常会发生 100 个以上的分区。 @PaulLiang 你找到解决方案了吗。我面临同样的问题并尝试了以下解决方案,但它们不起作用。 【参考方案1】:

我遇到了完全相同的问题,并在this article 的帮助下找到了解决方案(other resources 指向同一个方向)。设置好这些配置选项后,写入S3成功:

spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version 2
spark.speculation false

我正在使用 Spark 2.1.1 和 Hadoop 2.7。我最终的 spark-submit 命令如下所示:

spark-submit
--packages com.amazonaws:aws-java-sdk:1.7.4,org.apache.hadoop:hadoop-aws:2.7.3
--conf spark.hadoop.fs.s3a.endpoint=s3.eu-central-1.amazonaws.com
--conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
--conf spark.executor.extraJavaOptions=-Dcom.amazonaws.services.s3.enableV4=true
--conf spark.driver.extraJavaOptions=-Dcom.amazonaws.services.s3.enableV4=true
--conf spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version=2
--conf spark.speculation=false
...

另外,我定义了这些环境变量:

AWS_ACCESS_KEY_ID=****
AWS_SECRET_ACCESS_KEY=****

【讨论】:

【参考方案2】:

我遇到了同样的问题,并通过从 aws-java-sdk:1.7.4 升级到 aws-java-sdk:1.11.199hadoop-aws:2.7.7hadoop-aws:3.0.0 来解决它。

但是,为了避免在与 AWS 交互时出现依赖项不匹配,我必须重建 Spark 并为其提供我自己的 Hadoop 3.0.0 版本。

我推测根本原因是 v4 签名算法采用当前时间戳的方式,然后所有 Spark 执行程序都使用相同的签名来验证他们的 PUT 请求。但是,如果一个超出算法允许的时间“窗口”,则请求以及所有进一步的请求都会失败,导致 Spark 回滚更改并出错。这就解释了为什么调用 .coalesce(1).repartition(1) 总是有效,但失败率与正在写入的分区数量成正比。

【讨论】:

我想知道你是否有关于如何使用 3.0.0 正确重建 Spark 的指南,我似乎无法让它工作 有一个选项可以在构建时指定版本。或者您可以在没有任何 hadoop 的情况下构建它,单独安装您自己的 3.0.3 并将 $SPARK_DIST_CLASSPATH 设置为 hadoop 类路径。 docs 中的详细信息。看看Getty Images spark-docker repo,它给出了一个很好的例子。【参考方案3】:
    “s3a”死了是什么意思?我对此很好奇。如果您有堆栈跟踪,请将它们归档在 Apache JIRA 服务器、项目 HADOOP、组件 fs/s3 上。 s3n 不支持 v4 API。这不是端点的问题,而是新的签名机械。除非出于安全原因,它不会升级它的 jets3t 库,所以不要再尝试使用它了。

无论驱动程序如何,Spark 都会遇到的一个问题是,它是一个最终一致的对象存储,其中:重命名需要 O(bytes) 才能完成,PUT 和 LIST 之间的延迟一致性可能会破坏提交.更简洁:Spark 假设在您向文件系统写入内容后,如果您对父目录执行 ls 操作,您会找到您刚刚写入的内容。 S3 不提供,因此术语“最终一致性”。现在,在 HADOOP-13786 中,我们正在努力改进,HADOOP-13345 看看我们是否不能使用 Amazon Dynamo 来获得更快、更一致的世界观。但是您必须为该功能支付 dynamodb 溢价。

最后,关于 s3a 故障排除的所有当前已知信息,包括 403 错误的可能原因,is online。希望它会有所帮助,如果您发现其他原因,欢迎使用补丁

【讨论】:

嗨史蒂夫,我的意思是 s3fs 死了(我正在安装 s3 存储桶并让 spark 写入本地 fs 以解决此问题; 我们使用的s3 uri是s3a;关于最终一致;从日志中我们实际上已经看到,sdk(或 Spark)足够智能,可以进行多次 HEAD 调用,以确保在发出 PUT 调用之前已成功将对象写入暂存位置(S3 返回 200 而不是 404)将其移至最终目的地。

以上是关于Spark 写入 S3 V4 SignatureDoesNotMatch 错误的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spark 通过 s3a 将 parquet 文件写入 s3 非常慢

Spark - 读取和写入相同的 S3 位置

使用 403 写入 S3 时,在 EMR 上运行的 Spark 偶尔会失败

来自EMR / Spark的S3写入速度非常慢

Spark saveAsTextFile写入空文件 - _ $ folder $到S3

将大型 Spark 数据帧作为镶木地板写入 s3 存储桶