Apache Beam核心—触发器规约
来源:互联网 发布:node http 客户端ip 编辑:程序博客网 时间:2024/06/18 10:04
概述
本文公式化的描述了Apache Beam中触发器的语义,然后推导出在实现触发器时的限制。 目标是为Beam Runner开发者和高级的用户提供参考。
动机
大数据中批处理的输出结果是最终的结果,处理时间是在计算过程中的临时使用的。相比之下,流处理更关注在最终结果出来之前的中间结果。可能的方式是对输入数据进行窗口化切分,当窗口数据被认定是已经全部到达的时候,对每一个窗口产生输出,计算出一个推测性的结果,推测性结果收敛于最终结果。举例来说,当数据无限、无序和必定会存在延迟时,一般来说,获取所有的数据,计算最终的准确结果是不可行的。因此,在文档的剩余部分,所有的说明都是基于流处理,其实也一样适用于批处理。
所以如果我们不等待最终结果,我们什么时候应该产生输出?批处理是一个极端(所有数据都达到,不会再变了之后进行计算),另一个极端"每当有新信息时计算输出",换句话说,在每个输入元素之后立即计算并输出。这样做成本很高,收益不见得大,所以一般不建议这么做。不同的场景需要不同的策略。
触发器是让用户在完整性(当前窗口的是否所有数据到达),延迟(等待输出)和计算成本之间的取得平衡的一种机制。触发器以用户指定的方式调节计算步骤之间的数据流。本文并没有深入到这些动机中,而是重新回顾一下,以便给出触发器规范的上下文。本文档的其余部分以语言无关和执行引擎无关的方式,使用公式的形式定义触发器的细节。
触发器是什么?
此处先我们描述的触发器是什么,触发器并不限于Apache Beam中现有的触发器。在本文中使用触发器R (t 表示时间, T 表示类型, 所以使用R)表示1个满足单调性的断言PR,对于一组元素和时间t,断言PR满足条件,可以进行计算并输出结果。断言隐式的是对应于key+window组。
单调性定义如下:
PR({e1, ..., en},t) ⇒ PR({e1, ..., en, ..., en+k},t+Δ)
... 对于任何k(k个任意的新元素)和Δ(任意的一段事件时间)
断言触发产生输出的规约如下:
只要P(<该触发器上次触发以来收到的数据>,<当前Watermark>)成立,则输出结果并移动到下一个(next函数定义)触发器。
触发器用来管理什么时刻系统可以计算并输出结果,关于输出的细节参考Apache Beam中的延迟和窗格设计.
触发器语法
下边的触发器语法的定义,其中包含了大部分已经在Apache Beam中实现的触发器,并在语法中扩展了新的语义(Done).
Trigger
::=
OnceTrigger
一次性触发器
|
AfterEach(Trigger+)
当所有的子触发器Ready之后进入ready状态,当一个子触发器进入Done之后按照顺序移动到下一个触发器。
|
Repeat(Trigger)
当子触发器是Ready之后,输出结果并重置触发器。
|
Trigger.orFinally(OnceTrigger)
无论任何子触发器为Ready 状态,"finally"分支会一直等待直到该分支中的OnceTrigger为Ready,当该分支中的OnceTrigger进入Ready之后,进入完成状态。
OnceTrigger
::=
LeafTrigger
|
Done(LeafTrigger)
1个叶子触发器完成(Done状态)。
|
AfterFirst(OnceTrigger+)
当任何一个子触发器Ready的时候进入Ready状态,然后进入Done状态。
|
AfterAll(OnceTrigger+)
当所有的子触发器为Ready状态的时候,进入Ready状态,然后进入Done状态。
LeafTrigger
::=
ElemCount(k)
当有k个元素到达的时候进入Ready状态,然后进入Done状态。
|
EndOfWindow
当时间超过窗口的末尾是进入Ready状态,然后进入Done状态。
|
SinceEarliestElem(Δ)
当前时间与最早的元素之间的时间超过Δ之后进入Ready状态,然后进入Done状态。
完成
表示1个叶子触发器完成,封装到Done(...)墓碑中。对于复合触发器,完成状态的判断是个递归判断的问题。下边的isDone()函数比较清楚的说明了状态判断的规则。
isDone(Done(...))
=
True
isDone(LeafTrigger)
=
False
isDone(AfterFirst(R1, R2, ...))
=
isDone(R1)∨isDone(R2) ∨ ...
isDone(AfterAll(R1, R2,...))
=
isDone(R1) ∧isDone(R2) ∧ ...
isDone(Repeat(R))
=
False (Repeat确保isDone(R)永远是false)
isDone(AfterEach(R1, R2, …))
=
isDone(R1)∧ isDone(R2) ∧ ...
isDone(R1.orFinally(R2))
=
isDone(R1)∨isDone(R2)
(在Apache Beam的Java SDK中,使用深度优先搜索的索引方式,在BitSet跟踪触发器的完成状态。)
断言
每一个触发器表示一个如下递归定义的断言,并且隐式隶属于为1个key+window组。特别注意AfterEach和orFinally是目前为止最复杂的触发器。
PDone(R)({e1, ..., en},t)
=
True,由于rewind(下边有定义)的存在,一个触发器进入Done状态了,仍然会让复合触发器处于Ready状态。
PElemCount(k)({e1, ..., en},t)
=
n >= k
PEndOfWindow({e1, ..., en},t)
=
t >= 窗口末尾
PSinceEarliestElem(Δ)({e1, ..., en},t)
=
t - Δ >= {e1, ..., en}中的最小时间
PAfterFirst(R1,R2, …)({e1, …, en}, t)
=
PR1({e1, …, en},t) ∨ PR2({e1, …, en},t) ∨ …
PAfterAll(R1,R2, …)({e1, ..., en},t)
=
PR1({e1, …, en},t) ∧ PR2({e1, …, en},t) ∧ …
PAfterEach(R1,R2, …)({e1, ..., en},t)
=
PR({e1, ..., en},t),R是第一个未进入Done状态子触发器
PRepeat(R)({e1, ..., en},t)
=
PR({e1, ..., en},t)
PR1.orFinally(R2)({e1, …, en},t)
=
PR1({e1, …, ek},t) ∨ PR2({e1, …, en},t)
R2触发时{e1, ..., en}必须包含所有的数据元素,R1触发时{ek, ..., en} (k < n)。这意味着需要保存额外的状态,但是在触发之间仍然是单调的。
很明显元素个数不是一个特别的属性-只是元数据的一个例子。
(在Apache Beam的Java SDK中实现为shouldFire()方法。)
触发进度
当PR 成立的时候,可以计算行发出结果。在这个时刻,触发器可以生成一个新的触发器来计算下一个输出。下边给出了next 的定义,来判断触发器的断言条件是否满足和触发器尚未进入Done状态。
next:
next(R)
=
Done(R) 如果R是叶子触发器。
next(AfterFirst(R1, R2,…))
=
AfterFirst(nextIfReady(R1),nextIfReady(R2), ...),对于所有的R PR
成立。
next(AfterAll(R1, R2,…))
=
AfterAll(next(R1),next(R2),…)
next(AfterEach(R1, R2, …))
=
AfterEach(…,next(R),…) ,R是第一个未进入Done状态的。
next(Repeat(R))
=
Repeat(restart(R)) 如果isDone(next(R))为true, 否则Repeat(next(R))
next(R1.orFinally(R2))
=
nextIfReady(R1).orFinally(nextIfReady(R2))
nextIfReady(R)
=
如果PR 成立 ,返回next(R),否则返回R
重启restart:
restart(Done(R))
=
仍然是R ,当R是叶子触发器时
restart(R)
=
重启R 的所有子触发器
注意:
- 如果触发器R从未发生过状态迁跃,restart(R) =R 。
- 注意墓碑机制的使用。在实际中可以跳跃状态(和向后变换来重启),但是这样在数学上比较别扭。
- next 隐式的依赖于上次输出以来的watermark和元素,因为对断言的引用。
- next(AfterFirst(...))变换所有Ready状态的子触发器。
- next(AfterEach(...))直观的说就是,将当前的活动触发器变换状态。
- orFinally 的next函数,隐藏了一些复杂性:当执行nextIfReady的时候,主触发器的断言和"finally"触发器的断言应用的是不同的数据元素集合。主触发器和"finally"触发器实际上是被看做是同等地位的角色。
(在Apache Beam的java SDK中实现为onFire()方法)。
实现
描述在Beam Java中如何实现触发器。
在实现中,触发器R将通过检查来自<elements>,<event time>的相关信息的紧凑摘要状态来响应PR (<elements>,<event time>)的查询,我们以代码readyR(state)。State通过onElement(),onTimer(), 和onMerge()进行更新。为了简单起见,假设这借个方法返回新的State。
合并
当合并窗口的时候,窗口的当前触发器也需要被合并。触发器合并需要满足交换律和结合律。
假设窗口w1 和w2 要被合并成一个新的窗口w1⊕w2。现在需要将窗口w1 and w2的触发器R1 和R2 进行合并,并赋予窗口w1⊕w2,R1和R2的区别在于他们的完成状态/触发进度,新的触发器被称为R1⊕R2 。这个Pcollection的初始Trigger被称之为R,R= restart(R1)&restart(R2)。
如果R从开头我们想合理的估计R的触发进度,规约如下:
合并之后R1⊕R2 触发的输出应该跟R从一开始就w1⊕w2输出是一致的。
之前的输出取决于将输入切分成小块的数据,所以某种意义上说是任意的。但是我们希望随着R1 和R2 触发的进度,将关联在w1 和w2的数据元素进行1:1切分,然后作为一对数据输出。
更详细的说,假设我们一个Combine.perKey(⊙).
- 有两个输出元素<k, v1⊙v2⊙v3⊙...> and <k, v4⊙v5⊙v6⊙…>。两个元素(丢弃模式下) 表示对总体结果的渐进聚合,然后得出总体结果<k, v1⊙v2⊙v3⊙v4⊙v5⊙v6⊙...>.
- 最理想的的情况是不管聚合是提前发生还是延后发生,最终我们能得到正确的结果<k, v1⊙v2⊙v3⊙v4⊙v5⊙v6⊙...>。
对于触发器R(在单调性上)只有一种情况是例外的:超过了窗口末尾(EOW)。扩展窗口的末尾值为w1 的EOW 和w2的EOW (例如在会话窗口中)无法保持单调性。如果w1 andw2都没有到达窗口末尾,那么合并后的窗口也没有到达末尾。如果触发器R从合并窗口的起始位置开始执行,在需要EOW的地方,就会卡住。
- 如果两个窗口的末尾都到达了(可能是因为刚刚收到延迟的数据),则不用考虑下边的逻辑,因为所有的输出都是允许的。
- 如果两个窗口的末尾都没到达,接下来的逻辑就是不做操作。
所以接下来的部分适用于只有1个窗口到达末尾的情况。
在窗口的末尾之前R1和R2会在什么位置卡住?由rewind函数决定:
rewind(Done(EndOfWindow))
=
EndOfWindow
rewind(Done(R))
=
Done(R)如果是叶子触发器,而不是EndOfWindow
rewind(ElemCount(k))
=
ElemCount(k)
rewind(EndOfWindow)
=
EndOfWindow
rewind(AfterFirst(R1, R2,…))
=
AfterFirst(rewind(R1),rewind(R2), ...)
rewind(AfterAll(R1, R2,…))
=
AfterAll(rewind(R1),rewind(R2),…)
rewind(AfterEach(R1, R2, …))
=
AfterEach(rewind(R1),…, rewind(Rk),restart(Rk+1), …)
Rk 是第一个未处于Done状态的子触发器
rewind(Repeat(R))
=
Repeat(rewind(R))
rewind(R1.orFinally(R2))
=
rewind(R1).orFinally(rewind(R2))
restart(Done(R))
=
R,如果是叶子触发器
restart(R)
=
Restart所有子触发器
注意:
- 如果一个窗口并没有到达窗口末尾并且触发了,那么 rewind(R) =R
所以我们使用rewind(R1) 和rewind(R2)的中最靠后(further)的触发作为合并后的触发器的定义。
R1 ⊕R2
=
further(rewind(R1),rewind(R2))
further(ElemCount(k),DoneElemCount(k))
=
DoneElemCount(k) (and etc. commutative)
further(R1,R2)
=
使用further合并所有的子触发器
(在Apache Beam的Java SDK中实现为onMerge() 方法.)
由于rewind的存在,如果允许延迟数据,在合并的WindowFn中数据消费者,的仍然要注意上游的复合触发的输出。我们不想推迟所有输出,以防有延迟数据到达。数据消费者需要做好以下准备:
- 多个ON_TIME 输出但是只有1个是给合并了的窗口的。
- ON_TIME 的输出可能会是EARLY 的在合并的窗口中。
- LATE(延迟) 可能是ON_TIME(及时) 甚至是EARLY(提前) 在合并的窗口中
数据元素和时间
P的单调性可以由如下的规范定义:
readyR(state) ⇒ (readyR(onElement(state)) & readyR(onTimer(state)))
readyR(state) ⇒ (readyR(onElement(state)) and readyR(onTimer(state)))
状态类型
State本质上是对上次触发以来收到的数据元素的摘要,时间可以在任何时间由系统提供,但是数据可能会聚合之后被丢弃。
StateT(Done(R))
=
∅
StateT(ElemCount(k))
=
ℕ
StateT(EndOfWindow)
=
∅
StateT(SinceEarliestElem(Δ))
=
timestamp
StateT(Repeat(R))
=
StateT(R)
StateT(AfterEach(R1,…))
=
所有子触发器的State
StateT(AfterFirst( ...))
=
所有子触发器的State
StateT(AfterAll(...))
=
所有子触发器的State
StateT(R1.orFinally(R2))
=
所有子触发器的State
注意:
- Repeat(...)的子触发器完成之后,State会被清空。
- AfterEach(...) 维护所有子触发器的状态,不考虑当前哪个子触发器是活动状态。
状态更新
很多状态的更新都是将新的State转给子触发器,所以在此将拿几个距离说明。顺便说明一下,只允许更新未处于Done状态的触发器。
onElementElemCount(k)(n)
=
n+1(n始于 0)
onElementAfterEach(…)(state1, ...)
=
调用每一个子触发器的onElement 方法
注意:
- 尽管将所有的时间传递给触发器是默认行为,但是要特殊说明一下AfterEach,因为AfterEach是违反直觉的。这是为了支持further函数,触发器的状态在合并的时候,可能会向前推进。
下一个状态
当触发器调用next进入下一个触发器的时候,旧的处于Ready状态的触发器的State会被丢弃掉,因为此时State代表了已经发送的数据元素。特别注意orFinally触发器的"finally"的分支在"main"分支触发的时候,依然会保持自己的State。
跟踪完成状态
在实际中,将触发器路径映射到Tree是一种简单的实现方式,用Tree来表示每个触发器是否是Done状态。对于叶子触发器,映射关系真实的反应了触发器的Done(...)状态。对于复合触发器,由isDone 函数来判断状态。
合并状态
当合并窗口w1 andw2 的时候需要合并R1 和R2,state1 和state2 代表尚未触发的元素{x1, …, xn} and {y1, …, ym},当前的已知事件时间是t。我们需要产生mergeR1⊕R2(state1,state2)如下:
readyR1⊕R2(state1⊕state2)
⇔
PR1⊕R2({x1, …, xn, y1, …, ym}, t)
触发如果要合并,两个触发器必须匹配。注意,最上层的触发器是非Done状态。对于大部分触发器来说,只要简单的合并子触发器的State就行,如下几个:
mergeElemCount(k)(m, n)
=
m + n
mergeAfterEach(R1, ...)(state1, ...)
=
合并所有子触发器的State
合并AfterEach的状态需要通过一个例子来说明:合并需要rewind并且通过further函数向前推进。
AfterEach(R1, R2, R3)⊕AfterEach(<done>,<done>, R3)
=
AfterEach(<done>, R2', R3)
所以等待状态应该已经被R2'观察到。 任何一个子触发器都可能成为当前活动的触发器,所以我们需要子触发器可以理解的State。
本文作者专栏:http://blog.csdn.net/ffjl1985
- Apache Beam核心—触发器规约
- Apache Beam的分窗与触发器
- Apache Beam
- Apache Beam核心--延迟和窗格设计
- Apache Beam发布--- apache beam概述
- Apache Beam初探
- Apache Beam Overview
- Apache Beam 翻译
- Apache Beam开发指南
- Apache Beam 综述
- Apache Beam是什么?
- Apache Beam开发指南
- Apache Beam 剖析
- Apache Beam+Spark教程
- Apache Beam简介
- Apache Beam编程指南
- apache beam使用spark runner
- Apache Beam程序向导4
- C#文本朗读
- 向量范数与矩阵范数
- 基于深度学习的目标检测
- 第七章练习
- eclipse debug 基本的操作按钮解释
- Apache Beam核心—触发器规约
- HOG特征(Histogram of Gradient)
- Win10 64位 企业版 -- 安装MySQL使用笔记
- mysql索引的学习笔记
- 外观模式(Facade)-----基于JAVA语言
- Nexus2.12.1-01.war下载
- Springboot 集成Shiro自定义Filter
- qsort函数
- java-Swing-paintComponent