Trigger作用在WindowStream上,也就是说,trigger是跟随在window()算子之后的。Trigger决定了窗口中的数据何时可以被window function处理, 每一个窗口分配器都有一个默认的触发器,如果默认的触发器不能满足需要,你可以通过调用WindowedStream.trigger(...)来指定一个自定义的触发器。

例如:TumblingEventTimeWindows(滚动窗口)默认触发器为EventTimeTrigger,默认情况下在当前水印时间大于等于当前窗口最大时间(窗口结束时间-1)时触发window function。

和onTimer的区别:

1.onTimer用在process function中,也就是说,onTimer是基于DataStream和KeyedStream的。

2.trigger是用在window func

Trigger有 5 个方法来允许触发器处理不同的事件:

onElement()方法,每个元素被添加到窗口时调用

onEventTime()方法,当一个已注册的事件时间计时器启动时调用

onProcessingTime()方法,当一个已注册的处理时间计时器启动时调用

onMerge()方法,与状态性触发器相关,当使用会话窗口时,两个触发器对应的窗口合并时,合并两个触发器的状态。

clear()方法,执行任何需要清除的相应窗口。

上面的方法中有两个需要注意的地方:

1.前三个方法通过返回一个TriggerResult来决定如何操作调用他们的事件,这些操作可以是下面操作中的一个:

CONTINUE:什么也不做

FIRE:触发计算

PURGE:清除窗口中的数据

FIRE_AND_PURGE:触发计算并清除窗口中的数据

2.这些函数可以注册 “处理时间定时器” 或者 “事件时间计时器”,被用来为后续的操作使用,比如:ctx.registerEventTimeTimer(window.maxTimestamp())

默认情况下,内置的触发器只返回 FIRE,不会清除窗口状态

注意:清除将仅删除窗口的内容,并将保留有关该窗口的任何潜在元信息和任何触发状态。

触发器可以访问流的时间属性以及定时器,还可以对state状态编程。所以触发器和process function一样强大。例如我们可以实现一个触发逻辑:当窗口接收到一定数量的元素时,触发器触发。再比如当窗口接收到一个特定元素时,触发器触发。还有就是当窗口接收到的元素里面包含特定模式(5秒钟内接收到了两个同样类型的事件),触发器也可以触发。在一个事件时间的窗口中,一个自定义的触发器可以提前(在水位线没过窗口结束时间之前)计算和发射计算结果。这是一个常见的低延迟计算策略,尽管计算不完全,但不像默认的那样需要等待水位线没过窗口结束时间。

下面的例子展示了一个触发器在窗口结束时间之前触发。当第一个事件被分配到窗口时,这个触发器注册了一个定时器,定时时间为水位线之前一秒钟。当定时事件执行,将会注册一个新的定时事件,这样,这个触发器每秒钟最多触发一次。

自定义实现: 通过触发器在15秒的窗口内每秒触发一次计算。

import com.atguigu.StreamingJob.{SensorReading, SensorSource}
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.api.scala.typeutils.Types
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
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
import org.apache.flink.util.Collector

case  class SensorReading(id:String,timestamp :Long,temperature : Double)
object TriggersTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //设置并行度
    env.setParallelism(1)
    //设置事件时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //获取事件源
    val stream = env.addSource(new SensorSource)
    val setTime = stream.assignAscendingTimestamps(_.timestamp)
    //设置事件时间的获取方式
   val result = setTime.keyBy(_.id).timeWindow(Time.seconds(15)).trigger(new OneSecondIntervalTrigger ).process(new AllWindom)
   result.print()
   env.execute()
  }
  //创建窗口的全量函数 in out key windom
  class  AllWindom extends ProcessWindowFunction[SensorReading,(String,Double,Double,Long),String,TimeWindow]{
    override def process(key: String,
                         context: Context,
                         elements: Iterable[SensorReading],
                         out: Collector[(String, Double, Double,Long)]): Unit = {
      var doubles: Iterable[Double] = elements.map(_.temperature)
    out.collect( (key,doubles.max,doubles.min,context.window.getEnd))
    }
  }
}
//设置一秒钟一次的触发器
class OneSecondIntervalTrigger extends  Trigger[SensorReading , TimeWindow]{
  //回调函数
  override def onEventTime(l: Long, //触发定时器的时间,即前文设置的定时时间,默认窗口结束时会调用一次
                           w: TimeWindow, //窗口
                           triggerContext: Trigger.TriggerContext): TriggerResult = {
//判断l是否为窗口的结束时间
    if(l==w.getEnd){
      //触发窗口的计算,并且清空数据
      print("=============================")
      TriggerResult.FIRE_AND_PURGE
    }else{
val t = triggerContext.getCurrentWatermark+(1000-(triggerContext.getCurrentWatermark%1000))
      if(t<w.getEnd){
        triggerContext.registerEventTimeTimer(t)
      }
    }
    //支触发计算
    TriggerResult.FIRE
  }
//这是系统时间的 ,不执行业务逻辑
  override def onProcessingTime(l: Long,
                                w: TimeWindow,
                                triggerContext: Trigger.TriggerContext): TriggerResult = {
    TriggerResult.CONTINUE
  }
  //每个窗口的结束时调用
  override def clear(w: TimeWindow,
                     triggerContext: Trigger.TriggerContext): Unit = {
    val firstSeen: ValueState[Boolean] = triggerContext
      .getPartitionedState(
        new ValueStateDescriptor[Boolean](
          "firstSeen", classOf[Boolean]
        )
      )
    //注销之前设置的事件
    firstSeen.clear()
  }
  //每个数据调用一次
  override def onElement(t: SensorReading,
                         l: Long,
                         w: TimeWindow,
                         triggerContext: Trigger.TriggerContext): TriggerResult = {
    //获取分区的状态变量
    var firstSeen: ValueState[Boolean] = triggerContext.getPartitionedState(new ValueStateDescriptor[Boolean]("firstSeen", Types.of[Boolean]))
    //每个窗口的第一个数据才会进入到里面设置回调事件的事件
    if(!firstSeen.value()){
      //获取当前水位线
      val t = triggerContext.getCurrentWatermark+(1000-(triggerContext.getCurrentWatermark%1000))
      //注册事件时间的回调事件,注册下一秒的事件
      triggerContext.registerEventTimeTimer(t)
      //注册窗口结束时的事件
      triggerContext.registerEventTimeTimer(w.getEnd)
      //关闭时间的注册,保证每一秒内的事件不重复注册
      firstSeen.update(true)
    }
   TriggerResult.CONTINUE
  }