I.はじめに
Flinkは、ウィンドウにさまざまなカスタムトリガーを提供しますが、その中には、CountTriggerとProcessingTimeTriggerがあります。2つのトリガーの内部実装の原則と、2つのデモを通じてウィンドウトリガーの知識について学びましょう。
2.補助的な知識
上記の2つのトリガーを紹介する前に、以前に改善されたトリガーの基本的な知識を確認しましょう。
1.内部メソッドをトリガーします
onElement:要素が到着した後に実行するアクション
・onProcessingTime:指定された処理時間ウィンドウに達したときに実行される操作
・onEventTime:指定されたイベント時間ウィンドウに達したときに実行される操作
clear:関連する値変数をクリアします
2.ウィンドウトリガー後の操作
TriggerResult.CONTINUE:スキップして何もしない
TriggerResult.FIRE:トリガーウィンドウの計算
TriggerResult.PURGE:ウィンドウ要素をクリアします
TriggerResult.FIRE_AND_PURGE:ウィンドウ操作をトリガーしてから、ウィンドウ要素をクリアします
3.ReducingStateValue
ReducingStateValueは抽象的な統計であり、ユーザーがその戻りタイプと対応するreduce操作を定義する必要があります。ここで、reduceは削減を意味するのではなく、マージを意味します。これは、sparkのreduce(_ + _)操作として理解できます。与えられたオブジェクトに対してobject1とobject2は単一のオブジェクトに合成され、変数を定義する方法は次のとおりです。
val reduceStateDesc = new ReducingStateDescriptor[T]($key, new ReduceFunction(), classOf[T])
Tは戻り変数タイプを表し、キーはその名前識別子です。ReduceFunctionはorg.apache.flink.api.common.functions.ReduceFunctionを継承して、reduce(o1:T、o2:T):Tのメソッドを実装する必要があります。次の例では、1つを生成して2つを取得します。最小の数値のRecuceFunction:
class ReduceMin() extends ReduceFunction[Long] {
override def reduce(t1: Long, t2: Long): Long = {
math.min(1, t2)
}
}
4.ウィンドウのデフォルトのトリガータイミング
dataStream.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))
ローリングウィンドウTumblingProcessingTimeWindowsなどのorg.apache.flink.streaming.api.windowing.assignersクラスの下のウィンドウを使用する場合、設定した時間に応じて、ウィンドウの開始時間と終了時間が実際に固定されます。パラメータの後上記の10秒のローリングウィンドウを例にとると、ウィンドウのデフォルトの開始時間と終了時間は、18:00から始まる1時間です。
18:00:00 - 18:00:10 ,18:00:10 - 18:00:20 ... 18:59:50 - 19:00:00
ウィンドウが指定された終了時間に達すると、デフォルトでonProcessingTimeメソッドが呼び出されます。ここで理解していなくても、デモを待ってください。
3.CountTriggerの詳細な説明
CountTriggerは、要素の数に応じてトリガーされます。要素の数がcount_sizeに達すると、ウィンドウの計算ロジックがトリガーされ、前述のReduceStateValueを使用して、内部カウントの数がカウントされます。実行プロセスでは、SelfDefinedCountTriggerがここに追加されます。コードは公式のCountTriggerとまったく同じですが、唯一の違いはログ印刷の追加です。
1.SelfDefinedCountTrigger
トリガー時間とトリガーされた要素の数がトリガーに追加されます。メインロジックはonElement関数にあります。要素が来るたびに、トリガーのReduceStateValueが累積されます。ここでは、RecudeSum関数を使用して2つを合計します。値がcountSizeに達すると、ウィンドウはFireを再トリガーし、ReduceStateValueをクリアして、新しいカウントラウンドを開始します。残りの時間は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.主な機能
主な関数ロジックは0から始まり、毎秒30の数値を生成し、ループに蓄積します。ウィンドウは10の集約ローリングウィンドウを使用し、トリガーはカウント= 30のCountTriggerを使用します。理論的には、毎秒30の要素が生成されます。ウィンドウ実行ロジックをトリガーします。ウィンドウ処理ロジックも非常に単純で、現在のウィンドウの要素数、要素の最小値、最大値、および処理時間を直接出力します。
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.実行ログ
起動時の整数時間ではないため、最初のウィンドウは8:30:02から8:30:10までの8秒のデータのみを処理します。最初のウィンドウのロジックが終了した後、以下は10秒のスライディングウィンドウから安定します。30要素は一度にトリガーされ、10秒以内のウィンドウデータの変化は、Window Elem Num、10s x 30=300で確認できます。
4.ウィンドウのデフォルトのトリガーメカニズム
上記の改善されたウィンドウは、デフォルトの終了時間にonProcessingTimeメソッドを実行します。メソッドにはTriggerResult.CONTINUEのみが返されるため、明確ではありません。onProcessingTimeメソッドにログを追加して、次のことを確認しましょう。
override def onProcessingTime(l: Long, w: TimeWindow, triggerContext: Trigger.TriggerContext): TriggerResult = {
println(s"Window Trigger At Default End Time: $l")
TriggerResult.CONTINUE
}
ビュー・ログ:
ログ情報から、ウィンドウが実際に終了時にonProcessingTimeメソッドを実行することがわかりますが、なぜ整数ではないのですか?
これは、ウィンドウによって実際にトリガーされるtimeStampが、次のように定義されているwindow.maxTimestamp()変数に対応するメソッドであるためです。
public long maxTimestamp() {
return this.end - 1L;
}
ソースコードは、デフォルトの終了に対応するタイムスタンプから1を減算し、実際のウィンドウ終了時間は1648034659999+1です。
したがって、ここでは2つの問題を検証します。1つはウィンドウがデフォルトの終了時間にonProcessingTimeメソッドを呼び出すこと、もう1つはウィンドウの終了時間と実際のトリガー時間の差が1L、window.maxTimestampであるということです。 + 1=window.getEnd。
4.ProcessingTimeTriggerの詳細な説明
processingTimeTriggerは、Flinkプログラムウィンドウに対応するprocessTimeのデフォルトのトリガーです。これは、ウィンドウのデフォルトの開始時間と終了時間に従ってトリガーされるか、10秒のTumblingProcessingTimeWindowsウィンドウとカスタムSelfDefinedProcessingTimeTriggerがデモ表示に使用されます。
1.SelfDefinedProcessingTimeTrigger
ProcesingTimeTriggerの主なメソッドはonElementとonProcessingTimeです。前者はウィンドウをtimeServerに登録し、その有効期限はwindow.maxTimestampです。前述のように、時間はwindow.getEnd()-1です。後者はFireトリガーウィンドウの計算を実行します。詳細な時間情報については、processingTimeとwindow-startwindow-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.主な機能
データソースはカスタムソースであり、毎秒30個の要素が生成され、ローリングウィンドウは10秒のサイクルで生成されます。処理ロジックは出力ウィンドウの要素数(最小および最大)のままです。
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.実行ログ
ウィンドウは10秒ごとにトリガーを実行し、トリガー要素の数は10x30 = 300です。この例では、window-start window-endとそれに対応するフォーマットの時間形式を確認し、maxTimestamp=window.end-1であることを再度確認できます。 。
5.まとめ
公式のトリガーを微調整してログを追加することで、CountTriggerとProcessingTimeTriggerの最も一般的な実行ロジックを確認し、ウィンドウトリガーのロジックを深めることができます。後で、CountTriggerとProcessTimeTriggerを組み合わせて、トリガー条件を組み合わせたカスタムCountAndTimeTriggerを実装します。 CountとProcessingTimeの数。、バーの数または間隔が満たされたときにウィンドウをトリガーすることができます。