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値を同時にクリアします。