我正在尝试编写一个胶粘作业,该操作使用文件的csv的每一行将多个csv文件转换为单独的json文件。作业完成后,s3中会显示正确数量的文件,但是有些文件为空,有些文件中包含多个json对象。
应用映射后,这就是创建分区和写入文件的方式:
numEntities = applyMapping1.toDF().count()
partitions = applymapping1.repartition(numEntities)
partitions.toDF().write.mode("ignore").format("json").option("header", "true").save("s3://location/test")
使用此文件,一些文件被创建为一个json文件,该文件具有一个接一个的2个对象,有些是正确的,有些是空的。
有什么方法可以确保每个分区创建一个仅包含其数据的单独文件?
答案 0 :(得分:0)
我认为repartition
后面的Partitioner确实不完全符合您的意愿:
它创建了所需数量的分区-到目前为止,一切都很好。但是它并没有将行仅分配到每个分区中。这可能是由于HashPartitioner中的逻辑已经为多个行计算了相同的哈希值。
作为repartition.save...
的替代方法,您可以使用foreachPartition
,然后遍历每一行,将其保存到文件中(例如,在/tmp
下),然后将其上传到S3。在此之前,我不会repartition
,因为将要从foreachPartition
执行的UDF相当昂贵,因此您应尽量减少UDF调用的次数。
这是一个对我有用的例子。它是用Scala编写的:
dynamicFrame.
repartition(1).
toDF().
foreachPartition(p => {
val out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("/tmp/temp.xsv.gz")))
p.foreach(r => {
val row = ...
out.write(row)
})
val s3 = AmazonS3ClientBuilder.standard().withRegion(Regions.EU_CENTRAL_1).build()
val tm = TransferManagerBuilder.standard().withS3Client(s3).build()
val rq = new PutObjectRequest(bucket, key, new File("/tmp/temp.xsv.gz"))
tm.upload(rq).waitForCompletion()
})
答案 1 :(得分:0)
好吧,看来我已经开始工作了。根据{{3}}的回答,我最终使用了foreach来处理数据,但是由于spark的工作原理,我不得不将数据发送到s3。我还必须使用累加器将json字符串存储在foreach中。
class ArrayAccumulator(AccumulatorParam):
def zero(self, value):
return []
def addInPlace(self, val1, val2):
val1.extend(val2)
return val1
jsonAccumulator = sc.accumulator([], ArrayAccumulator())
def write_to_json(row):
# Process json
jsonAccumulator += [json]
mappedDF = applymapping1.toDF()
mappedDF.foreach(write_to_json)
count = 0
for x in jsonAccumulator.value:
s3.Object('bucket-name', 'test/' + str(count) + '.json').put(Body=x)
count += 1