Flink aprende de 0 a 1-Capítulo 7 Semántica del tiempo y Wartermark

1. Semántica de tiempo en Flink

En el procesamiento de transmisión de Flink, están involucrados diferentes conceptos de tiempo, como se muestra en la siguiente figura:

Inserte la descripción de la imagen aquí

Figura concepto de tiempo de Flink
- ** Hora del evento **: la hora en que se creó el evento. Por lo general, se describe mediante la marca de tiempo del evento. Por ejemplo, en los datos de registro recopilados, cada registro registrará su propia hora de generación y Flink accede a la marca de tiempo del evento a través del distribuidor de marcas de tiempo.
  • Tiempo de ingestión : es el momento en que los datos ingresan a Flink.

  • Tiempo de procesamiento : Es el tiempo del sistema local de cada operador que realiza operaciones basadas en el tiempo. Está relacionado con la máquina. El atributo de tiempo predeterminado es ProcessingTime.

Por ejemplo, la hora en que un registro ingresa a Flink es 2020-09-03 10:00:00.123, y la hora del sistema cuando llega a la ventana es 2020-09-03 10:00:01.234. El contenido del registro es el siguiente:

2020-09-03 09:59:58.624 INFO Fail over to rm2

Para las empresas, las estadísticas 1 minfallan en el número de inicios de sesión, ¿a qué hora es la más significativa? —— eventTime, porque necesitamos hacer estadísticas basadas en el tiempo de generación del registro.

2. La introducción de EventTime

En el procesamiento de transmisión por secuencias de Flink, la mayoría de las empresas utilizarán eventTime. Generalmente, solo cuando no se pueda utilizar eventTime, se verán obligados a utilizar ProcessingTime o IngestionTime.

Si desea utilizar EventTime, debe introducir el atributo de tiempo de EventTime, de la siguiente manera:

val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

3. Marca de agua

3.1 Conceptos básicos

Sabemos que el procesamiento de flujo tiene un proceso y un tiempo desde la generación del evento, hasta que fluye a través de la fuente y luego al operador, aunque en la mayoría de los casos, los datos que fluyen al operador vienen en el orden de tiempo en que se genera el evento. Sin embargo, no descarta la ocurrencia de desorden debido a la red, distribución y otras razones El llamado desorden significa que el orden de los eventos recibidos por Flink no está estrictamente en el orden del EventTime del evento.

Inserte la descripción de la imagen aquí

Fuera del orden de los datos del gráfico
Entonces hay un problema en este momento. Una vez que hay desorden, si solo determinamos el funcionamiento de la ventana en función de eventTime, no podemos saber si todos los datos están en su lugar, pero no podemos esperar indefinidamente. En este momento, debe haber un mecanismo para asegurar un Después de un tiempo, la ventana debe activarse para realizar cálculos.Este mecanismo especial es Watermark.
  • La marca de agua es un mecanismo para medir el progreso del tiempo del evento.
  • La marca de agua se usa para manejar eventos fuera de orden , y el manejo correcto de eventos fuera de orden generalmente se realiza combinando el mecanismo de marca de agua con la ventana.
  • La marca de agua en el flujo de datos se utiliza para indicar que los datos cuya marca de tiempo es menor que la marca de agua ya han llegado, por lo que la ejecución de la ventana también se activa mediante la marca de agua.
  • La marca de agua puede entenderse como un mecanismo de activación retardada. Podemos establecer el tiempo de retardo t de la marca de agua. Cada vez que el sistema verificará el tiempo máximo de evento máximo en los datos que han llegado, y luego determinará que todos los datos cuyo tiempo de evento sea menor que el tiempo máximo de evento-t han llegado, si los hay El tiempo de parada de la ventana es igual a maxEventTime-t, luego esta ventana se activa para ejecutarse.

El marcador de agua del flujo ordenado se muestra en la siguiente figura: (La marca de agua se establece en 0):

Inserte la descripción de la imagen aquí

Marca de agua para datos ordenados en gráficos
El marcador de agua del flujo desordenado se muestra en la siguiente figura: (La marca de agua se establece en 2):

Inserte la descripción de la imagen aquí

Marca de agua de los datos desordenados del gráfico
Cuando Flink recibe datos, generará una marca de agua de acuerdo con ciertas reglas. Esta marca de agua es igual a `` maxEventTime-delay duration``` en todos los datos que llegan actualmente. Es decir, la marca de agua es llevada por los datos. La marca de agua que llevan los datos es posterior a la hora de finalización de la ventana actualmente no activada, luego se activará la ejecución de la ventana correspondiente. Dado que la marca de agua es transportada por datos, si no se pueden obtener nuevos datos durante el funcionamiento, la ventana que no se activa nunca se activará.

La figura anterior nos permite establecer el tiempo máximo de retraso de llegada para los 2 s, por lo que la marca de tiempo para el evento correspondiente 7s Watermark es 5s, el evento Watermark de marca de tiempo 12s es 10s, si lo 窗口1estamos 1s~5s, 窗口2es 6s ~ 10s, entonces 时间戳为7sLa hora de llegada de la marca de agua del evento desencadenante ocurre 窗口1, la 时间戳为12smarca de agua cuando llega el evento solo se activa 窗口2.

La marca de agua es el "tiempo de cierre de la ventana" que activó la ventana anterior. Una vez que la puerta está cerrada, todos los datos dentro del rango de la ventana en función de la hora actual se incluirán en la ventana.

Mientras no se alcance el nivel del agua, no importa cuánto avance el tiempo real, la ventana no se activará.

3.2 Introducción de WaterMark

La introducción de Watermark es muy simple. Para datos fuera de orden, los métodos de cita más comunes son los siguientes:

stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(2)){
    
    
    override def extractTimestamp(element: SensorReading) = {
    
    
        element.timestamp * 1000
    }
})

El uso de Event Time debe especificar la marca de tiempo en la fuente de datos. De lo contrario, el programa no puede conocer la hora del evento del evento (si los datos en la fuente de datos no tienen una marca de tiempo, solo puede usar el Tiempo de procesamiento).

Vemos que en el ejemplo anterior se crea una clase que parece un poco complicada, esta clase realmente implementa la interfaz para asignar marcas de tiempo. Flink expone la interfaz TimestampAssigner para que la implementemos, de modo que podamos personalizar cómo extraer la marca de tiempo de los datos del evento.

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val readings:DataStream[SensorReading]=env
.addSource(newSensorSource)
.assignTimestampsAndWatermarks(newMyAssigner())

Hay dos tipos de MyAssigner:

  • AssignerWithPeriodicWatermarks

  • Asignador con marcas de agua puntuadas

Las dos interfaces anteriores se heredan de TimestampAssigner.

3.2.1 Generar marca de agua periódicamente

Generar marcas de agua periódicamente: el sistema insertará marcas de agua periódicamente en la corriente (¡la marca de agua también es un evento especial!). El período predeterminado es 200 milisegundos. Puede utilizar el ExecutionConfig.setAutoWatermarkInterval()método para configurar.

env.getConfig.setAutoWatermarkInterval(5000)

La lógica de generar una marca de agua: cada 5 segundos, Flink llamará AssignerWithPeriodicWatermarksal getCurrentWatermark()método. Si el método devuelve una marca de tiempo con una marca de tiempo mayor que la marca de agua anterior, la nueva marca de agua se insertará en la secuencia. Esta comprobación asegura que el nivel del agua aumenta de forma monótona. Si la marca de tiempo devuelta por el método es menor o igual que la marca de tiempo del nivel de agua anterior, no se generará una nueva marca de agua.

Por ejemplo, personalice una extracción de marca de tiempo periódica:

package com.flink.scala

import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.watermark.Watermark

/**
  * 自定义周期性的时间戳抽取类
  */
class PeriodicAssigner extends AssignerWithPeriodicWatermarks[SensorReading] {
    
    

  val bound: Long = 60 *1000 //延时 1 分钟
  var maxTs: Long = Long.MinValue // 观察到的最大时间戳

  override def getCurrentWatermark: Watermark = {
    
    
    new Watermark(maxTs - bound)
  }

  override def extractTimestamp(t: SensorReading, l: Long): Long = {
    
    
    maxTs = maxTs.max(t.timestamp)
    t.timestamp
  }
}

Un caso especial simple es que si sabemos de antemano que la marca de tiempo del flujo de datos aumenta monótonamente, es decir, no hay desorden, entonces podemos usarlo assignAscendingTimestamps. Este método utilizará directamente la marca de tiempo de los datos para generar la marca de agua.

val withTimestampsAndWatermarks = stream.assignAscendingTimestamps(e=>e.timestamp)

Para los flujos de datos fuera de orden, si podemos estimar aproximadamente el tiempo de retardo máximo de los eventos en el flujo de datos, podemos usar el siguiente código:

val withTimestampsAndWatermarks = stream.assignTimestampsAndWatermarks(newSensorTimeAssigner)
class SensorTimeAssigner extends
BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(5)){
    
    
    //抽取时间戳
    override def extractTimestamp(r:SensorReading):Long=r.timestamp
}

3.2.2 Generar marca de agua de forma intermitente

La generación intermitente de marcas de agua es diferente de la generación periódica. Este método no es de tiempo fijo, pero puede filtrar y procesar cada dato según sea necesario. Simplemente vaya al código para dar un ejemplo, solo insertamos una marca de agua en el flujo de datos del sensor de sensor_1:

package com.flink.scala

import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
import org.apache.flink.streaming.api.watermark.Watermark

/**
  * 间断式生成 Watermark
  */
class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks[SensorReading] {
    
    
  val bound:Long=60*1000
  override def checkAndGetNextWatermark(t: SensorReading, extractedTS: Long): Watermark = {
    
    
    if ("sensor_1".equals(t.id)) {
    
    
      new Watermark(extractedTS - bound)
    } else {
    
    
      null
    }
  }

  override def extractTimestamp(t: SensorReading, l: Long): Long = {
    
    
    t.timestamp
  }
}

4. Uso de EvnetTime en la ventana

4.1 Ventana móvil (TumblingEventTimeWindows)

package com.flink.scala


import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{
    
    DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

import scala.collection.mutable

object TumblingEventTimeWindowsTest {
    
    

  def main(args: Array[String]): Unit = {
    
    
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
    val stream: DataStream[String] = env.socketTextStream("127.0.0.1",8080)
    val textWithTsDstream: DataStream[(String, Long, Int)] = stream.map(text => {
    
    
      val arr: Array[String] = text.split(" ")
      (arr(0), arr(1).toLong, 1)
    })
    val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, Long, Int)]() {
    
    
      override def extractTimestamp(t: (String, Long, Int)) = t._2
    })
    val textKeyStream = textWithEventTimeDstream.keyBy(0)
    textKeyStream.print("textKey:")
    val windowStream = textKeyStream.window(TumblingEventTimeWindows.of(Time.seconds(2)))
    val groupDstream: DataStream[mutable.HashSet[Long]] =
    windowStream.fold(new mutable.HashSet[Long]()) {
    
    case(set, (key, ts, count))=>
      set += ts
    }
    groupDstream.print("window::::").setParallelism(1)
    env.execute()
  }
}

El resultado se calcula de acuerdo con la ventana de tiempo de Event Time, independientemente del tiempo del sistema (incluida la velocidad de entrada).

4.2 Ventana deslizante (SlidingEventTimeWindows)

package com.flink.scala


import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{
    
    DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.assigners.{
    
    SlidingEventTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time

import scala.collection.mutable


object SlidingEventTimeWindowsTest {
    
    

  def main(args: Array[String]): Unit = {
    
    
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
    val stream: DataStream[String] = env.socketTextStream("127.0.0.1",8080)
    val textWithTsDstream: DataStream[(String, Long, Int)] = stream.map(text => {
    
    
      val arr: Array[String] = text.split(" ")
      (arr(0), arr(1).toLong, 1)
    })
    val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, Long, Int)]() {
    
    
      override def extractTimestamp(t: (String, Long, Int)) = t._2
    })
    val textKeyStream = textWithEventTimeDstream.keyBy(0)
    textKeyStream.print("textKey:")
    val windowStream = textKeyStream.window(SlidingEventTimeWindows.of(Time.seconds(2),Time.milliseconds(500)))

    val groupDstream: DataStream[mutable.HashSet[Long]] =
    windowStream.fold(new mutable.HashSet[Long]()) {
    
    case(set, (key, ts, count))=>
      set += ts
    }
    groupDstream.print("window::::").setParallelism(1)
    env.execute()
  }
}

4.3 Ventana de sesión (EventTimeSessionWindows)

La ejecución se activará si la diferencia de tiempo entre el EventTime de dos datos consecutivos excede el intervalo de tiempo especificado. Si agrega Marca de agua, se retrasará cuando se cumpla el activador de ventana. Cuando se alcanza el nivel de retardo, se realiza el disparo de ventana.

package com.flink.scala


import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{
    
    DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.assigners.{
    
    EventTimeSessionWindows, SlidingEventTimeWindows, TumblingEventTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time

object EventTimeSessionWindowsTest {
    
    

  def main(args: Array[String]): Unit = {
    
    
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
    val stream: DataStream[String] = env.socketTextStream("127.0.0.1",8080)
    val textWithTsDstream: DataStream[(String, Long, Int)] = stream.map(text => {
    
    
      val arr: Array[String] = text.split(" ")
      (arr(0), arr(1).toLong, 1)
    })
    val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, Long, Int)]() {
    
    
      override def extractTimestamp(t: (String, Long, Int)) = t._2
    })
    val textKeyStream = textWithEventTimeDstream.keyBy(0)
    textKeyStream.print("textKey:")
    val windowStream = textKeyStream.window(EventTimeSessionWindows.withGap(Time.milliseconds(500)))
    windowStream.reduce((text1,text2) =>
      (text1._1, 0L, text1._3 + text2._3)
    ).map(_._3).print("windows:::").setParallelism(1)

    env.execute()
  }
}

Supongo que te gusta

Origin blog.csdn.net/dwjf321/article/details/109068600
Recomendado
Clasificación