22 · 事件时间:迟到、窗口与历史连接
目标:为持续到达的报名事件定义窗口完成条件与迟到修正规则。
发生与被处理是两个时间
报名在 09:00 发生,网络恢复后 09:10 才到统计系统。按处理时间计入 09:10,反映系统何时处理;按事件时间计入 09:00,反映业务何时发生。没有一个口径天然正确,应该由问题决定。
流是无界的,无法像批处理一样等全部输入结束。固定滚动窗口可把时间切成不重叠区间;滑动窗口让同一事件进入多个区间;会话窗口按一段无事件间隔分组。以事件时间分组时,宣布窗口完成依赖进度估计,而不是已经看见全部世界。
水位线表达对事件时间进度的判断。它可能依据源的显式保证,也可能是启发式估计。使用“最大已见时间减允许延迟”时,仍可能遇到更晚到达的旧事件。多源输入通常需要考虑最慢的活跃源;空闲源识别和重新加入也影响判断。
窗口已经关闭,旧事件又来了
保存 stream_time.py。时间单位为秒,窗口宽度为 60;水位线为最大已见事件时间减 30。迟到策略是更新旧窗口并记录修正。先预测最后一条事件是否应改变 [0, 60) 的值。
events = [("e1", 10), ("e2", 80), ("e3", 130), ("e4", 20)]
counts, published, corrections = {}, {}, []
max_seen, watermark = -1, -1
for event_id, event_time in events:
window = event_time // 60 * 60
counts[window] = counts.get(window, 0) + 1
if window in published:
published[window] = counts[window]
corrections.append((window, counts[window]))
max_seen = max(max_seen, event_time)
watermark = max_seen - 30
for start, count in sorted(counts.items()):
if start + 60 <= watermark and start not in published:
published[start] = count
assert published == {0: 2}
assert corrections == [(0, 2)]
assert counts[60] == counts[120] == 1
print("published:", published)
print("corrections:", corrections)
print("watermark:", watermark)
uv run --python 3.12 --no-project stream_time.py
预期输出:
published: {0: 2}
corrections: [(0, 2)]
watermark: 100
时间 130 把水位线推进到 100,足以发布结束于 60 的窗口。随后事件时间 20 到达,已发布值从 1 改为 2。结束于 120 的窗口仍未达到发布条件。输入列表结束只是模拟器停止,不代表真实流可以自动关闭所有窗口。
本例的事件 ID 互不重复。实际流需要第 21 课的去重与状态恢复;窗口状态不能无限保留,因此还需制定保留期,以及超过保留期的迟到事件走补算还是丢弃并告警。
引导练习:① 把允许延迟从 30 改成 90;② 水位线为 40,published 与 corrections 都为空;③ 对应修改断言。等待更久减少过早发布,却增加出结果的延迟。
连接也要指定时间
流—流连接匹配一段时间内的两类事件,例如报名后 10 分钟内的核销;流—表连接用活动信息丰富报名;表—表连接持续维护两个变化表的连接视图。三者都要保存状态,并确定何时可以清理。
活动从北城搬到南城,昨天报名今天重放。如果连接“当前活动城市”,昨天的日报就变了。要按报名时含义计算,可以在事件中保存城市快照,或者引用特定活动版本;若用带有效期的历史表,查询应按事件时间做 as-of 连接。保留历史版本与只保留最新值的日志压缩策略可能冲突。
独立练习:将迟到策略改成“丢弃已经发布窗口的新事件并计数”,保持原始 30 秒延迟。给出完整的关键替换和预期结果。
参考修改
循环前加 dropped = 0。在计算 window 后、修改 counts 前增加:if window in published: dropped += 1; continue,删除原来的修正分支。循环后应为 published == {0: 1}、corrections == []、dropped == 1。这有意少计,必须把丢弃指标和补算政策告诉日报使用者,不能把它包装为准确总数。
窗口永不输出时检查水位线是否被某个源阻塞;重跑总数变化时检查维度版本与事件去重。在 design.md 确定事件时间来源、窗口区间、允许延迟及修正规则。