Python Apache Beam每用户会话窗口未合并

Python Apache Beam每用户会话窗口未合并,python,google-cloud-dataflow,apache-beam,Python,Google Cloud Dataflow,Apache Beam,我们有一个有用户的应用程序;每个用户每次使用我们的应用程序的时间大约为10-40分钟,我想根据发生的特定事件(例如,“此用户已转换”、“此用户上次会话有问题”、“此用户上次会话成功”)统计每个会话中发生的事件的分布/发生次数 (在此之后,我想每天统计这些更高级别的事件,但这是一个单独的问题) 为此,我一直在查看会话窗口;但所有这些似乎都是面向全局会话窗口的,但我想按用户创建它们(这也是一种自然分区) 我很难找到关于如何做到这一点的文档(首选python)。你能给我指一下正确的方向吗 或者换句话说

我们有一个有用户的应用程序;每个用户每次使用我们的应用程序的时间大约为10-40分钟,我想根据发生的特定事件(例如,“此用户已转换”、“此用户上次会话有问题”、“此用户上次会话成功”)统计每个会话中发生的事件的分布/发生次数

(在此之后,我想每天统计这些更高级别的事件,但这是一个单独的问题)

为此,我一直在查看会话窗口;但所有这些似乎都是面向全局会话窗口的,但我想按用户创建它们(这也是一种自然分区)

我很难找到关于如何做到这一点的文档(首选python)。你能给我指一下正确的方向吗

或者换句话说:如何创建每个用户每个会话的窗口,以输出更结构化(丰富的)事件?

我所拥有的 它输出什么
如你所见;Session()窗口不会展开窗口,但只将非常接近的事件分组在一起。。。做错了什么?

您可以通过在窗口打开后添加“按键分组”转换使其工作。您已经为记录分配了键,但实际上还没有按键将它们组合在一起,而会话窗口(按键工作)不知道这些事件需要合并在一起

为了证实这一点,我用一些内存中的虚拟数据做了一个可复制的示例(将发布/订阅与问题隔离开来,并能够更快地进行测试)。所有五个事件都将具有相同的键或
用户id
,但它们将按顺序彼此间隔1、2、4和8秒“到达”。当我使用5秒的
session\u gap
时,我希望前4个元素合并到同一个session中。第五场比赛将在第四场比赛后8秒进行,因此必须降级到下一场比赛(差距超过5秒)。数据是这样创建的:

data=[{'user_id':'Thanos','value':'event_{}.格式(event),'timestamp':范围(5)内事件的time.time()+2**event}
我们使用
beam.Create(data)
初始化管道,并使用
beam.window.TimestampedValue
分配“假”时间戳。同样,我们只是用这个来模拟流行为。之后,我们通过
user\u id
字段创建键值对,我们打开
window.Sessions
,然后添加缺少的
beam.GroupByKey()
步骤。最后,我们使用稍微修改过的
DebugPrinter
::版本记录结果。管道现在看起来如下所示:

events=(p
|“创建事件”>>beam.Create(数据)\
|'addtimestamps'>>beam.Map(lambda x:beam.window.TimestampedValue(x,x['timestamp']))\
|'keyed_on_user_id'>>beam.Map(lambda x:'x['user_id'],x))
|'user\u session\u window'>>beam.WindowInto(window.Sessions(session\u gap)),
timestamp_combiner=window.TimestampCombiner.OUTPUT_AT_EOW)\
|'Group'>>beam.GroupByKey()
|'debug_printer'>>beam.ParDo(DebugPrinter()))
其中
DebugPrinter
为:

类调试打印机(beam.DoFn):
“”“仅打印带有日志记录的元素”“”
def过程(自身、元素、窗口=beam.DoFn.WindowParam):
对于元素[1]中的x:
logging.info(“>>>接收到%s%s,窗口=%s”,x['value'],x['timestamp'],窗口)
屈服要素
如果我们在不按键分组的情况下进行测试,我们会得到相同的行为:

INFO:root:>>>接收到的事件_01554117323.0,窗口=[1554117323.0,1554117328.0)
信息:root:>>>接收到事件_1 1554117324.0,窗口=[1554117324.0,1554117329.0)
信息:root:>>>接收到事件_2 1554117326.0,窗口=[1554117326.0,1554117331.0)
信息:root:>>>接收到事件_3 1554117330.0,窗口=[1554117330.0,1554117335.0)
信息:root:>>>接收到事件_4 1554117338.0,窗口=[1554117338.0,1554117343.0)
但添加后,窗口现在可以按预期工作。事件0到3合并在一个扩展的12s会话窗口中。事件4属于一个单独的5s会话

INFO:root:>>>接收到的事件\u 0 1554118377.37,窗口=[1554118377.37,1554118389.37)
信息:root:>>>接收到事件_1 1554118378.37,窗口=[1554118377.37,1554118389.37)
信息:root:>>>接收到事件_3 1554118384.37,窗口=[1554118377.37,1554118389.37)
信息:root:>>>接收到事件_2 1554118380.37,窗口=[1554118377.37,1554118389.37)
信息:root:>>>接收到事件_4 1554118392.37,窗口=[1554118392.37,1554118397.37)
完整代码

另外还有两件事值得一提。第一件是,即使使用DirectRunner在一台机器上本地运行此程序,记录也可能是无序的(在我的例子中,事件_3在事件_2之前处理)。这样做的目的是模拟所记录的分布式处理

最后一个是,如果得到如下堆栈跟踪:

TypeError:无法将GlobalWindow转换为apache_beam.utils.windowed_值。_IntervalWindowBase[在运行'Write Results/Write/WriteImpl/WriteBundles'时]

从2.10.0/2.11.0 SDK降级到2.9.0。请参见此示例。

在窗口转换之前添加一个ParDo如何?此ParDo将从传入流中创建键值对,其中键值将是用户Id。因此,我们有一组对,我们[每x分钟]将其送入窗口进行聚合。然后,您可以按键[用户id]分组或组合窗口的输出我想它已经在那里了;它被称为
keyed_on_user_id
。如果您发现了这一点,请告诉我。谢谢!会话窗口应该按每个键按指定的间隔大小对一系列连续事件进行分组。您给了一个5分钟的间隔,因此值应该是一个键的所有项目,如果这是第一次此键的tem显示时间或上次显示键的时间在过去超过5分钟。值将为grou
class DebugPrinter(beam.DoFn):
  """Just prints the element with logging"""
  def process(self, element, window=beam.DoFn.WindowParam):
    _, x = element
    logging.info(">>> Received %s %s with window=%s", x['jsonPayload']['value'], x['timestamp'], window)
    yield element

def sum_by_event_type(user_session_events):
  logging.debug("Received %i events: %s", len(user_session_events), user_session_events)
  d = {}
  for key, group in groupby(user_session_events, lambda e: e['jsonPayload']['value']):
    d[key] = len(list(group))
  logging.info("After counting: %s", d)
  return d

# ...

by_user = valid \
  | 'keyed_on_user_id'      >> beam.Map(lambda x: (x['jsonPayload']['userId'], x))

session_gap = 5 * 60 # [s]; 5 minutes

user_sessions = by_user \
  | 'user_session_window'   >> beam.WindowInto(beam.window.Sessions(session_gap),
                                               timestamp_combiner=beam.window.TimestampCombiner.OUTPUT_AT_EOW) \
  | 'debug_printer'         >> beam.ParDo(DebugPrinter()) \
  | beam.CombinePerKey(sum_by_event_type)
INFO:root:>>> Received event_1 2019-03-12T08:54:29.200Z with window=[1552380869.2, 1552381169.2)
INFO:root:>>> Received event_2 2019-03-12T08:54:29.200Z with window=[1552380869.2, 1552381169.2)
INFO:root:>>> Received event_3 2019-03-12T08:54:30.400Z with window=[1552380870.4, 1552381170.4)
INFO:root:>>> Received event_4 2019-03-12T08:54:36.300Z with window=[1552380876.3, 1552381176.3)
INFO:root:>>> Received event_5 2019-03-12T08:54:38.100Z with window=[1552380878.1, 1552381178.1)