Flink - Explicación de CountTrigger && ProcessingTimeTriger

I. Introducción

Flink proporciona una variedad de disparadores personalizados para ventanas, entre los cuales los más comunes son CountTrigger y ProcessingTimeTrigger.Aprendamos sobre los principios de implementación internos de los dos disparadores y el conocimiento de la activación de ventanas a través de dos demostraciones.

2. Conocimiento auxiliar

Antes de presentar los dos disparadores anteriores, repasemos el conocimiento básico de los disparadores que se han mejorado anteriormente.

1. Activar método interno

onElement: la acción a realizar después de que llegue el elemento

· onProcessingTime: la operación realizada cuando se alcanza la ventana de tiempo de procesamiento especificada

· onEventTime: la operación realizada cuando se alcanza la ventana de tiempo del evento especificado

clear : borra la variable de valor asociada

2. Operación después del disparador de ventana

TriggerResult.CONTINUE: omitir, no hacer nada

TriggerResult.FIRE: cálculo de la ventana de activación

TriggerResult.PURGE: borra el elemento de la ventana

TriggerResult.FIRE_AND_PURGE: activa la operación de la ventana, luego borra el elemento de la ventana

3.Reducción del valor del estado

ReducingStateValue es una estadística abstracta, que requiere que el usuario defina su tipo de devolución y la operación de reducción correspondiente.Aquí, reducir no significa reducción sino fusión, lo que puede entenderse como la operación de reducción (_ + _) en chispa, es decir, para un objeto dado El objeto1 y el objeto2 se sintetizan en un solo objeto, y el método para definir la variable es el siguiente:

val reduceStateDesc = new ReducingStateDescriptor[T]($key, new ReduceFunction(), classOf[T])

T representa el tipo de variable de retorno y la clave es su identificador de nombre. ReduceFunction necesita heredar org.apache.flink.api.common.functions.ReduceFunction para implementar el método de reduce(o1: T, o2: T): T. El siguiente ejemplo genera uno para obtener dos The RecuceFunction del menor de los números:

class ReduceMin() extends ReduceFunction[Long] {
  override def reduce(t1: Long, t2: Long): Long = {
    math.min(1, t2)
  }
}

4. Tiempo de activación predeterminado de la ventana

dataStream.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))

Al usar la ventana bajo la clase org.apache.flink.streaming.api.windowing.assigners, como la ventana móvil TumblingProcessingTimeWindows, de acuerdo con el tiempo que configuramos, las horas de inicio y finalización de la ventana son realmente fijas. Después de los parámetros se determinan, el inicio - El final está determinado Tomando la ventana móvil de 10 segundos anterior como ejemplo, la hora predeterminada de inicio y finalización de la ventana es la hora completa, comenzando a las 18:00:

18:00:00 - 18:00:10 ,18:00:10 - 18:00:20 ... 18:59:50 - 19:00:00

Cada vez que la ventana alcance la hora de finalización especificada, se llamará al método onProcessingTime de forma predeterminada. No importa si no lo entiende aquí, solo espere y vea la demostración.

3. Explicación detallada de CountTrigger

CountTrigger se activa de acuerdo con la cantidad de elementos. Cuando la cantidad de elementos alcanza el tamaño de conteo, se activa la lógica de cálculo de la ventana y el ReduceStateValue mencionado anteriormente se usa para contar la cantidad de conteos internos. Para agregar un registro en ejecución durante el proceso de ejecución, aquí se agrega un SelfDefinedCountTrigger. El código es exactamente el mismo que el CountTrigger oficial, la única diferencia es la adición de la impresión de registro.

1.Disparador de recuento autodefinido

El tiempo de activación y el número de elementos activados se agregan en el activador. La lógica principal está en la función onElement. Cada vez que llega un elemento, se acumulará el ReduceStateValue del activador. Aquí, la función RecudeSum se utiliza para sumar los dos Cuando el valor llega a countSize, la ventana vuelve a activar Fire y borra ReduceStateValue para comenzar una nueva ronda de conteo; el resto del tiempo devuelve TriggerResult.CONTINUE.

class SelfDefinedCountTrigger(maxCount: Long) extends Trigger[String, TimeWindow] {

  // 条数计数器
  val countStateDesc = new ReducingStateDescriptor[Long]("count", new ReduceSum(), classOf[Long])

  // 元素过来后执行的操作
  override def onElement(t: String, l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {

    // 获取 count state 并累加数量
    val count = triggerContext.getPartitionedState(countStateDesc)
    count.add(1L)

    // 满足数量触发要求
    if (count.get() >= maxCount) {
      // 首先清空计数器
      count.clear()

      val dateFormat = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss")
      val cla = Calendar.getInstance()
      cla.setTimeInMillis(System.currentTimeMillis())
      val date = dateFormat.format(cla.getTime)

      println(s"[$date] Window Trigger By Count = ${maxCount}")

      TriggerResult.FIRE
    } else {
      TriggerResult.CONTINUE
    }
  }

  override def onProcessingTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    TriggerResult.CONTINUE
  }

  override def onEventTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    TriggerResult.CONTINUE
  }

  override def clear(w: TimeWindow, triggerContext: Trigger.TriggerContext): Unit = {
    val count = triggerContext.getPartitionedState(countStateDesc)
    count.clear()
  }

}

2. Función principal

La lógica de la función principal comienza desde 0, genera 30 números cada s y los acumula en un bucle. La ventana usa una ventana rodante agregada de 10 y el disparador usa un CountTrigger con conteo = 30. En teoría, los 30 elementos generados cada s solo activar la lógica de ejecución de la ventana. La lógica de procesamiento de la ventana también es muy simple, ya que genera directamente la cantidad de elementos en la ventana actual, los elementos mínimos, máximos y el tiempo de procesamiento:

object CountTriggerDemo {

  val dateFormat = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss")

  // 每s生成一批数据
  class SourceFromCollection extends RichSourceFunction[String] {
    private var isRunning = true
    var start = 0

    override def run(ctx: SourceFunction.SourceContext[String]): Unit = {
      while (isRunning) {
        (start until (start + 100)).foreach(num => {
          ctx.collect(num.toString)
          if (num % 30 == 0) {
            TimeUnit.SECONDS.sleep(1)
          }
        })
        start += 100
      }
    }

    override def cancel(): Unit = {
      isRunning = false
    }
  }

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment

    env
      .addSource(new SourceFromCollection())
      .setParallelism(1)
      .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .trigger(new SelfDefinedCountTrigger(30))
      .process(new ProcessAllWindowFunction[String, String, TimeWindow] {
        override def process(context: Context, elements: Iterable[String], out: Collector[String]): Unit = {
          val cla = Calendar.getInstance()
          cla.setTimeInMillis(System.currentTimeMillis())
          val date = dateFormat.format(cla.getTime)
          val info = elements.toArray.map(_.toInt)
          val min = info.min
          val max = info.max
          val output = s"==========[$date] Window Elem Num: ${elements.size} Min: $min -> Max $max=========="
          out.collect(output)
        }
      }).print()

    env.execute()

  }

}

3. Registro de ejecución

Dado que no es un tiempo entero al inicio, la primera ventana solo procesa datos de 8 s desde las 8:30:02 hasta las 8:30: 10. Después de que finaliza la lógica de la primera ventana, los siguientes son estables desde la ventana deslizante de 10 s. los elementos se activan a la vez, y el cambio de los datos de la ventana dentro de 10s se puede ver a través de Window Elem Num, 10s x 30 = 300.

4. Mecanismo de activación predeterminado de la ventana

La ventana mejorada anterior ejecutará el método onProcessingTime a la hora de finalización predeterminada. Dado que solo se devuelve TriggerResult.CONTINUE en el método, no es obvio. Agreguemos un registro al método onProcessingTime para verificar:

  override def onProcessingTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    println(s"Window Trigger At Default End Time: $l")
    TriggerResult.CONTINUE
  }

Ver registro:

A través de la información de registro, puedo ver que la ventana ejecutará el método onProcessingTime al final del tiempo, pero ¿por qué no es un número entero? 

Esto se debe a que la marca de tiempo realmente activada por la ventana es el método correspondiente a la variable window.maxTimestamp(), que se define de la siguiente manera:

    public long maxTimestamp() {
        return this.end - 1L;
    }

El código fuente resta uno de la marca de tiempo correspondiente al final predeterminado, y el tiempo de finalización real de la ventana es 1648034659999 + 1:

 Por lo tanto, aquí se verifican dos problemas, el primero es que la ventana llamará al método onProcessingTime en el tiempo de finalización predeterminado, y el segundo es que la diferencia entre el tiempo de finalización de la ventana y el tiempo real de disparo es 1L, window.maxTimestamp + 1 = ventana.getEnd.

4. Explicación detallada de ProcessingTimeTrigger

processingTimeTrigger es el activador predeterminado de processTime correspondiente a la ventana del programa Flink. Se activa de acuerdo con la hora de inicio y finalización predeterminada de la ventana, o la ventana TumblingProcessingTimeWindows de 10s más un SelfDefinedProcessingTimeTrigger personalizado que se utiliza para la visualización de demostración.

1. Disparador de tiempo de procesamiento definido por sí mismo

Los métodos principales de ProcesingTimeTrigger son onElement y onProcessingTime. El primero registra la ventana con timeServer, y su tiempo de expiración es window.maxTimestamp. Como se mencionó anteriormente, el tiempo es window.getEnd() - 1. El último realiza el cálculo de la ventana de activación de Fire. Para obtener información detallada sobre el tiempo, se agregan aquí los registros relacionados de processingTime y window-start window-end.

class SelfDefinedProcessingTimeTrigger() extends Trigger[String, TimeWindow] {

  // 条数计数器
  val countStateDesc = new ReducingStateDescriptor[Long]("count", new ReduceSum(), classOf[Long])
  val dateFormat = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss")
  val cla = Calendar.getInstance()

  // 元素过来后执行的操作
  override def onElement(t: String, l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    triggerContext.registerProcessingTimeTimer(w.maxTimestamp)
    TriggerResult.CONTINUE
  }

  override def onProcessingTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    cla.setTimeInMillis(w.getStart)
    val start = dateFormat.format(cla.getTime)
    cla.setTimeInMillis(w.getEnd)
    val end = dateFormat.format(cla.getTime)

    println(s"start: $start end: $end processTime: $l maxTimeStamp: ${w.maxTimestamp()} windowStart: ${w.getStart} windowEnd: ${w.getEnd}")
    TriggerResult.FIRE
  }

  override def onEventTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    TriggerResult.CONTINUE
  }

  override def clear(w: TimeWindow, triggerContext: Trigger.TriggerContext): Unit = {
    triggerContext.deleteProcessingTimeTimer(w.maxTimestamp)
  }

}

2. Función principal

La fuente de datos es una fuente personalizada, se generan 30 elementos cada segundo y se genera una ventana móvil en un ciclo de 10. La lógica de procesamiento sigue siendo la cantidad de elementos en la ventana de salida, mínimo y máximo.

object ProcessTimeTriggerDemo {

  val dateFormat = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss")

  // 每s生成一批数据
  class SourceFromCollection extends RichSourceFunction[String] {
    private var isRunning = true
    var start = 0

    override def run(ctx: SourceFunction.SourceContext[String]): Unit = {
      while (isRunning) {
        (start until (start + 100)).foreach(num => {
          ctx.collect(num.toString)
          if (num % 30 == 0) {
            TimeUnit.SECONDS.sleep(1)
          }
        })
        start += 100
      }
    }

    override def cancel(): Unit = {
      isRunning = false
    }
  }

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment

    env
      .addSource(new SourceFromCollection())
      .setParallelism(1)
      .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .trigger(new SelfDefinedProcessingTimeTrigger())
      .process(new ProcessAllWindowFunction[String, String, TimeWindow] {
        override def process(context: Context, elements: Iterable[String], out: Collector[String]): Unit = {
          val cla = Calendar.getInstance()
          cla.setTimeInMillis(System.currentTimeMillis())
          val date = dateFormat.format(cla.getTime)
          val info = elements.toArray.map(_.toInt)
          val min = info.min
          val max = info.max
          val output = s"==========[$date] Window Elem Num: ${elements.size} Min: $min -> Max $max=========="
          out.collect(output)
        }
      }).print()

    env.execute()

  }
}

3. Registro de ejecución

La ventana ejecuta un disparador cada 10 segundos y el número de elementos disparadores es 10x30 = 300. A través del ejemplo, puede ver window-start window-end y su forma de tiempo de formato correspondiente, y verificar nuevamente que maxTimestamp = window.end - 1 .

5. Resumen

Al ajustar el Trigger oficial y agregar registros, puede ver la lógica de ejecución más común de CountTrigger y ProcessingTimeTrigger y profundizar la lógica de la activación de la ventana Más adelante, combinaremos CountTrigger y ProcessTimeTrigger para implementar un CountAndTimeTrigger personalizado, que combina las condiciones de activación de Count y ProcessingTime. , puede hacer que la ventana se active cuando se cumpla el número de barras o el intervalo.

Supongo que te gusta

Origin blog.csdn.net/BIT_666/article/details/123693325
Recomendado
Clasificación