一段时间以来,你已经能够在你的机器上运行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 )。