Flink-カウントと時間に基づくCountAndProcessingTimeTriggerトリガーウィンドウ

I.はじめに

前の記事では、  CountTrigger && ProcessingTimeTriggerについて説明しました。前者のCountTriggerは、カウント数を指定し、ウィンドウ内の要素がロジックに一致したときにトリガーをトリガーします。後者は、TimeServerを介してウィンドウの有効期限を登録し、有効期限後にトリガーをトリガーします。この記事では、両方を達成するためのトリガーCountとProcessingTimeの組み合わせは、いずれかの条件が満たされたときにウィンドウをトリガーします。

2.詳細なコードの説明

1.CountAndProcessingTimeTrigger

全体的なコードは次のとおりです。メインロジックはonElementとonProcessingTimeに含まれ、前者は主にカウントに応じたトリガー、つまりCountTriggerの機能を実現し、後者は主にProcessingTimeの機能を実現します。 CountとTimeをそれぞれ記録するために事前に2つのReduceValuesを定義する必要があります。ReduceValueの詳細な使用法は次のとおりです。上記を参照して、主なメソッドを以下で分析します。

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.onElement

count.addを実行して、各要素が到着したときにカウントし、定義されたmaxCountを超えた場合に操作をトリガーします。

----MaxCountに到達する

A.log-CountTriggerからこのトリガーを識別するためのログを印刷します

B.count.clear-値をクリアして、カウントを再累積し、トリガーします

C.deleteProcessingTime-トリガー後にCountとProcessingTimeの両方を再カウントまたはタイミング調整する必要があるため、TimeServerカウンターをクリアします

-----MaxCountに達していません

A.currentTime-ctxコンテキストを介して現在のProcessingTimeを取得します

B.registerProcessingTimeTimer-時間値に値があるかどうかを判断します。値がない場合は、現在と間隔に従って、ProcessingTimeに対応する次のウィンドウトリガー時間を計算します。

-----満足していない

A.TriggerResult.CONTINUE-トリガーせず、TimeServerの有効期限が切れるのを待ちます

  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.onProcessingTime

指定された処理時間ウィンドウに達したときに実行される操作。ウィンドウはonProcessingTimeメソッドを2回呼び出すことを前述しました。1つは、それ自体で定義されたProcessintTimeTimerに到達することであり、ウィンドウはトリガーを起動します。このとき、トリガーdataはウィンドウデータの一部であり、1つはwindow.maxTimeStampに到達すること、つまりwindow.getEnd-1Lに到達することです。この時点で、ウィンドウFireによってトリガーされるデータは、windowAllで定義された時間範囲内のすべてのデータです。 、Time.seconds(10)の定義など、前者は時間データの一部をトリガーし、後者はFull10sウィンドウをトリガーします。

-----到着ウィンドウのデフォルトのトリガー時間

A.window.maxTimestamp-ウィンドウに到達するためのデフォルトの時間、対応するログ識別子を出力します

B.count.clear-カウント状態をクリアします

C.deleteProcessingTime-元のカウンターをクリアします。これは、このウィンドウがトリガーされた後に再カウントされ、タイミングが取られるためです。

D.registerProcessingTIme-現在のProcessingTime+間隔に基づいて次回登録します

E.TriggerResult.FIRE-完全なデータウィンドウトリガーを実行します

-----カスタム間隔間隔に到達します

A.ログロゴ-TimeTriggeredロゴを印刷このトリガーはカスタムProcessingTimeから取得されます

B.clear-ウィンドウがトリガーされた後、元のカウント状態をクリアします

C.registerProcessingTIme-現在のProcessingTime+間隔に基づいて次回登録します

D.TriggerResult.FIRE-トリガーウィンドウデータ

-----満足していない

A.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
  }

4.onEventTime

これはCountとProcessingTimeに基づいているため、onEventTimeはTriggerResult.CONTINUEを返します。

5.クリア

CountとFireTimestampに対応するReduceValueをクリアします

3.コードの練習

1.主な機能

上記のSoucreofCountTriggerとProcessingTimeTriggerは固定データソースであり、毎秒30個のデータを送信します。CountAndProcessingTimeTriggerを検証するために、ここではソケットを使用してカスタム送信データを実装します。ローカルのnc-lkポートを開くことができ、processFunctionは次のことを実現できます。ウィンドウ内のデータ。最小、最大の統計および処理時間の出力。

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.データ検証

上記のCountAndProcessintTimeTriggerは、count = 10、interval=5sに設定されています

トリガー方式 入力 処理する
CountTrigger 1-10 count = 10を満たし、CountTriggerをトリガーします
DeaultTrigger デフォルトのトリガー、フルウィンドウデータは1〜10です
ProcessingTimeTrigger 11,12 11、12を入力して、ProcessingTimeTriggerをトリガーします
黄色 DeaultTrigger 13 終了前に13と入力すると、ウィンドウ全体のデータは11〜13になります。
ProcessingTimeTrigger 14 14と入力し、ProcessingTimeTriggerがトリガーされるのを待ちます
DeaultTrigger デフォルトでトリガーされますが、現時点ではウィンドウ全体のデータは14です。

間隔が5秒である理由の説明ですが、ProcessingTimeTriggerの出力ログ時間は16と27です。これは、手動でソケットに入るのが遅れるためです。マシンがデフォルトでデータを送信する場合、ログは次のように修正されます。 15および25を使用して、ProcessingTimeTriggerをトリガーします。

4.もっと

CountTriggerのロジックは比較的単純です。ProcessingTimeTriggerはここでの定義方法にすぎません。つまり、有効期限はウィンドウの最後でリセットされるか、ウィンドウ間で定義するか、整数時間を設定しないようにすることができます。 、あなたはそれをカスタマイズすることができます。この記事でカスタマイズしたCountTrigger、ProcessingTrigger、CountAndProcessingTimeTriggerの場合、ウィンドウがトリガーされると、FIREのみが呼び出され、FIRE_AND_PURGEは呼び出されずにクリア操作が実行されることがわかりました。ウィンドウデータがクリアされない場合、さらに蓄積され、より多くのバーストストレージ?はい、WindowOperator関数には組み込みのデフォルトのonProcessingTimeメソッドがあり、データをクリアするために内部でclearAllStateメソッドを判断して呼び出します。

 WindowOperatorは、すべてのウィンドウ処理ロジックのエントリポイントです。TriggerがTiggerResult.FIREを返す場合、ウィンドウはclearAllStateメソッドを実行して、CleanupTimeに達したときに現在のすべてのウィンドウ状態をクリアします。TriggerResult.FIRE_AND_PURGEを返す場合、windowOperatorはclearを呼び出します。 Trigger @overrideのメソッド。たとえば、ProcessingTimeTriggerはウィンドウのタイマーをクリアし、この例でFIRE_AND_PURGEを返すと、countとfireTimestampに対応する2つのReduceValue値を同時にクリアします。

おすすめ

転載: blog.csdn.net/BIT_666/article/details/123740650