Pyspark:如何在Google存储桶中读取.csv文件?

时间:2020-03-23 12:31:23

标签: python apache-spark google-cloud-platform pyspark

我在Google存储桶中存储了一些文件。这些是我建议的here设置。

spark = SparkSession.builder.\
        master("local[*]").\
        appName("TestApp").\
        config("spark.serializer", KryoSerializer.getName).\
        config("spark.jars", "/usr/local/.sdkman/candidates/spark/2.4.4/jars/gcs-connector-hadoop2-2.1.1.jar").\
        config("spark.kryo.registrator", GeoSparkKryoRegistrator.getName).\
        getOrCreate()
#Recommended settings for using GeoSpark
spark.conf.set("spark.driver.memory", 6)
spark.conf.set("spark.network.timeout", 1000)
#spark.conf.set("spark.driver.maxResultSize", 5)
spark.conf.set

spark._jsc.hadoopConfiguration().set('fs.gs.impl', 'com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem')
# This is required if you are using service account and set true, 
spark._jsc.hadoopConfiguration().set('fs.gs.auth.service.account.enable', 'false')
spark._jsc.hadoopConfiguration().set('google.cloud.auth.service.account.json.keyfile', "myJson.json")



path = 'mBucket-c892b51f8579.json'
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = path
client = storage.Client()
name = 'https://console.cloud.google.com/storage/browser/myBucket/'
bucket_id = 'myBucket'
bucket = client.get_bucket(bucket_id)

我可以使用以下内容简单阅读它们:

df = pd.read_csv('gs://myBucket/myFile.csv.gz', compression='gzip')
df.head()

    time_zone_name           province_short
0   America/Chicago              US.TX
1   America/Chicago              US.TX
2   America/Los_Angeles          US.CA
3   America/Chicago              US.TX
4   America/Los_Angeles          US.CA

我正在尝试使用pyspark

读取同一文件
myTable = spark.read.format("csv").schema(schema).load('gs://myBucket/myFile.csv.gz', compression='gzip')

但出现以下错误

Py4JJavaError: An error occurred while calling o257.load.
: java.lang.NoClassDefFoundError: Could not initialize class com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at org.apache.hadoop.conf.Configuration.getClassByNameOrNull(Configuration.java:2134)
    at org.apache.hadoop.conf.Configuration.getClassByName(Configuration.java:2099)
    at org.apache.hadoop.conf.Configuration.getClass(Configuration.java:2193)
    at org.apache.hadoop.fs.FileSystem.getFileSystemClass(FileSystem.java:2654)
    at org.apache.hadoop.fs.FileSystem.createFileSystem(FileSystem.java:2667)
    at org.apache.hadoop.fs.FileSystem.access$200(FileSystem.java:94)
    at org.apache.hadoop.fs.FileSystem$Cache.getInternal(FileSystem.java:2703)
    at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:2685)
    at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:373)
    at org.apache.hadoop.fs.Path.getFileSystem(Path.java:295)
    at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:45)
    at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:332)
    at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:223)
    at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211)
    at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:178)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)
    at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:357)
    at py4j.Gateway.invoke(Gateway.java:282)
    at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
    at py4j.commands.CallCommand.execute(CallCommand.java:79)
    at py4j.GatewayConnection.run(GatewayConnection.java:238)
    at java.lang.Thread.run(Thread.java:748)

2 个答案:

答案 0 :(得分:1)

欢迎来到hadoop依赖地狱!

1。使用包装而不是罐子

您的配置基本上是正确的,但是当您将gcs-connector添加为本地jar时,您还需要手动确保其所有依赖项在JVM类路径中均可用。

通常将连接器作为一个软件包添加并让spark处理依赖关系更加容易,因此使用@Override public int getItemCount() { if(List != null) { return List.size(); } return 0; } 代替config("spark.jars", "/usr/local/.sdkman/candidates/spark/2.4.4/jars/gcs-connector-hadoop2-2.1.1.jar")

2。管理ivy2依赖项解决问题

当您执行上述操作时,由于maven(用于发布)和ivy2(用于spark用于依赖关系解析)之间的分辨率差异,spark可能会抱怨无法下载某些依赖关系。

通常可以通过使用config('spark.jars.packages', 'com.google.cloud.bigdataoss:gcs-connector:hadoop2-2.1.1')来简单地让spark忽略未解决的依赖项来解决此问题,因此添加新的配置行,例如 spark.jars.excludes

3。管理类路径冲突

完成此操作后,SparkSession将启动,但文件系统仍然会失败,因为pyspark的标准发行版打包了一个旧版本的guava库,该版本未实现gcs-connector依赖的API。

您需要通过使用以下配置config('spark.jars.excludes','androidx.annotation:annotation,org.slf4j:slf4j-api')config('spark.driver.userClassPathFirst','true')来确保gcs-connector首先找到其预期版本

4。管理依赖冲突

现在,您可能会认为一切正常,但实际上没有,因为默认的pyspark发行版包含hadoop库的2.7.3版本,而gcs-connector版本2.1.1仅依赖于2.8+ API。

现在您的选择是:

  • 使用带有更新的hadoop(或没有内置hadoop库的软件包)的自定义spark版本
  • 使用旧版本的gcs-connector(1.9.17版可以正常工作)

5。最后一个有效的配置

假设您要坚持使用pyspark的PyPi或Anaconda最新发行版,则以下配置应该可以正常工作。

我仅包含了与gcs相关的配置,将Hadoop配置直接移至spark配置,并假设您正确设置了GOOGLE_APPLICATION_CREDENTIALS:

config('spark.executor.userClassPathFirst','true')

请注意,gcs-connector版本1.9.17与2.1.1相比具有不同的排除集,因为为什么不这样做...

PS:您还需要确保使用的是Java 1.8 JVM,因为Spark 2.4在新的JVM上不起作用。

答案 1 :(得分:0)

除了@rluta 的好答案之外,您还可以通过将番石榴罐特别放在 extraClassPath 中来替换 userClassPathFirst 行:

spark.driver.extraClassPath=/root/.ivy2/jars/com.google.guava_guava-27.0.1-jre.jar:/root/.ivy2/jars/com.google.guava_failureaccess-1.0.1.jar:/root/.ivy2/jars/com.google.guava_listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
spark.executor.extraClassPath=/root/.ivy2/jars/com.google.guava_guava-27.0.1-jre.jar:/root/.ivy2/jars/com.google.guava_failureaccess-1.0.1.jar:/root/.ivy2/jars/com.google.guava_listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar

这有点笨拙,因为您需要确切的本地 ivy2 路径,不过您也可以将 jar 下载/复制到更永久的地方。

但是,这减少了其他潜在的依赖冲突,例如与 livy 的冲突,如果 gcs-connector 的 jackson 依赖在类路径前面,则会抛出 java.lang.NoClassDefFoundError: org.apache.livy.shaded.json4s.jackson.Json4sModule