我从kafka主题获取消息,该主题向我发送JSON消息。我想从该json消息中提取一个字段(可以是一个ID),然后我想为'n'个唯一设备ID创建'n'个会话。
我尝试为我收到的每个唯一ID创建一个新的会话实例,但是在创建新的会话窗口实例(即在管道中为每个ID创建一个新的分支)之后,我无法将下一个即将出现的消息推送到已经存在的相应分支。
我想要的预期结果是,假设我们收到这样的消息
{ID:1,...},{ID:2,...},{ID:3,...},{ID:1,...}
将创建三个不同的会话,第四个消息将转到设备ID为1的会话。 在Apache Beam编程范式或Java编程范式中,有没有办法做到这一点?任何帮助将不胜感激。
答案 0 :(得分:2)
是的,如果您使用自定义WindowFn
,则使用Beam范例是可能的。您可以对Sessions类进行子类化并对其进行修改,以根据每个元素的ID来不同地设置间隙持续时间。您可以在assignWindows
中执行此操作,就像在Sessions
中这样:
@Override
public Collection<IntervalWindow> assignWindows(AssignContext c) {
// Assign each element into a window from its timestamp until gapDuration in the
// future. Overlapping windows (representing elements within gapDuration of
// each other) will be merged.
return Arrays.asList(new IntervalWindow(c.timestamp(), gapDuration));
}
AssignContext
类可用于访问为此窗口分配的元素,这将允许您检索该元素的ID。
听起来您还希望将具有不同ID的元素分组到不同的窗口中(即,如果元素A和B进入间隔持续时间但具有不同ID的元素,它们仍应位于不同的窗口中)。这可以通过使用元素的ID作为键执行GroupByKey
来完成。会话窗口基于每个键as described in the Beam Programming Guide进行应用,因此这将按ID分隔元素。
答案 1 :(得分:0)
我已经为此用例实现了Java和Python示例。 Java语言遵循了Daniel Oliveira建议的方法,但是我认为分享一个有效的示例很好。
我们可以从Session窗口修改代码以适合我们的用例。
简而言之,当记录进入会话时,它们将被分配到一个从元素的时间戳记开始的窗口(未对齐的窗口),并将间隔时间添加到起点以计算终点。然后,mergeWindows
函数将合并每个键的所有重叠窗口,从而导致会话扩展。
我们需要修改assignWindows
函数以创建一个具有数据驱动间隙而不是固定持续时间的窗口。我们可以通过WindowFn.AssignContext.element()
访问该元素。原始分配函数是:
public Collection<IntervalWindow> assignWindows(AssignContext c) {
// Assign each element into a window from its timestamp until gapDuration in the
// future. Overlapping windows (representing elements within gapDuration of
// each other) will be merged.
return Arrays.asList(new IntervalWindow(c.timestamp(), gapDuration));
}
修改后的功能将是:
@Override
public Collection<IntervalWindow> assignWindows(AssignContext c) {
// Assign each element into a window from its timestamp until gapDuration in the
// future. Overlapping windows (representing elements within gapDuration of
// each other) will be merged.
Duration dataDrivenGap;
JSONObject message = new JSONObject(c.element().toString());
try {
dataDrivenGap = Duration.standardSeconds(Long.parseLong(message.getString(gapAttribute)));
}
catch(Exception e) {
dataDrivenGap = gapDuration;
}
return Arrays.asList(new IntervalWindow(c.timestamp(), dataDrivenGap));
}
请注意,我们添加了一些额外的内容:
withDefaultGapDuration
和withGapAttribute
方法是:
/** Creates a {@code DynamicSessions} {@link WindowFn} with the specified gap duration. */
public static DynamicSessions withDefaultGapDuration(Duration gapDuration) {
return new DynamicSessions(gapDuration, "");
}
public DynamicSessions withGapAttribute(String gapAttribute) {
return new DynamicSessions(gapDuration, gapAttribute);
}
我们还将添加一个新字段(gapAttribute
)和构造函数:
public class DynamicSessions extends WindowFn<Object, IntervalWindow> {
/** Duration of the gaps between sessions. */
private final Duration gapDuration;
/** Pub/Sub attribute that modifies session gap. */
private final String gapAttribute;
/** Creates a {@code DynamicSessions} {@link WindowFn} with the specified gap duration. */
private DynamicSessions(Duration gapDuration, String gapAttribute) {
this.gapDuration = gapDuration;
this.gapAttribute = gapAttribute;
}
然后,我们可以使用以下方法将消息显示到新的自定义会话中:
.apply("Window into sessions", Window.<String>into(DynamicSessions
.withDefaultGapDuration(Duration.standardSeconds(10))
.withGapAttribute("gap"))
为了对此进行测试,我们将使用一个带有受控输入的简单示例。对于我们的用例,我们将根据运行应用程序的设备来考虑用户的不同需求。桌面用户可以长时间闲置(允许更长的会话),而我们仅希望移动用户的会话为短时间。我们生成一些模拟数据,其中一些消息包含gap
属性,而另一些消息则忽略它(间隙窗口将对这些属性使用默认值):
.apply("Create data", Create.timestamped(
TimestampedValue.of("{\"user\":\"mobile\",\"score\":\"12\",\"gap\":\"5\"}", new Instant()),
TimestampedValue.of("{\"user\":\"desktop\",\"score\":\"4\"}", new Instant()),
TimestampedValue.of("{\"user\":\"mobile\",\"score\":\"-3\",\"gap\":\"5\"}", new Instant().plus(2000)),
TimestampedValue.of("{\"user\":\"mobile\",\"score\":\"2\",\"gap\":\"5\"}", new Instant().plus(9000)),
TimestampedValue.of("{\"user\":\"mobile\",\"score\":\"7\",\"gap\":\"5\"}", new Instant().plus(12000)),
TimestampedValue.of("{\"user\":\"desktop\",\"score\":\"10\"}", new Instant().plus(12000)))
.withCoder(StringUtf8Coder.of()))
视觉上:
对于桌面用户,只有两个事件间隔12秒。没有指定间隔,因此默认为10秒,并且两个分数都属于不同的会话,因此不会相加。
另一个用户(移动用户)有4个事件,分别间隔2、7和3秒。时间间隔都没有大于默认间隔,因此在标准会话中,所有事件都属于一个会话,且得分加18:
user=desktop, score=4, window=[2019-05-26T13:28:49.122Z..2019-05-26T13:28:59.122Z)
user=mobile, score=18, window=[2019-05-26T13:28:48.582Z..2019-05-26T13:29:12.774Z)
user=desktop, score=10, window=[2019-05-26T13:29:03.367Z..2019-05-26T13:29:13.367Z)
对于新的会话,我们为这些事件指定5秒的“间隔”属性。第三条消息比第二条消息晚7秒,因此现在进入另一个会话。上一个得分18的大型会议将分为两个9分会议:
user=desktop, score=4, window=[2019-05-26T14:30:22.969Z..2019-05-26T14:30:32.969Z)
user=mobile, score=9, window=[2019-05-26T14:30:22.429Z..2019-05-26T14:30:30.553Z)
user=mobile, score=9, window=[2019-05-26T14:30:33.276Z..2019-05-26T14:30:41.849Z)
user=desktop, score=10, window=[2019-05-26T14:30:37.357Z..2019-05-26T14:30:47.357Z)
完整代码here。使用Java SDK 2.13.0进行了测试
类似地,我们可以将相同的方法扩展到Python SDK。 Sessions
类的代码可以在here中找到。我们将定义一个新的DynamicSessions
类。在assign
方法内,我们可以使用context.element
访问处理后的记录,并根据数据修改间隔。
原文:
def assign(self, context):
timestamp = context.timestamp
return [IntervalWindow(timestamp, timestamp + self.gap_size)]
扩展:
def assign(self, context):
timestamp = context.timestamp
try:
gap = Duration.of(context.element[1][“gap”])
except:
gap = self.gap_size
return [IntervalWindow(timestamp, timestamp + gap)]
如果输入数据包含一个gap
字段,它将使用它来覆盖默认的间隙大小。在我们的管道代码中,我们只需要将事件窗口放入DynamicSessions
而不是标准的Sessions
:
'user_session_window' >> beam.WindowInto(DynamicSessions(gap_size=gap_size),
timestamp_combiner=window.TimestampCombiner.OUTPUT_AT_EOW)
在标准会话中,输出如下:
INFO:root:>> User mobile had 4 events with total score 18 in a 0:00:22 session
INFO:root:>> User desktop had 1 events with total score 4 in a 0:00:10 session
INFO:root:>> User desktop had 1 events with total score 10 in a 0:00:10 session
使用我们的自定义窗口,移动事件被分为两个不同的会话:
INFO:root:>> User mobile had 2 events with total score 9 in a 0:00:08 session
INFO:root:>> User mobile had 2 events with total score 9 in a 0:00:07 session
INFO:root:>> User desktop had 1 events with total score 4 in a 0:00:10 session
INFO:root:>> User desktop had 1 events with total score 10 in a 0:00:10 session
所有文件here。经过Python SDK 2.13.0的测试