Flink: ventana de activación de CountAndProcessingTimeTrigger basada en Count y Time

I. Introducción

El artículo anterior mencionó  CountTrigger && ProcessingTimeTrigger . El antiguo CountTrigger especifica el número de conteos y activa un disparador cuando los elementos en la ventana cumplen con la lógica. El último registra el tiempo de expiración de la ventana a través de TimeServer y dispara un disparador después de la expiración. Este artículo personaliza el Disparar para lograr ambos La combinación de Count y ProcessingTime activará una ventana cuando se cumpla cualquier condición.

2. Explicación detallada del código

1. Disparador de tiempo de recuento y procesamiento

El código general es el siguiente. La lógica principal se incluye en onElement y onProcessingTime. El primero es principalmente responsable de la activación según el conteo, es decir, para realizar la función de CountTrigger, mientras que el segundo realiza principalmente la función de ProcessingTime. Es necesario definir dos ReduceValues ​​por adelantado para registrar Count y Time respectivamente.

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

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

  // 时间计数器,保存下一次触发的时间
  val timeStateDesc = new ReducingStateDescriptor[Long]("interval", new ReduceMin(), classOf[Long])

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

    // 获取 count state 并累加数量
    val count = triggerContext.getPartitionedState(countStateDesc)
    val fireTimestamp = triggerContext.getPartitionedState(timeStateDesc)

    // 考虑count是否足够
    count.add(1L)
    if (count.get() >= maxCount) {
      val log = s"CountTrigger Triggered Count: ${count.get()}"
      println(formatString(log))
      count.clear()
      // 不等于默认窗口的触发时间
      if (fireTimestamp.get() != window.maxTimestamp()) {
        triggerContext.deleteProcessingTimeTimer(fireTimestamp.get())
      }
      fireTimestamp.clear()
      return TriggerResult.FIRE
    }

    // 添加窗口的下次触发时间
    val currentTimeStamp = triggerContext.getCurrentProcessingTime
    if (fireTimestamp.get() == null) {
      val nextFireTimeStamp = currentTimeStamp + interval
      triggerContext.registerProcessingTimeTimer(nextFireTimeStamp)
      fireTimestamp.add(nextFireTimeStamp)
    }

    TriggerResult.CONTINUE
  }

  override def onProcessingTime(time: Long, window: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    // 获取 count state
    val count = triggerContext.getPartitionedState(countStateDesc)
    // 获取 Interval state
    val fireTimestamp = triggerContext.getPartitionedState(timeStateDesc)

    // time default trigger
    if (time == window.maxTimestamp()) {
      val log = s"Window Trigger By maxTimeStamp: $time FireTimestamp: ${fireTimestamp.get()}"
      println(formatString(log))
      count.clear()
      triggerContext.deleteProcessingTimeTimer(fireTimestamp.get())
      fireTimestamp.clear()
      fireTimestamp.add(triggerContext.getCurrentProcessingTime + interval)
      triggerContext.registerProcessingTimeTimer(fireTimestamp.get())
      return TriggerResult.FIRE
    } else if (fireTimestamp.get() != null && fireTimestamp.get().equals(time)) {
      val log = s"TimeTrigger Triggered At: ${fireTimestamp.get()}"
      println(formatString(log))
      count.clear()
      fireTimestamp.clear()
      fireTimestamp.add(triggerContext.getCurrentProcessingTime + interval)
      triggerContext.registerProcessingTimeTimer(fireTimestamp.get())
      return TriggerResult.FIRE
    }
    TriggerResult.CONTINUE
  }

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

  override def clear(w: TimeWindow, triggerContext: Trigger.TriggerContext): Unit = {
    // 获取 count state
    val count = triggerContext.getPartitionedState(countStateDesc)
    // 获取 Interval state
    val fireTimestamp = triggerContext.getPartitionedState(timeStateDesc)

    count.clear()
    fireTimestamp.clear()
  }

}

2.enElemento

Ejecute count.add para contar cuando llegue cada elemento y active la operación si excede el maxCount definido:

---- Alcance MaxCount

A.log: imprima el registro para identificar este disparador de CountTrigger

B.count.clear: borre el valor para volver a acumular el conteo y activar

C.deleteProcessingTime: borra el contador del TimeServer, porque tanto Count como ProcessingTime deben volver a contarse o cronometrarse después de la activación

----- MaxCount no alcanzado

A.currentTime: obtenga el ProcessingTime actual a través del contexto ctx

B.registerProcessingTimeTimer: determine si el valor de tiempo tiene un valor, si no hay valor, calcule el tiempo de activación de la siguiente ventana correspondiente a ProcessingTime de acuerdo con el intervalo actual

----- no están satisfechos

A.TriggerResult.CONTINUE: no activar, esperar a que TimeServer caduque

  override def onElement(t: String, time: Long, window: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {

    // 获取 count state 并累加数量
    val count = triggerContext.getPartitionedState(countStateDesc)
    val fireTimestamp = triggerContext.getPartitionedState(timeStateDesc)

    // 考虑count是否足够
    count.add(1L)
    if (count.get() >= maxCount) {
      val log = s"CountTrigger Triggered Count: ${count.get()}"
      println(formatString(log))
      count.clear()
      // 不等于默认窗口的触发时间
      if (fireTimestamp.get() != window.maxTimestamp()) {
        triggerContext.deleteProcessingTimeTimer(fireTimestamp.get())
      }
      fireTimestamp.clear()
      return TriggerResult.FIRE
    }

    // 添加窗口的下次触发时间
    val currentTimeStamp = triggerContext.getCurrentProcessingTime
    if (fireTimestamp.get() == null) {
      val nextFireTimeStamp = currentTimeStamp + interval
      triggerContext.registerProcessingTimeTimer(nextFireTimeStamp)
      fireTimestamp.add(nextFireTimeStamp)
    }

    TriggerResult.CONTINUE
  }

3.en tiempo de procesamiento

La operación realizada cuando se alcanza la ventana de tiempo de procesamiento especificada. Mencionamos anteriormente que la ventana llamará al método onProcessingTime en dos ocasiones. Una es alcanzar el ProcessintTimeTimer definido por sí mismo, y la ventana activará el disparador. En este momento, el disparador los datos son parte de los datos de la ventana, y Uno es llegar a window.maxTimeStamp, es decir, llegar a window.getEnd - 1L. En este momento, los datos activados por el disparo de la ventana son todos los datos dentro del rango de tiempo definido por windowAll , como definir Time.seconds(10), el primero activa parte de los datos de tiempo, el último activa la ventana Full 10s.

----- Hora de activación predeterminada de la ventana de llegada

A.window.maxTimestamp: el tiempo predeterminado para llegar a la ventana, imprime el identificador de registro correspondiente

B.count.clear - borrar el estado de conteo

C.deleteProcessingTime: borre el contador original, porque esta ventana se volverá a contar y cronometrará después de que se active

D.registerProcessingTime: registre la próxima vez en función del ProcessingTime + intervalo actual

E.TriggerResult.FIRE: realiza la activación de la ventana de datos completa

----- Alcanza el intervalo de intervalo personalizado

A. Logotipo de registro: imprimir el logotipo de TimeTriggered Este activador proviene de ProcessingTime personalizado

B.clear: borra el estado de conteo original después de que se activa la ventana

C.registerProcessingTime: registre la próxima vez en función del ProcessingTime + intervalo actual

D.TriggerResult.FIRE - datos de la ventana de activación

----- no están satisfechos

A.TriggerResult.CONTINUAR - no hacer nada

  override def onProcessingTime(time: Long, window: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
    // 获取 count state
    val count = triggerContext.getPartitionedState(countStateDesc)
    // 获取 Interval state
    val fireTimestamp = triggerContext.getPartitionedState(timeStateDesc)

    // time default trigger
    if (time == window.maxTimestamp()) {
      val log = s"Window Trigger By maxTimeStamp: $time FireTimestamp: ${fireTimestamp.get()}"
      println(formatString(log))
      count.clear()
      triggerContext.deleteProcessingTimeTimer(fireTimestamp.get())
      fireTimestamp.clear()
      fireTimestamp.add(triggerContext.getCurrentProcessingTime + interval)
      triggerContext.registerProcessingTimeTimer(fireTimestamp.get())
      return TriggerResult.FIRE
    } else if (fireTimestamp.get() != null && fireTimestamp.get().equals(time)) {
      val log = s"TimeTrigger Triggered At: ${fireTimestamp.get()}"
      println(formatString(log))
      count.clear()
      fireTimestamp.clear()
      fireTimestamp.add(triggerContext.getCurrentProcessingTime + interval)
      triggerContext.registerProcessingTimeTimer(fireTimestamp.get())
      return TriggerResult.FIRE
    }
    TriggerResult.CONTINUE
  }

4.onEventTime

Debido a que se basa en Count y ProcessingTime, onEventTime devuelve TriggerResult.CONTINUE

5.claro

Borre el ReduceValue correspondiente a Count y FireTimestamp

3. Código de práctica

1. Función principal

Las fuentes de CountTrigger y ProcessingTimeTrigger anteriores son fuentes de datos fijas, que envían 30 piezas de datos cada s. Para verificar CountAndProcessingTimeTrigger, aquí se utilizan sockets para implementar datos de envío personalizados. El puerto nc-lk local se puede abrir y processFunction puede realizar los datos en la ventana Las estadísticas mínimas, máximas y el tiempo de procesamiento de salida.

object CountAndProcessTimeTriggerDemo {

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

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

    val env = StreamExecutionEnvironment.getExecutionEnvironment

    env
      .socketTextStream("localhost", 9999)
      .setParallelism(1)
      .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .trigger(new CountAndProcessingTimeTrigger(10, 5000))
      .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()

  }
}

2. Validación de datos

El CountAndProcessintTimeTrigger anterior está configurado para contar = 10, intervalo = 5s

color Método de activación ingresar proceso
azul CountTrigger 1-10 Satisfacer recuento = 10, activar CountTrigger
rojo Desencadenador predeterminado Disparador predeterminado, los datos de ventana completa son 1-10
verde ProcessingTimeTrigger 11,12 Ingrese 11, 12 para activar ProcessingTimeTrigger
amarillo Desencadenador predeterminado 13 Ingrese 13 antes del final, los datos de la ventana completa son 11-13
Ceniza ProcessingTimeTrigger 14 Ingrese 14, espere a que se active ProcessingTimeTrigger
Blanco Desencadenador predeterminado Activado de forma predeterminada, en este momento los datos de la ventana completa son 14

Aquí hay una explicación de por qué el intervalo es de 5 s, pero el tiempo de registro de salida de ProcessingTimeTrigger es de 16 y 27. Esto se debe a que habrá un retraso al ingresar manualmente el Socket. Si la máquina envía datos de forma predeterminada, el registro se corregirá a 15 y 25 para activar ProcessingTimeTrigger.

4 más

La lógica de CountTrigger es relativamente simple. ProcessingTimeTrigger es solo un método de definición aquí, es decir, el tiempo de vencimiento se restablece al final de la ventana, o se puede definir entre ventanas o no establecer un tiempo entero. Si está interesado , puedes personalizarlo. Para CountTrigger, ProcessingTrigger y CountAndProcessingTimeTrigger personalizados en este artículo, todos descubrimos que cuando se activa la ventana, solo se llama FIRE y FIRE_AND_PURGE no se llama para realizar la operación de borrado. Si los datos de la ventana no se borran, ¿se acumularán más y Sí, la función WindowOperator tiene un método onProcessingTime predeterminado incorporado, que juzgará y llamará al método clearAllState internamente para borrar los datos:

 WindowOperator es el punto de entrada para toda la lógica de procesamiento de ventanas. Si nuestro Trigger devuelve TiggerResult.FIRE, la ventana ejecutará el método clearAllState para borrar todos los estados de la ventana actual cuando llegue a CleanupTime. Si devuelve TriggerResult.FIRE_AND_PURGE, windowOperator llamará al método clear método de Trigger @override, por ejemplo, ProcessingTimeTrigger borrará el temporizador de la ventana, y si devuelve FIRE_AND_PURGE en este ejemplo, borrará los dos valores de ReduceValue correspondientes a count y fireTimestamp al mismo tiempo:

Supongo que te gusta

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