我有一个我从Spark访问的数据集(例如Parquet文件),它包含大量行。我需要将这些行中的一些数据发送到外部服务,我需要对它们进行批处理,以便每次对外部服务的调用都包含一定数量的行(例如,每批1000行)。基本上take(n)
正在做什么,但是在大型数据集上重复,迭代地进行。执行此类任务的好方法是什么?我想可以用foreach()
完成并手动批量聚合数据,但我想知道是否有任何内置/推荐的方法。
答案 0 :(得分:5)
我不知道任何内置或推荐的选项,但简单的解决方案是结合RDD API和Scala Iterable
API。如果你申请的操作是幂等的,你可以直接从工人那里做到:
val batchSize: Int = ???
val rdd: RDD[T] = ???
def doSomething(xs: Seq[T]) = ???
rdd.foreachPartition(_.grouped(batchSize).foreach(doSomething))
否则,您可以在此时向驱动程序提取单个分区:
rdd.cache
rdd.toLocalIterator.grouped(batchSize).foreach(doSomething)
请注意,每个分区需要单独的作业,因此最好先缓存输入rdd以避免重新计算。
在Python中,您可以使用toolz
库替代Iterator
API:
from toolz.itertoolz import partition_all
rdd.foreachPartition(
lambda iter: [doSomething(xs) for xs in partition_all(batchSize, iter)])
或
for xs in partition_all(batchSize, rdd.toLocalIterator()):
doSomething(xs)
答案 1 :(得分:4)
当您从镶木地板文件创建DataFrame时,它会根据HDFS块位置进行分区。
首先要问的是,您是否可以将数据集并行写入外部服务。即同时从多个服务器发送1000行的批次。
如果可以,那么最有效的方法是foreachPartition
功能。类似的东西:
df.rdd.forEachPartition { it =>
it.grouped(1000).foreach(sendBatch)
}
如果您的外部服务无法以这种方式使用,那么第二个最佳选择是toLocalIterator
:
df.rdd.toLocalIterator { it =>
it.grouped(1000).foreach(sendBatch)
}
请注意,此解决方案的效率明显较低,因为它会序列化每个分区并将其从执行程序传输到驱动程序。