用Apache Spark从PySpark访问S3数据的具体实例

721 阅读4分钟

一段时间以来,你已经能够在你的机器上运行pip install pyspark ,并获得所有的Apache Spark,所有的jars等等,而不用担心其他的事情。虽然这是一个在你的机器上设置PySpark来解决本地问题的好方法,但它也有一系列的注意事项,你基本上是在运行一个分布式的、难以维护的系统通过管道安装。

虽然我习惯于Spark被管理(由工作中的团队、Databricks或在亚马逊的EMR中),但我喜欢在本地运行。我通过pip使用PySpark已经有一段时间了,但我总是遇到S3的问题。现在让我们来看看这些问题的一些解决方案。

为了这篇文章的目的,我创建了两个S3桶法兰克福的ok-eu-central-1 和俄勒冈州的ok-us-west-2 。我们稍后会看到原因。让我们管道安装PySpark,并尝试一个简单的管道,只是导出一个数据点。

from pyspark.sql import SparkSession


if __name__ == '__main__':
    spark = (SparkSession
                .builder
                .appName('my_export')
                .master("local[*]")
                .getOrCreate()
            )
    spark.sql('select 1').write.mode('overwrite').csv('s3a://ok-us-west-2/tmp')

但是,当我们调用spark-submit pipeline.py ,我们得到的结果是这样的。

py4j.protocol.Py4JJavaError: An error occurred while calling o34.csv.
: java.lang.RuntimeException: java.lang.ClassNotFoundException: Class org.apache.hadoop.fs.s3a.S3AFileSystem not found

这是因为Spark没有和相关的AWS库一起发货,所以我们来解决这个问题。我们可以指定一个依赖关系,这个依赖关系将在第一次调用时被下载(所以当把它捆绑在例如Docker中时要小心,你要在那里以不同的方式来运送它)。

$ spark-submit --packages org.apache.hadoop:hadoop-aws:2.7.4 pipeline.py 

这里2.7.4反映了与PySpark一起运送的Hadoop库的版本,在写这篇文章的时候(2020年6月),有(Py)Spark 3.0.0版本,它与2.7.4一起运送。虽然Spark现在支持Hadoop 3.x,但这是它的默认版本。

当我们运行上面的命令时,一切都很正常!所以,如果你现在就能工作,那么你就可以把它当作一个新的工具。所以,如果现在对你有用,你就知道你要做什么了--你需要指示Spark(以某种方式),包括这个AWS的依赖。我在运行时通过cli参数做到了这一点,但也有其他(更好的)方法。

不是所有的区域都是平等的

现在,这一切都在工作,但让我们稍微改变一下上面的代码。让我们写到法兰克福的桶中。

# ...
    spark.sql('select 1').write.mode('overwrite').csv('s3a://ok-eu-central-1/tmp')

现在的调用,即使有--packages 参数也不会工作。它将以400错误失败。

py4j.protocol.Py4JJavaError: An error occurred while calling o37.csv.
: com.amazonaws.services.s3.model.AmazonS3Exception: Status Code: 400, AWS Service: Amazon S3, AWS Request ID: (...), AWS Error Code: null, AWS Error Message: Bad Request, S3 Extended Request ID: (...)=

400是一个错误的请求,所以我们所做的事情是不正确的。如果你进入兔子洞,你会发现有多个版本的通信协议,新地区只支持一个新的协议(v4)。可悲的是,2.7.4版本中的hadoop-aws 库默认为v2,所以如果没有进一步的设置,它不能处理法兰克福和其他地区。我们不能只是升级hadoop-aws,因为还有其他与Hadoop相关的库被链接,它们的版本需要匹配。

如果你在Hadoop 2和新的AWS地区使用Spark,你需要做两件事。首先,指定你使用的是v4协议。这里我把它作为一个参数提交(而且只针对驱动程序,因为我使用的是独立模式),你要在其他地方指定。

spark-submit --packages org.apache.hadoop:hadoop-aws:2.7.4 --conf spark.driver.extraJavaOptions='-Dcom.amazonaws.services.s3.enableV4' pipeline.py

第二,我们需要为我们的S3桶设置端点。不是说得太远,但S3库往往有一个endpoint 选项,这样你就可以把它们指向其他服务器,甚至AWS之外的服务器,只要它们支持S3 API。这对于测试和其他事情来说是非常有用的。在这里,我们将利用这个选项,但只是在AWS内指向它。

from pyspark.sql import SparkSession


if __name__ == '__main__':
    spark = (SparkSession
                .builder
                .appName('my_export')
                .master("local[*]")
                .config('fs.s3a.endpoint', 's3.eu-central-1.amazonaws.com') # this has been added
                .getOrCreate()
            )
    spark.sql('select 1').write.mode('overwrite').csv('s3a://ok-eu-central-1/tmp')

就这样了。现在我们已经指定了端点、协议版本和hadoop-aws,我们终于可以写到新的S3区域。查看相关的AWS文档以获得你所在地区的端点。

我希望Spark将其PySpark发行版切换到Hadoop 3.x,这样我们就可以避免这里的大部分诡计(那时我们只需要hadoop-aws )。