Apache Beam-Python:如何通过累积获取PCollection的前10个元素?

时间:2019-06-16 06:15:44

标签: python google-cloud-dataflow apache-beam

我想这样提取前10名最高得分:

Paul - 38
Michel - 27
Hugo - 27
Bob - 24
Kevin - 19
...
(10 elements)

我正在使用固定的窗口和数据驱动的触发器,该触发器在窗格收集了X个元素之后输出早期结果。 另外,我正在使用组合器获得最高的10个最高得分。

(inputs
         | 'Apply Window of time' >> beam.WindowInto(
                        beam.window.FixedWindows(size=5 * 60))
                        trigger=trigger.Repeatedly(trigger.AfterCount(2)),
                  accumulation_mode=trigger.AccumulationMode.ACCUMULATING)
         | beam.ParDo(PairWithOne()) # ('key', 1)
         | beam.CombinePerKey(sum)
         | 'Top 10 scores' >> beam.CombineGlobally(
                        beam.combiners.TopCombineFn(n=10,
                                                    compare=lambda a, b: a[1] < b[
                                                        1])).without_defaults())

这里的问题是,第一个输出似乎是正确的,但随后的输出包含类似的重复键:

Paul - 38
Paul - 37
Michel - 27
Paul - 36
Michel - 26
Kevin - 20
...
(10 elements)

如您所见,我没有得到10个不同的K / V对,但是按键重复。

当不使用触发/累加策略时,此方法效果很好。.但是,如果我希望有2个小时的窗口,我想经常进行更新...

1 个答案:

答案 0 :(得分:1)

如评论中所述,一种可能性是过渡到Discarding fired panes,可以通过accumulation_mode=trigger.AccumulationMode.DISCARDING进行设置。如果仍要保持ACCUMULATING模式,则可能需要修改TopCombineFn,以使来自同一用户的重复窗格会覆盖以前的值,并避免重复的键。 TopDistinctFn将以Beam SDK 2.13.0的代码here为基础。在add_input方法中,我们将进行如下检查:

for current_top_element in enumerate(heap):
  if element[0] == current_top_element[1].value[0]:
    heap[current_top_element[0]] = heap[-1]
    heap.pop()
    heapq.heapify(heap)

基本上,我们将要评估的元素(element[0])与堆中的每个元素的键进行比较。堆元素的类型为ComparableValue,因此我们可以使用value来获取元组(和value[0]来获取键)。如果它们匹配,我们将要从堆中弹出它(因为我们累加总和会更大)。 Beam SDK使用heapq库,因此我基于this answer的方法删除了i-th元素(我们使用enumerate来保留索引信息)。

我添加了一些日志记录以帮助检测重复项:

logging.info("Duplicate: " + element[0] + "," + str(element[1]) + ' --- ' + current_top_element[1].value[0] + ',' + str(current_top_element[1].value[1]))

该代码位于top.py文件夹(带有combiners)的__init__.py文件中,我使用以下命令导入它:

from combiners.top import TopDistinctFn

然后,我可以在管道中使用TopDistinctFn,如下所示:

(inputs
     | 'Add User as key' >> beam.Map(lambda x: (x, 1)) # ('key', 1)
     | 'Apply Window of time' >> beam.WindowInto(
                    beam.window.FixedWindows(size=10*60),
                    trigger=beam.trigger.Repeatedly(beam.trigger.AfterCount(2)),
                    accumulation_mode=beam.trigger.AccumulationMode.ACCUMULATING)
     | 'Sum Score' >> beam.CombinePerKey(sum)   
     | 'Top 10 scores' >> beam.CombineGlobally(
                    TopDistinctFn(n=10, compare=lambda a, b: a[1] < b[1])).without_defaults()
     | 'Print results' >> beam.ParDo(PrintTop10Fn()))

完整代码可在here中找到。 generate_messages.py是发布/订阅消息生成器,top.py包含已重命名为TopCombineFn的{​​{1}}的自定义版本(可能看起来不知所措,但我仅从第几行开始添加了几行代码425)和TopDistinctFn主管道代码。要运行此文件,可以将文件放在正确的文件夹中,如果需要,请安装Beam SDK 2.13.0,在test_combine.pygenerate_messages.py中修改项目ID和Pub / Sub主题。然后,开始使用test_combine-py发布消息,并在另一个shell中使用python generate_messages.py运行管道:DirectRunner。使用python test_combine.py --streaming,您可能需要在DataflowRunner文件中添加extra files

例如,setup.py领先9分,当下一次更新时,他的得分高达11分。他将出现在下一个摘要中,仅包含更新的得分,没有重复(如在我们的日志记录中检测到的)。 9点的条目将不会出现,并且顶部仍然会根据需要保留10个用户。 Bob也是如此。我注意到,即使得分排在前10位,得分也仍然会出现在堆中,但是我不确定Marta的垃圾回收如何工作。

heapq

让我知道这对您的用例是否也很好。