有没有办法将结果传递给驱动程序而无需等待所有分区完成执行?
我是Spark的新手,所以如果有更好的方法,请指出我正确的方向。我想并行执行大量分区并使用spark来处理分发/重启等。操作完成后,我想将结果收集到驱动程序中的单个存档中。
toLocalIterator()
我能够使用toLocalIterator()
执行此操作,according to the docs限制了驱动程序所需的资源。所以它基本上有效。
问题是toLocalIterator()
不仅一次将驱动程序限制为一个分区,而且似乎每次都执行一个分区。这对我没用。下面的演示代码演示了这种行为。
persist()
+ count()
+ toLocalIterator()
我发现通过持久化然后用count()
触发并行执行,我可以解决这个问题。之后,toLocalIterator()
能够快速提取预先计算的结果。
这个问题是我有大量的分区(大约10 ^ 3或10 ^ 4),每个分区的大小约为15分钟。这最终会持续存在大量数据(并不是什么大不了的事)但更糟糕的是,一旦整体工作持续太久,它似乎就会失去持久性。分区最终会重新计算。我正在使用google dataproc和可抢占的工作人员,这可能与它有关,但我很确定它甚至会在固定工人上重新计算...我不确定究竟发生了什么。
在任何情况下,在访问第一个结果之前必须执行所有分区似乎并不理想。
下面的演示代码演示了当一切都持续良好并且迭代不会触发重新计算时的最佳情况。
有类似的吗?
import time
import pyspark.storagelevel
def slow_square(n):
time.sleep(5)
return n**2
with pyspark.SparkContext() as spark_context:
numbers = spark_context.parallelize(range(4), 4) # I think 4 is default executors locally
squares = numbers.map(slow_square)
# Use toLocalIterator()
start = time.time()
list(squares.toLocalIterator())
print('toLocalIterator() took {:.1f} seconds (expected about 5)'.format(time.time() - start))
# I get about 20s
# Use count() to show that it's faster in parallel
start = time.time()
squares.count()
print('count() took {:.1f} seconds (expected about 5)'.format(time.time() - start))
# I get about 5s
# Use persist() + count() + toLocalIterator()
start = time.time()
squares.persist(pyspark.storagelevel.StorageLevel.MEMORY_AND_DISK)
squares.count()
list(squares.toLocalIterator())
print('persisted toLocalIterator() took {:.1f} seconds (expected about 5)'.format(time.time() - start))
# I get about 5s
答案 0 :(得分:2)
一般来说,这不是你通常在Spark中做的事情。通常,我们会尝试将通过驱动程序传递的数据量限制到最小。有两个主要原因:
在正常情况下,您只需让工作继续,写入持久存储并最终对结果应用进一步的处理步骤。
如果您希望能够迭代访问结果,您可以选择以下几种方法:
foreach
/ foreachPartition
处理数据,并在生成数据时将数据推送到外部邮件系统,并使用其他进程进行使用和编写。这需要额外的组件,但在概念上可以更容易(您可以使用背压,缓冲结果,将合并逻辑与驱动程序分开,以最大限度地降低应用程序失败的风险)。Hack Spark累加器。任务完成后更新Spark累加器,以便您以离散批次处理累积的即将到来的数据。
警告:以下代码只是一个概念验证。它没有经过适当的测试,很可能是非常不可靠的。
示例AccumulatorParam
使用RXPy
# results_param.py
from rx.subjects import Subject
from pyspark import AccumulatorParam, TaskContext
class ResultsParam(AccumulatorParam, Subject):
"""An observable accumulator which collects task results"""
def zero(self, v):
return []
def addInPlace(self, acc1, acc2):
# This is executed on the workers so we have to
# merge the results
if (TaskContext.get() is not None and
TaskContext().get().partitionId() is not None):
acc1.extend(acc2)
return acc1
else:
# This is executed on the driver so we discard the results
# and publish to self instead
for x in acc2:
self.on_next(x)
return []
Simple Spark应用程序(Python 3.x):
# main.py
import time
from pyspark import SparkContext, TaskContext
sc = SparkContext(master="local[4]")
sc.addPyFile("results_param.py")
from results_param import ResultsParam
# Define accumulator
acc = sc.accumulator([], ResultsParam())
# Dummy subscriber
acc.accum_param.subscribe(print)
def process(x):
"""Identity proccess"""
result = x
acc.add([result])
# Add some delay
time.sleep(5)
return result
sc.parallelize(range(32), 8).foreach(process)
这相对简单,但如果多个任务同时完成,则存在驱动程序压倒性风险,因此您必须显着超额订阅驱动程序资源(与并行度级别和任务结果的预期大小成比例)。
直接使用Scala runJob
(不支持Python)。
Spark实际上异步获取结果,只要您不关心订单,就不需要等待处理所有数据。您可以看到例如the implementation Scala reduce
。
应该可以使用这种机制将分区推送到Python进程,但我还没有尝试过。