我有一个传入的事件流,每个事件都有一个来自另一个进程的关联sessionId。
我想做的就是使用自定义CombineFn将这些事件组合成一个会话对象。
在开发过程中,我正在使用从文件中读取的有界数据集,以下代码似乎有效:
let json = {
a: [1, 2, 3],
b: [], // omit b
c: 1,
d: "test&encoding", // uriencode
e: [[4,5],[6,7]], // flatten this
f: null, // omit nulls
g: 0
};
let qs = to_qs(json)
=> "a=1&a=2&a=3&c=1&d=test%26encoding&e=4&e=5&e=6&e=7&g=0"
上述代码(带输入/输出处理)将输出一系列会话,每个会话中包含多个事件。
input.apply(ParDo.named("ParseEvent").of(new ParseEventFn()))
.setCoder(KvCoder.of(StringUtf8Coder.of(), AvroCoder.of(Event.class)))
.apply(GroupByKey.<String, Event>create())
.apply(Combine.groupedValues(new SessionAccumulator()))
但为了使其能够在无界数据集上工作,我需要应用一个Windowing函数,在本例中是一个SessionWindow。
{sessionId: 1, events: [event1,event2,event3]}
{sessionId: 2, events: [event4,event5]}
在这种情况下,唯一的新代码是Windowing函数,而不是汇总事件,我将每个事件都放在它自己的会话中,如下所示:
input.apply(ParDo.named("ParseEvent").of(new ParseEventFn()))
.setCoder(KvCoder.of(StringUtf8Coder.of(), AvroCoder.of(Event.class)))
.apply(Window.<KV<String, Event>>into(Sessions.withGapDuration(Duration.standardMinutes(30))))
.apply(GroupByKey.<String, Event>create())
.apply(Combine.groupedValues(new SessionAccumulator()))
知道为什么会这样吗?
编辑:我应该补充一点,ParseEventFn正在使用context.outputWithTimestamp()将时间戳应用于PCollection,并且该时间戳似乎是正确的。
答案 0 :(得分:2)
在您的情况下,您可以编写自己的WindowFn
。如果您将密钥设置为会话ID,那么大间隙持续时间也可以,但它也不能反映数据和计算的性质。
WindowFn
的要素是:
BoundedWindow
子类,在这种情况下,您将创建一个包含字段中会话ID的窗口类型assignWindows
,您可以在其中将每个元素分配给由会话ID标识的窗口。窗口的长度仍然很重要,因为它控制窗口何时到期并被垃圾收集。mergeWindows
,您将合并具有相同会话ID的所有窗口。它们不必落入任何特定的间隙期限内。您需要注意的另一件事是,控制这些窗口的垃圾收集的水印是由您的无限事件流的来源决定的。因此,在ParDo.of(new ParseEventFn())
中设置时间戳对于影响水印来说为时已晚。您可能会丢弃要保留的数据。
答案 1 :(得分:1)
进一步深入研究,在我看来,我的核心假设是时间戳是正确的,这是错误的。
我在窗口之前应用的时间戳是错误的。
Windowing正在做它应该做的事情,但我把时间戳设置得太远了,它为每个事件创建了单独的会话。
糟糕