我有一个PCollection[str]
,我想生成随机对。
来自Apache Spark,我的策略是:
但是我似乎找不到一种压缩2个PCollections的方法...
答案 0 :(得分:2)
这很有趣,并且不是很常见的用例,因为正如@chamikara所说,Dataflow中没有订单保证。但是,我考虑过实现一种解决方案,在该解决方案中,您可以混洗输入的PCollection,然后基于state配对连续的元素。我发现了一些注意事项,但我认为仍然值得分享。
首先,我使用了Python SDK,但是Dataflow Runner尚不支持有状态DoFn。它可以与Direct Runner一起使用,但是:1)它不可扩展,并且2)如果不使用多线程,则很难对记录进行洗牌。当然,后者的一个简单解决方案是将已经改组的PCollection馈送到管道中(我们可以使用其他作业来预处理数据)。否则,我们可以使该示例适应Java SDK。
就目前而言,我决定尝试改组并将其与单个管道配对。我真的不知道这是否有助于解决问题或使事情变得更复杂,但是可以在here中找到代码。
简而言之,有状态DoFn会查看缓冲区,如果缓冲区为空,则会将其放入当前元素。否则,它将从缓冲区中弹出前一个元素,并输出(previous_element,current_element)元组:
class PairRecordsFn(beam.DoFn):
"""Pairs two consecutive elements after shuffle"""
BUFFER = BagStateSpec('buffer', PickleCoder())
def process(self, element, buffer=beam.DoFn.StateParam(BUFFER)):
try:
previous_element = list(buffer.read())[0]
except:
previous_element = []
unused_key, value = element
if previous_element:
yield (previous_element, value)
buffer.clear()
else:
buffer.add(value)
管道根据需要将键添加到输入元素以使用有状态DoFn。这里会有一个折衷,因为您可以使用beam.Map(lambda x: (1, x))
将相同的键分配给所有元素。这不能很好地并行化,但这不是问题,因为无论如何我们都在使用Direct Runner(如果使用Java SDK,请记住这一点)。但是,它不会随机播放记录。相反,如果改用大量键,则会得到大量无法配对的“孤立”元素(因为每个键都保留了状态,并且我们随机分配它们,所以我们可以有奇数个记录每个键):
pairs = (p
| 'Create Events' >> beam.Create(data)
| 'Add Keys' >> beam.Map(lambda x: (randint(1,4), x))
| 'Pair Records' >> beam.ParDo(PairRecordsFn())
| 'Check Results' >> beam.ParDo(LogFn()))
就我而言,我得到了类似的东西:
INFO:root:('one', 'three')
INFO:root:('two', 'five')
INFO:root:('zero', 'six')
INFO:root:('four', 'seven')
INFO:root:('ten', 'twelve')
INFO:root:('nine', 'thirteen')
INFO:root:('eight', 'fourteen')
INFO:root:('eleven', 'sixteen')
...
编辑:我想到了使用Sample.FixedSizeGlobally
组合器的另一种方法。好处是,它可以更好地对数据进行洗牌,但是您需要先验知道元素的数量(否则我们需要对数据进行一次初始传递),并且似乎将所有元素一起返回。简要地说,我两次初始化相同的PCollection,但是应用了不同的洗牌顺序,并在有状态的DoFn中分配索引。这将确保索引在同一PCollection中的元素之间是唯一的(即使无法保证顺序)。在我的情况下,两个PCollections对于[0,31]范围内的每个键都只有一条记录。 CoGroupByKey转换将在同一索引上连接两个PCollection,因此具有随机的元素对:
pc1 = (p
| 'Create Events 1' >> beam.Create(data)
| 'Sample 1' >> combine.Sample.FixedSizeGlobally(NUM_ELEMENTS)
| 'Split Sample 1' >> beam.ParDo(SplitFn())
| 'Add Dummy Key 1' >> beam.Map(lambda x: (1, x))
| 'Assign Index 1' >> beam.ParDo(IndexAssigningStatefulDoFn()))
pc2 = (p
| 'Create Events 2' >> beam.Create(data)
| 'Sample 2' >> combine.Sample.FixedSizeGlobally(NUM_ELEMENTS)
| 'Split Sample 2' >> beam.ParDo(SplitFn())
| 'Add Dummy Key 2' >> beam.Map(lambda x: (2, x))
| 'Assign Index 2' >> beam.ParDo(IndexAssigningStatefulDoFn()))
zipped = ((pc1, pc2)
| 'Zip Shuffled PCollections' >> beam.CoGroupByKey()
| 'Drop Index' >> beam.Map(lambda (x, y):y)
| 'Check Results' >> beam.ParDo(LogFn()))
完整代码here
结果:
INFO:root:(['ten'], ['nineteen'])
INFO:root:(['twenty-three'], ['seven'])
INFO:root:(['twenty-five'], ['twenty'])
INFO:root:(['twelve'], ['twenty-one'])
INFO:root:(['twenty-six'], ['twenty-five'])
INFO:root:(['zero'], ['twenty-three'])
...
答案 1 :(得分:0)
将ParDo转换应用于将键附加到元素的两个PCollection并通过CoGroupByKey转换运行两个PCollection怎么样?
请注意,Beam不保证PCollection中元素的顺序,因此输出元素在任何步骤之后都可能会重新排序,但是对于您的用例来说似乎应该可以,因为您只需要一些随机顺序即可。