根据唯一密钥创建不同的会话

时间:2019-04-09 05:30:59

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

我从kafka主题获取消息,该主题向我发送JSON消息。我想从该json消息中提取一个字段(可以是一个ID),然后我想为'n'个唯一设备ID创建'n'个会话

我尝试为我收到的每个唯一ID创建一个新的会话实例,但是在创建新的会话窗口实例(即在管道中为每个ID创建一个新的分支)之后,我无法将下一个即将出现的消息推送到已经存在的相应分支。

我想要的预期结果是,假设我们收到这样的消息

  

{ID:1,...},{ID:2,...},{ID:3,...},{ID:1,...}

将创建三个不同的会话,第四个消息将转到设备ID为1的会话。 在Apache Beam编程范式或Java编程范式中,有没有办法做到这一点?任何帮助将不胜感激。

2 个答案:

答案 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建议的方法,但是我认为分享一个有效的示例很好。


Java版本:

我们可以从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));
}

请注意,我们添加了一些额外的内容:

  • 在数据中不存在自定义间隔的情况下的默认
  • 一种将主管道中的属性设置为自定义窗口的方法。

withDefaultGapDurationwithGapAttribute方法是:

/** 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()))

视觉上:

enter image description here

对于桌面用户,只有两个事件间隔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版本:

类似地,我们可以将相同的方法扩展到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的测试