1.問題分析
元のプログラムはEventTimeを使用し、JobGraphはSource + KeyBy + ProcessFunction + Window + Sinkの形式です。ProcessFunctionはValueStateとonTimerのメカニズムを設定します。タスクに必要な一部のリアルタイム変数は定期的に更新する必要があるため、BroadcastStreamは、リアルタイム変数の不規則な更新を実装するために導入されました。変更されたJobGraphは、図に示すように、ソース+キー付き-ブロードキャスト-プロセス+ウィンドウ+シンクです。
タスクが実行された後、3番目のステップのCo-Process-Broadcast-keyed Stage部分のRecords-Received部分は正常ですが、送信されたバイト数が消えます。つまり、ダウンストリーム(onTimer)データがありません。ウィンドウに書き込まれ、3番目のステップであるウィンドウですべてのデータがスタックします。トリガーできません:
下流の演算子を見て、WaterMarkが空であることを確認します。
このとき、タスクがEventTimeに設定され、元のソースがタイムスタンプと透かしを設定したことを思い出しましたが、新しく追加されたBroadcastStreamがタイムスタンプと透かしを設定しなかったため、別のソースが透かしを更新しなかったため、タスク全体の透かしが進むことができず、その結果、タスクがスタックします。したがって、解決策は、BroadcastStreamのassignTimestampsAndWatermarksメソッドを実装して、タイムスタンプと透かしを設定することです。
2.問題の修復
broadcastStreamのトピックフレームワークは次のとおりです。完全な例が必要な場合は、以下を参照でき ます。DataStreamブロードキャストの例の詳細
DataStream<T> output = dataStream
.connect(BroadcastStream)
.process(
// KeyedBroadcastProcessFunction 中的类型参数表示:
// 1. key stream 中的 key 类型
// 2. 非广播流中的元素类型
// 3. 广播流中的元素类型
// 4. 结果的类型,在这里是 string
new KeyedBroadcastProcessFunction<Ks, In1, In2, Out>() {
// 模式匹配逻辑
}
);
BroadcastStreamについて詳しく話しましょう。タスクが失敗する前は、BroadcastStreamは次のように定義されていました。
MapStateDescriptor<String, T> descriptor = new MapStateDescriptor<>("T", String.class, T.class);
BroadcastStream<T> contextStream = env
.addSource(new SelfDefinedSource())
.setParallelism(1)
.broadcast(descriptor);
ここで、更新されたオンライン変数は、カスタムSelfDefinedSource関数を介して定期的に読み取られ、ctx.collect()を介してBroadcastStreamに生成されます。データストリームに透かしが設定されていないため、タスクは失敗します。以下は、設定するBoundedOutOfOrdernessTimestampExtractorクラスを継承します。データストリームの透かし。:
public static class ContextTSExtrator extends BoundedOutOfOrdernessTimestampExtractor<T> {
public MultiSortContextTSExtrator() {
super(Time.seconds(MAX_SEND_EVENT_DELAY));
}
@Override
public long extractTimestamp(T t) {
return System.currentTimeMillis();
}
}
これはSystem.currentTimeMillisに直接設定されます。Tからタイムスタンプを取得する必要がある場合は、Tを生成するときにそのEventTimeStampをバインドし、assignメソッドを使用してタイムスタンプをソースデータに追加することもできます。
BroadcastStream<T> contextStream = env
.addSource(new SelfDefinedSource())
.assignTimestampsAndWatermarks(newContextTSExtrator())
.setParallelism(1)
.broadcast(descriptor);
タイムスタンプを追加すると、タスクは正常に実行されます。フローチャートのkeyd-Broadcast-Process部分とwindow部分にもリアルタイムで更新された透かしがあり、タスクは正常に実行されていることがわかります。
3.最適化
processFunctionで処理されたデータの設定時間=tの有効期限、つまりeventTime + tはonTimerのタイミングであり、BroadCastStreamはX分ごとに行の最新の変数を読み取り、更新して最終的に送信します。 context.collect(T)、X分Times.sleep()によって実装されます。理想的には、各データ間の間隔はtですが、ステージ内のログとログによると、シンクに処理される各要素間の間隔はt + Xであることがわかります。つまり、遅延処理時間は次のようになります。リアルタイム変数の読み取りのタイミングをとるための間隔。
上記の新しく追加されたwaterMarkはSystem.currentimeMillesですが、X分ごとに実行されるため、broadcastStreamによって送信されるwaterMarkは常に遅れています。この事実に基づいて、問題のトラブルシューティングを続行します。
1.シングルストリーム透かしメカニズム
まず、各データソースのウォーターマーク更新メカニズムを見てみましょう。これは、AssignerWithPeriodicWatermarksクラスで確認でき、デフォルトでオーバーライドする必要はありません。
public final Watermark getCurrentWatermark() {
long potentialWM = this.currentMaxTimestamp - this.maxOutOfOrderness;
if (potentialWM >= this.lastEmittedWatermark) {
this.lastEmittedWatermark = potentialWM;
}
return new Watermark(this.lastEmittedWatermark);
現在の要素のタイムスタンプから最大遅延許容時間を差し引いて、poettialWaterMarkを取得します。最後に発行されたwaterMarkより大きい場合は、再割り当てします。つまり、currentWaterMark = Max(poentitialWaterMark、lastEmittedWaterMark)を取得します。これは、Flinkのデータを表します。 currentWaterMark値よりも小さいと見なされます。到着しました。
2.マルチストリーム透かしメカニズム
co-broadcast-keyed-streamはデュアルストリームタスクであり、各ストリームにはwaterMarkが含まれ、2つのストリームは同時にwaterMarkを放出します。Flinkの最下層は、TwoInputStreamOperatorを継承することにより、デュアルストリーム要素とwaterMarkの処理を完了します。インターフェース:
public interface TwoInputStreamOperator<IN1, IN2, OUT> extends StreamOperator<OUT> {
void processElement1(StreamRecord<IN1> var1) throws Exception;
void processElement2(StreamRecord<IN2> var1) throws Exception;
void processWatermark1(Watermark var1) throws Exception;
void processWatermark2(Watermark var1) throws Exception;
void processLatencyMarker1(LatencyMarker var1) throws Exception;
void processLatencyMarker2(LatencyMarker var1) throws Exception;
}
上記で発生した遅延の問題に基づいて、AbstractStreamOperator抽象クラスの下に配置されるprocessWatermarkのロジックに焦点を当てる必要があります。
public void processWatermark(Watermark mark) throws Exception {
if (this.timeServiceManager != null) {
this.timeServiceManager.advanceWatermark(mark);
}
this.output.emitWatermark(mark);
}
public void processWatermark1(Watermark mark) throws Exception {
this.input1Watermark = mark.getTimestamp();
long newMin = Math.min(this.input1Watermark, this.input2Watermark);
if (newMin > this.combinedWatermark) {
this.combinedWatermark = newMin;
this.processWatermark(new Watermark(this.combinedWatermark));
}
}
public void processWatermark2(Watermark mark) throws Exception {
this.input2Watermark = mark.getTimestamp();
long newMin = Math.min(this.input1Watermark, this.input2Watermark);
if (newMin > this.combinedWatermark) {
this.combinedWatermark = newMin;
this.processWatermark(new Watermark(this.combinedWatermark));
}
}
Stream1とStream2に対してそれぞれprocessWatermark1とprocessWatermark2を実行します。Minメソッドは、2つのストリームのうち最小のものを新しい最小WaterMarkとして選択し、それをlastEmittedWatermarkと比較して、タスク全体の透かしフローを進めます。したがって、待機時間Xが長すぎるという問題に対応するために、BroadcastStreamの透かし生成効率を改善する、つまりTを生成する間隔を短くする必要があります。
3.透かしの送信間隔を短くします
間隔Xを非常に小さい間隔xに短縮するには、生成されたTに有効な属性を追加し、ソース関数にエポックコントロールを追加する必要があります。エポック* x = Xの場合にのみ、オンライン更新を読み取り、有効な= true T、残りの時間はvalid = false T(null)を送信し、ProcessBroadcastValueがtrueの場合にのみ有効な状態を判断し、Broadcastの値を各タスクに更新します。
override def run(sourceContext: SourceFunction.SourceContext[T]): Unit = {
Client client = new Socket(host, port);
while (isRunning) {
val t = if (epoch % X == 0) {
... 读取线上变量
re.setValid() // 生效
re
} else {
val re = new T(null)
re
}
sourceContext.collect(t)
TimeUnit.SECONDS.sleep(x)
epoch += 1
}
}
たとえば、Tは以前にX = 300秒の間隔で生成され、間隔がx = 5秒に短縮された場合、エポック= 300/5 = 60と計算されるため、ifロジックはエポック%60 == 0、残りの時間は類推によって設計されています。
4.より良い解決策
上記のスキームはXからxまでの間隔を最適化しますが、タスク実行のデータソースは実際には別のDataStreamであり、BroadcastStreamはDataStreamに必要な変数の定期的な更新のみを担当するため、最善の解決策はタスクの透かしフローが完全に依存することです。 DataStreamで。次に、デュアルストリームプロセスウォーターマーク関数を振り返ります。
public void processWatermarkX(Watermark mark) throws Exception {
this.input2Watermark = mark.getTimestamp();
long newMin = Math.min(this.input1Watermark, this.input2Watermark);
if (newMin > this.combinedWatermark) {
this.combinedWatermark = newMin;
this.processWatermark(new Watermark(this.combinedWatermark));
}
}
newMinは、2つのストリームの最小値を取得し、最小値をベンチマークとして使用して単一のストリームに縮退し、DataStreamのみに依存するWaterMark更新ロジックを更新します。次に、ブロードキャストのwaterMarkをMaxに設定します。そして、それは終わっていないので、newMinの値は毎回変更されます。watrtMarkのフローが完全にDataStreamに依存するように、DataStreamに依存します。これにより、最後に短い間隔xがなくなり、遅延が発生します。タスク実行の時間は、完全にonTimerによって設定されたvalueStateの有効期限です。
BroadcastStreamの機能を変更して、タイムスタンプと透かしを設定します。
Watermarkクラスに付属するMAX_WARTERMARK.getTimeStamp()に直接設定するか、非常に大きなタイムスタンプを自分で設定できます。
public static class ContextTSExtrator extends BoundedOutOfOrdernessTimestampExtractor<T> {
public MultiSortContextTSExtrator() {
super(Time.seconds(MAX_SEND_EVENT_DELAY));
}
@Override
public long extractTimestamp(T t) {
return Watermark.MAX_WATERMARK.getTimestamp();
}
}
その値は次のとおりです。
static final Watermark MAX_WATERMARK = new Watermark(9223372036854775807L);
4.まとめ
透かしロジックを追加および変更することにより、broadcastStreamを追加するデュアルストリームタスクが最終的に正常に実行され、データフローは元のデータストリームに完全に依存します。これは非常に完璧です。このような問題は、Flinkの透かしメカニズムについてはまだはっきりしておらず、今後も透かしについての理解を深めていく必要があります。