使用 Beam,如 docs 中所述,每个键和窗口都保持状态。因此,您无法访问以前窗口中的值。
要完成您想做的事情,您可能需要更复杂的管道设计。我的想法(在这里作为示例开发)是在 ParDo 中复制您的消息:
- 将它们不加修改地发送到主输出
- 同时,将它们发送到具有单窗口延迟的侧面输出
要执行第二个要点,我们可以将窗口的持续时间 (WINDOW_SECONDS) 添加到元素时间戳:
class DuplicateWithLagDoFn(beam.DoFn):
def process(self, element, timestamp=beam.DoFn.TimestampParam):
# Main output gets unmodified element
yield element
# The same element is emitted to the side output with a 1-window lag added to timestamp
yield beam.pvalue.TaggedOutput('lag_output', beam.window.TimestampedValue(element, timestamp + WINDOW_SECONDS))
我们调用指定正确标签的函数:
beam.ParDo(DuplicateWithLagDoFn()).with_outputs('lag_output', main='main_output')
然后将相同的窗口方案应用于两者,按键共同分组等。
windowed_main = results.main_output | 'Window main output' >> beam.WindowInto(window.FixedWindows(WINDOW_SECONDS))
windowed_lag = results.lag_output | 'Window lag output' >> beam.WindowInto(window.FixedWindows(WINDOW_SECONDS))
merged = (windowed_main, windowed_lag) | 'Join Pcollections' >> beam.CoGroupByKey()
最后,我们可以在同一个 ParDo 中同时拥有两个值(旧的和新的):
class CompareDoFn(beam.DoFn):
def process(self, element):
logging.info("Combined with previous vale: {}".format(element))
try:
old_value = int(element[1][1][0].split(',')[1])
except:
old_value = 0
try:
new_value = int(element[1][0][0].split(',')[1])
except:
new_value = 0
logging.info("New value: {}, Old value: {}, Difference: {}".format(new_value, old_value, new_value - old_value))
return (element[0], new_value - old_value)
为了测试这一点,我使用直接运行器运行管道,并在单独的 shell 上发布两条消息,间隔超过 10 秒(在我的情况下,WINDOW_SECONDS 是 10 秒):
gcloud pubsub topics publish lag --message="test,120"
sleep 12
gcloud pubsub topics publish lag --message="test,40"
并且作业输出显示了预期的差异:
INFO:root:New message: (u'test', u'test,120')
INFO:root:Combined with previous vale: (u'test', ([u'test,120'], []))
INFO:root:New value: 120, Old value: 0, Difference: 120
INFO:root:New message: (u'test', u'test,40')
INFO:root:Combined with previous vale: (u'test', ([u'test,40'], [u'test,120']))
INFO:root:New value: 40, Old value: 120, Difference: -80
INFO:root:Combined with previous vale: (u'test', ([], [u'test,40']))
INFO:root:New value: 0, Old value: 40, Difference: -40
我的示例 here 的完整代码。在复制元素时要考虑性能因素,但如果您需要在两个窗口期间提供可用的值,这很有意义。