语言-Scala
火花版本-2.4
我对Scala和Spark都是陌生的。 (我来自python背景,所以整个JVM生态系统对我来说还是很新的)
我想编写一个火花程序来并行执行以下步骤:
从数据框中的S3读取数据
转换此数据框的每一行
在新位置将更新的数据帧写回到S3
假设我有3个项目,A,B和C。对于每个项目,我都想执行以上3个步骤。
我想对所有这三个项目并行执行此操作。
我尝试创建一个具有3个分区的RDD,其中每个分区分别具有一项,即A,B和C。
然后,我尝试使用mapPartition
方法为每个分区编写逻辑(上面提到的3个步骤)。
我遇到Task not serializable
个错误。尽管我了解此错误的含义,但我不知道如何解决。
val items = Array[String]("A", "B", "C")
val rdd = sc.parallelize(items, 3)
rdd.mapPartitions(
partition => {
val item = partition.next()
val filePath = new ListBuffer[String]()
filePath += s"$basePath/item=$item/*"
val df = spark.read.format("parquet").option("basePath",s"$basePath").schema(schema).load(filePaths: _*)
//Transform this dataframe
val newDF = df.rdd.mapPartitions(partition => {partition.map(row =>{methodToTransformAndReturnRow(row)})})
newDf.write.mode(SaveMode.Overwrite).parquet(path)
})
我的用例是,对于每个项目,从S3中读取数据,对其进行转换(我将用例的新列直接添加到每一行中),然后将每个项目的最终结果写回到S3。
注意-我可以读取整个数据,按项目重新分区,进行转换并将其写回,但是重新分区会导致随机播放,这是我想要避免的,而我尝试的方法是,可以读取执行程序本身中每个项目的数据,以便它可以处理所获取的任何数据,而无需进行重新排序。
答案 0 :(得分:-1)
我不确定您要使用所展示的方法要实现什么,但是我觉得您可能会以困难的方式进行操作。除非有充分的理由这样做,否则通常最好让Spark(尤其是spark 2.0+)让它自己做。在这种情况下,只需使用一个操作即可处理所有三个分区。 Spark通常会很好地管理您的数据集。它还可能会自动引入您没有想到的优化,或者如果您尝试过多地控制过程,则可能无法实现的优化。话虽如此,如果它不能很好地管理流程,那么您可以通过尝试进行更多控制和手动操作来开始争论。到目前为止,至少这是我的经验。
例如,我曾经进行过一系列复杂的转换,为每个步骤/ DataFrame添加了更多的逻辑。如果我强迫Spark评估每个中间帧(例如在中间数据帧上进行计数或显示),我最终会由于无法满足需要而无法评估一个DataFrame(即,它无法进行计数)资源。但是,如果我忽略了这一点,并添加了更多的转换,Spark可以将某些优化推向较早的步骤(从较晚的步骤开始)。这意味着可以正确地评估后续的DataFrame(并且重要的是我的最终DataFrame)。最终评估可能是尽管,但无法评估中间的DataFrame本身仍在整个过程中。
请考虑以下内容。我使用CSV,但是对镶木地板也一样。
这是我的输入内容:
data
├── tag=A
│ └── data.csv
├── tag=B
│ └── data.csv
└── tag=C
└── data.csv
以下是其中一个数据文件(tag = A / data.csv)的示例
id,name,amount
1,Fred,100
2,Jane,200
这是一个可识别此结构内分区的脚本(即tag是列之一)。
scala> val inDataDF = spark.read.option("header","true").option("inferSchema","true").csv("data")
inDataDF: org.apache.spark.sql.DataFrame = [id: int, name: string ... 2 more fields]
scala> inDataDF.show
+---+-------+------+---+
| id| name|amount|tag|
+---+-------+------+---+
| 31| Scott| 3100| C|
| 32|Barnaby| 3200| C|
| 20| Bill| 2000| B|
| 21| Julia| 2100| B|
| 1| Fred| 100| A|
| 2| Jane| 200| A|
+---+-------+------+---+
scala> inDataDF.printSchema
root
|-- id: integer (nullable = true)
|-- name: string (nullable = true)
|-- amount: integer (nullable = true)
|-- tag: string (nullable = true)
scala> inDataDF.write.partitionBy("tag").csv("outData")
scala>
同样,我使用csv而不是parquet,因此您可以省去读取标头并推断模式的选项(parquet将自动执行此操作)。但除此之外,它将以相同的方式工作。
上面产生了以下目录结构:
outData/
├── _SUCCESS
├── tag=A
│ └── part-00002-9e13ec13-7c63-4cda-b5af-e2d69cb76278.c000.csv
├── tag=B
│ └── part-00001-9e13ec13-7c63-4cda-b5af-e2d69cb76278.c000.csv
└── tag=C
└── part-00000-9e13ec13-7c63-4cda-b5af-e2d69cb76278.c000.csv
如果您想操纵内容,一定要在读写之间添加任何映射操作,连接,过滤或其他所需的操作。
例如,将金额加500:
scala> val outDataDF = inDataDF.withColumn("amount", $"amount" + 500)
outDataDF: org.apache.spark.sql.DataFrame = [id: int, name: string ... 2 more fields]
scala> outDataDF.show(false)
+---+-------+------+---+
|id |name |amount|tag|
+---+-------+------+---+
|31 |Scott |3600 |C |
|32 |Barnaby|3700 |C |
|20 |Bill |2500 |B |
|21 |Julia |2600 |B |
|1 |Fred |600 |A |
|2 |Jane |700 |A |
+---+-------+------+---+
然后只需写出DataDF而不是inDataDF。