触发器(Trigger)
触发器定义
触发器决定(由窗口分配器形成
)何时可以被窗口函数处理,清空和关闭等操作。每个WindowAssigner都自带有一个默认的触发器。如果默认触发器不适合您的需要,您可以使用trigger(…)指定一个自定义触发器。
**触发器接口(trigger interface)**有5个方法允许触发器对不同的事件做出反应:
onElement()
:窗口中每添加一个事件数据,调用一次。换句话每条数据执行一次。onEventTime
:当已注册的事件时间计时器被触发时,调用该方法。换句话基于事件时间的数据且达到计时器条件。onProcessingTime()
: 当已注册的处理时间计时器被触发时,调用该方法。onMerger()
: 和有状态触发器相关,用于两个触发器对应的Windows合并时它们的状态,例如:当使用SessionWindow时。clear()
: 执行删除相应窗口所需的任何操作。
以上的方法需要注意2点:
1,前三个函数通过返回TriggerResult来决定如何处理它们的调用事件。该操作可以是以下方式之一:
CONTINUE
: 什么也不做FIRE
: 触发计算。注意计算完成后,窗口中的数据并不会被清除,将会保留。PURGE
: 清除窗口中的元素FIRE_AND_PURGE
: 触发计算,然后清除窗口中的元素
2,上述5个方法中的任何一个都可以用来为将来的操作注册处理时间或事件时间计时器
。
内置触发器
WindowAssigner的默认触发器适用于许多用例。例如:
- 所有事件时间窗口分配器都有一个EventTimeTrigger作为默认触发器。
- GlobalWindow的默认触发器是NeverTrigger。它从不触发。因此,在使用GlobalWindow时,必须定义一个自定义触发器。
Flink自带内置触发器:
- EventTimeTrigger:基于EventTime Window的默认触发器
- ProcessingTimeTrigger:基于ProcessingTime Window的默认触发器
- CountTrigger:基于Count Window的默认触发器
- PurgingTrigger:内部使用,用于清除窗口内容
- NeverTrigger:永不触发的触发器
如果用户不指定Trigger类,Flink将会调用默认的Trigger,例如对于时间属性为EventTime的窗口,Flink默认会用EventTimeTrigger
类;时间属性为ProcessingTime的窗口,Flink默认使用ProcessingTimeTrigger
类。
如果用户指定了要使用的Trigger,默认的Trigger将会被覆盖,不会起作用。
代码案例
自定义一个基于时间和计数并存的触发器,如:窗口时间到达或窗口数据量任意之一条件到达即窗口触发。
package 复习
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.triggers.{Trigger, TriggerResult}
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
/**
* 自定义触发器
* 2022-04-19 beijing 1
* 2022-04-19 beijing 2
* 2022-04-19 beijing 3
* 2022-04-19 beijing 4
* 2022-04-19 beijing 5
* 2022-04-19 beijing 6
* 2022-04-19 beijing 7
* 2022-04-19 beijing 8
*
*/
object Trigger_My {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
val ds = env.socketTextStream("localhost", 6666)
.map(x => {
val fields: Array[String] = x.split(" ")
val date = fields(0).trim
val province = fields(1)
val ts = fields(2).trim.toInt
(date + "_" + province, ts)
}).keyBy(_._1)
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.trigger(new MyTrigger)
.sum(1)
.print()
env.execute("test Trigger")
}
}
/*
自定义窗口的触发器
1. 需要实现trigger
2. 第一个泛型 是输入数据类型,第二个泛型为window的类型
3.需要实现4个方法
*/
class MyTrigger extends Trigger[(String,Int),TimeWindow]{
// 定义一个计数器
var cnt:Int = 0
// 窗口中每个元素的处理逻辑
override def onElement(element: (String, Int), timestamp: Long, window: TimeWindow, ctx: Trigger.TriggerContext): TriggerResult = {
// 注册一个时间触发器 这里的是window当中最大时间 window.maxTimestamp()
// 也就是当窗口满足触发器时间,就会触发,往下游走,也可以自己设置时间戳
ctx.registerProcessingTimeTimer(window.maxTimestamp())
// 根据数据的条数判断是否大于2条,大于则触发并且将cnt 重置为 0 ,否则什么都不做
if(cnt > 2){
println("通过计数触发窗口")
cnt = 0
// 触发窗口执行
TriggerResult.FIRE
}else{
cnt = cnt+1
TriggerResult.CONTINUE // 不满足条件什么都不做
}
}
// 基于处理时间的逻辑, 当定时器达到条件是就调用
override def onProcessingTime(time: Long, window: TimeWindow, ctx: Trigger.TriggerContext): TriggerResult = {
println("通过处理时间触发窗口")
TriggerResult.FIRE
}
// 基于事件时间什么都不做 这段代码是基于处理时间
override def onEventTime(time: Long, window: TimeWindow, ctx: Trigger.TriggerContext): TriggerResult = ???
// 窗口清除的时候调用
override def clear(window: TimeWindow, ctx: Trigger.TriggerContext): Unit = {
ctx.deleteEventTimeTimer(window.maxTimestamp())
}
}