Flinkは古典的なコース2を知っている必要があります:ApacheFlinkを使用したストリーム処理

はじめに: この記事には、Apache Flinkを使用したスト​​リーム処理を紹介する3つのパートが含まれています:1。並列処理とプログラミングパラダイム;2。DataStreamAPIの概要と簡単なアプリケーション;3。Flinkのステータスと時間。

著者❤CuiXingcan

この記事には、ApacheFlinkを使用したスト​​リーム処理を紹介する3つのパートが含まれています。

  1. 並列処理とプログラミングパラダイム
  2. DataStreamAPIの概要と簡単なアプリケーション
  3. Flinkのステータスと時間

1.並列処理とプログラミングパラダイム

ご存知のとおり、比較的大量の計算を必要とする計算集約型またはデータ集約型のタスクの場合、並列計算または分割統治は、このタイプの問題を解決するための非常に効果的な手段です。このアプローチのより重要な部分は、既存のタスクを分割する方法、またはコンピューティングリソースを合理的に割り当てる方法です。

たとえば、学校では、教師がクラスメートを見つけて試験問題の復習を支援することがあります。論文に合計3つの質問ABCがある場合、学生は次の分業と協力を行うことができます。

  • 方法1:すべてのテストペーパーの3つの質問を、レビューのためにさまざまな人に渡します。このようにして、各レビューアは、自分が担当する主題をレビューした後、次のレビューアにテストペーパーを渡すことができます。これにより、組立ライン効果が形成されます。質問は全部で3つしかないため、このパイプラインコラボレーション方式は、学生数が増えるにつれて拡大し続けることは困難です。
  • 方法2:分業の延長。複数の学生が同じトピックをレビューできます。たとえば、トピックAは2人の学生がレビューし、トピックBは3人の学生がレビューし、トピックCは1人の学生だけがレビューします。現時点では、コンピューティングタスクをさらに分割する方法を検討する必要があります。たとえば、すべての生徒を3つのグループに分けることができます。最初のグループは質問Aを担当し、2番目のグループは質問Bを担当し、3番目のグループは質問Cを担当します。最初のグループの生徒は、グループ内の作業を再度分割できます。たとえば、グループAでは、最初の生徒が論文の半分を承認し、2番目の生徒が残りの半分の論文を承認します。彼らは別々にそれらを承認した後、彼らは次のグループに彼らの手でテストペーパーを渡します。

前述のように、テストペーパーの質問による分割とテストペーパー自体の分割は、いわゆるコンピューティングの並列処理とデータの並列処理です。

image.png

上記の有向非巡回グラフを使用して、この並列性を表現できます。

この図では、質問Aを確認する生徒は、教師のオフィスからテスト用紙を確認する場所にテスト用紙を持っていくなど、いくつかの追加タスクを実行したと想定しています。質問Cを担当する生徒にも追加のタスクがあります。タスクは、すべての学生がテストペーパーを終了するのを待つことです。、合計スコア統計を実行し、提出の作業を記録します。これに基づいて、グラフ内のすべてのノードを3つのカテゴリに分類できます。最初のカテゴリはソースであり、データの取得(テストペーパーの取得)を担当します。2番目のカテゴリはデータ処理ノードであり、ほとんどの場合外部システムを処理する必要はありません。最後のカテゴリは計算全体の記述を担当します。外部システムへのロジック(システム分割してレコードを提出する)。これらの3つのタイプのノードは、ソースノード、変換ノード、およびシンクノードです。DAGダイアグラムでは、ノードは計算を表し、ノード間の接続は計算間の依存関係を表します。

プログラミングについての何か

image.png

1から10までの10個の数値を含むデータセットがあるとします。各数値に2を掛けて、累積合計演算を実行するにはどうすればよいですか(上の図を参照)。多くの方法があります。

プログラミングを使用して問題を解決する場合、2つの観点があります。1つは命令型プログラミングを採用することです。これは、データ構造を生成する方法、これらのデータ構造を使用して一時的な中間結果を格納する方法をマシンに指示することと同じです。方法これらの中間結果は、最終結果に変換されます。これは、マシンにステップバイステップで実行する方法を指示するのと同じです。2番目は宣言型の方法です。宣言型プログラミングでは、通常、マシンにどのタスクを実行するかを指示するだけで済みます。完全で、必須ではありません。詳細な配信。たとえば、元のデータセットをStreamに変換してから、StreamをInt型Streamに変換できます。このプロセスでは、すべての数値に2を掛け、最後にSumメソッドを呼び出してすべての数値を取得します。合計。

宣言型プログラミング言語のコードはより簡潔であり、簡潔な開発方法はまさに計算エンジンが追求する効果です。したがって、Flinkでのタスクの記述に関連するすべてのAPIは、宣言型に偏っています。

2. DataStreamAPIの概要と簡単なアプリケーション

DataStream APIを詳細に紹介する前に、FlinkAPIの論理レベルを見てみましょう。

image.png

古いバージョンのFlinkでは、そのAPI階層は上の図の左側にある4層の関係に従います。最上位層は、より高度なAPI、またはより宣言的なTableAPIおよびSQLメソッドを使用してロジックを記述できることを意味します。SQLおよびTableAPIで記述されたすべてのコンテンツは、Flinkによって内部的に変換され、DataStreamAPIで実装されたプログラムに最適化されます。次のレベルでは、DataStream APIプログラムは一連の変換として表され、最後に変換はJobGraph(つまり、上記のDAG)に変換されます。

新しいバージョンのFlinkでいくつかの変更が行われ、主な変更はTableAPIおよびSQLレイヤーに反映されています。DataStream APIプログラムに変換されなくなりますが、基盤となる変換レイヤーに直接到達します。つまり、DataStreamAPIとTableAPIの関係は、下位層と上位層の関係から水平方向の関係に変更されました。このプロセスの簡素化は、それに応じてクエリの最適化にいくつかの利点をもたらします。

次に、紹介する例として単純なDataStream APIプログラムを使用するか、2を掛けて上記を合計する要件を使用します。

image.png

Flinkで表現した場合、その基本的なコードを上の図に示します。スタンドアロンの例よりも少し複雑に思えます。段階的に分解してみましょう。

  • まず、Flinkで関数を実装するには、対応するオペレーティング環境であるSream ExecutionEnvironmentを取得する必要があります。
  • 次に、環境を取得した後、環境のadd Sourceメソッドを呼び出して、ロジックの初期データソースの入力を追加できます。データソースを設定した後、データであるデータソースへの参照を取得できます。ソースオブジェクト;
  • 最後に、一連の変換メソッドを呼び出して、データソース内のデータを変換できます。

この変換を図に示します。つまり、各数値は2倍であり、合計するには、keyByを使用してデータをグループ化する必要があります。入ってくる定数は、すべてのデータがグループに分割され、最後にこのグループのすべてのデータが最初のフィールドに従って累積され、結果が最終的に取得されることを意味します。結果を取得した後、スタンドアロンプ​​ログラムのように単純に出力することはできませんが、すべてのデータをターゲットの場所に書き込むには、ロジック全体にシンクノードを追加する必要があります。上記の作業が完了したら、EnvironmentのExecuteメソッドを呼び出して、上記で記述したすべてのロジックをリモートクラスターまたはローカルクラスターに送信して実行する必要があります。

Flink DataStream APIプログラミングプログラムとスタンドアロンプ​​ログラムの最大の違いは、プロセスの最初の数ステップではデータの計算がトリガーされないことですが、DAGグラフを描画するようなものです。論理DAGダイアグラム全体が描画されたら、Executeメソッドを使用して、ダイアグラム全体をクラスターに送信して実行できます。

イントロダクションのこの時点で、Flink DataStreamAPIとDAGグラフは相互にリンクされています。実際、Flinkタスクの特定の生成プロセスは、上記よりもはるかに複雑であり、段階的に変換および最適化する必要があります。次の図は、Flinkタスクの特定の生成プロセスを示しています。

image.png

DataStreamAPIで提供される変換操作

上記のサンプルコードに示されているように、各DataStreamオブジェクトは、対応するメソッドが呼び出されたときに新しい変換を生成します。これに対応して、最下層に新しい演算子が生成され、この演算子が既存の論理DAGグラフに追加されます。これは、既存のDAGグラフの最後のノードを指す線を追加することと同じです。これらのAPIはすべて、モバイル化時に新しいオブジェクトを生成します。その後、新しいオブジェクトでその変換メソッドを引き続き呼び出すことができます。このチェーン方式と同様に、DAG図は段階的に描画されます。

image.png

上記の説明には、いくつかの高階関数のアイデアが含まれています。DataStreamで変換を呼び出すたびに、パラメーターを渡す必要があります。つまり、変換によって、このデータに対して実行する操作が決まり、オペレーターに実際に渡される関数によって、変換操作の完了方法が決まります。

上の図では、左側にリストされているAPIに加えて、Flink DataStream APIには、ProcessFunctionとCoProcessFunctionの2つの非常に重要な関数があります。これらの2つの機能は、最下部の処理ロジックとしてユーザーに提供されます。上の図の左側にある青に含まれるすべての変換は、理論的には、基礎となるProcessFunctionとCoProcessFunctionを使用して完了することができます。

データパーティションについて

データ分割とは、従来のバッチ処理におけるデータのシャッフル操作を指します。トランプをデータと考えると、従来のバッチ処理でのシャッフル操作は、カードを並べ替えるプロセスと同じです。通常の状況では、カードを引く過程で、カードを順番に並べ、同じ番号を組み合わせます。これを行う最大の利点は、カードをプレイするときに必要なカードを一度に見つけることができることです。シャッフルは、従来のバッチ処理方法です。ストリーム処理のすべてのデータは動的に処理されるため、カードの処理、またはデータの処理、グループ化、またはパーティション化のプロセスもオンラインで実行されます。

image.png

たとえば、上の図の右側に示されているように、アップストリームにはオペレーターAの2つの処理インスタンスがあり、ダウンストリームにはオペレーターBの3つの処理インスタンスがあります。ここに示すストリーム処理は、データパーティショニングまたはデータルーティングと呼ばれるシャッフル操作と同等です。これは、Aがデータを処理した後に、ダウンストリームBのどの処理インスタンスに結果を送信するかを示すために使用されます。

Flinkで提供されるパーティション戦略

図Xは、Flinkが提供するパーティショニング戦略です。DataStreamがkeyByメソッドを呼び出した後、データ全体をKey値に従って分割できることに注意してください。しかし厳密に言えば、実際には、keyByは低レベルの物理パーティション化戦略ではなく、変換操作です。これは、APIの観点から、DataStreamをKeyedDataStreamタイプに変換し、2つでサポートされる操作も同じであるためです。違います。違います。

image.png

これらすべてのパーティショニング戦略の中で、Rescaleは少し理解しにくいかもしれません。リスケールには、アップストリームデータとダウンストリームデータの局所性が含まれます。これは、従来のリバランス、つまりラウンドポビンに似ています。違いは、Rescaleがネットワークを介したデータ送信を回避しようとすることです。

上記のすべてのパーティショニング戦略が適用できない場合は、PartitionCustomを呼び出してデータパーティションをカスタマイズすることもできます。これはカスタムユニキャストのみであることに注意してください。つまり、データごとに1つのインスタンスのみを指定でき、複数のコピーにコピーして複数のダウンストリームインスタンスに送信する方法はありません。 。

Flinkでサポートされているコネクタ

上記のように、図Xには2つの主要なノードがあります。外部システムに接続する必要があるノードAと、外部システムからFlinkの処理クラスターにデータを読み取るノードC、集約する必要があるシンクノードであるノードC、およびそして、この結果を外部システムに書き込みます。ここでの外部システムは、ファイルシステムまたはデータベースにすることができます。

image.png

Flinkには状態の概念もあるため、Flinkの計算ロジックにはデータ出力がありません。つまり、最終データを外部システムに書き込む必要はありません。中間計算の結果は、実際には状態を介して外部システムに公開される可能性があるため、特別なシンクは許可されません。ただし、すべてのFlinkアプリケーションにはソースが必要です。つまり、後続の処理を実行する前に、どこかからデータを読み込む必要があります。

ソースコネクタとシンクコネクタについて注意すべき点は次のとおりです。

  • Sourseの場合、継続的な監視をサポートしてデータの更新にアクセスし、対応する更新データをこのシステムに送信するかどうかについて、より懸念することがよくあります。たとえば、Flinkには、CSVファイルなどのファイルに対応するFileSystemコネクタがあります。CSVファイルコネクタを定義すると、特定のディレクトリ内のファイルの変更を継続的に監視し、更新されたファイルにアクセスするかどうかをパラメータで指定できます。
  • Sinkの場合、書き込まれる外部システムが書き込まれた結果の更新をサポートしているかどうかを気にすることがよくあります。たとえば、Kafkaにデータを書き込みたい場合、データの書き込みは通常Append-Onlyです。つまり、システムに書き込まれたレコードを変更することはできません(コミュニティはKafkaCompactionを使用してUpsertSinkを実装しています)。データベースに書き込まれると、通常、既存のデータを更新するための主キーの使用をサポートできます。

上記の2つの特性により、Flinkのコネクタが静的データに向けられているか動的データに向けられているかという重要なポイントが決まります。

上記のスクリーンショットは、Flinkバージョン1.11以降のドキュメントであり、コネクタにはFlinkバージョン1.11で主要なリファクタリングがあります。さらに、テーブル、SQL、およびAPIレベルのコネクターは、DataStreamレベルのコネクターよりも多くのタスクを引き受けます。たとえば、一部の述語をサポートするか、プロジェクション操作のプッシュダウンをサポートするかなどです。これらの機能は、データ処理の全体的なパフォーマンスを向上させるのに役立ちます。

3.Flinkのステータスと時刻

DataStream APIを深く理解したい場合は、ステータスと時間が習得する必要がある重要なポイントです。

すべての計算は、ステートレス計算とステートフル計算に簡単に分けることができます。ステートレス計算は比較的簡単です。加算演算子があると仮定します。データのセットが入るたびに、それらすべてが加算され、結果が出力されます。これは、純粋関数のビットです。純粋関数とは、各計算の結果が入力データにのみ関連し、前の計算または外部状態がそれに影響を与えないことを意味します。

image.png

ここでは、主にFlinkのステートフルコンピューティングについて説明します。例として、枝を選ぶ小さなゲームを取り上げます。私の意見では、このゲームは非常にうまく機能しており、多くのステータスを記録します。たとえば、数日間オンラインになっていないのに、内部のNPCに話しかけると、そのことがわかります。長い間オンラインになっていない。つまり、前回のオンライン時間を状態として記録し、NPCダイアログを生成するときにこの状態の影響を受けます。

この種のステートフル計算を実現するには、前の状態を記録してから、この状態を新しい計算に挿入する必要があります。2つの特定の実装方法があります。

  • 1つ目は、演算子を入力する前に状態データを抽出し、状態データと入力データをマージして、同時に演算子に入力して出力を取得することです。このメソッドは、SparkのStructureStreamingで使用されます。利点は、既存のステートレス演算子を再利用できることです。
  • 2つ目の方法は、Flinkの現在の方法であり、演算子自体がステートフルです。演算子は、新しいデータが発生するたびに計算するときに、新しい入力データと既存の状態が計算プロセスに与える影響も考慮します。結果を出力します。

コンピューティングエンジンも、上記のゲームのようにますますスマートになる必要があります。データの基礎となる法則を自動的に学習し、コンピューティングロジックを適応的に最適化して、高い処理パフォーマンスを維持できます。

Flinkの状態プリミティブ

Flinkの状態プリミティブには、コードを介してFlinkの状態を使用する方法が含まれます。基本的な考え方は、プログラミング時にネイティブ言語(JavaやScalaなど)によって提供されるデータコンテナーを破棄し、Flinkの状態プリミティブに置き換えることです。

状態サポートのためのより良いシステムとして、Flinkは内部で使用できる多くのオプションの状態プリミティブを提供します。大きな観点から、すべての状態プリミティブは、キー付き状態とオペレーター状態の2つのタイプに分けることができます。オペレーター状態のアプリケーションは比較的少ないため、ここでは紹介しません。キー付き状態に焦点を当てましょう。

image.png

キー付き状態、パーティション状態。パーティション状態の利点は、ロジックによって提供されるパーティションに応じて、既存の状態を異なるブロックに分割できることです。ブロック内の計算と状態はすべて相互に関連付けられており、異なるキー値間の計算と状態の読み取りと書き込みは分離されています。キー値ごとに、独自の計算ロジックと状態を管理するだけでよく、他のキー値に対応するロジックと状態を考慮する必要はありません。

キー付き状態は、さらに次の5つのカテゴリに分類できます。

  • より一般的に使用されるもの:ValueState、ListState、MapState
  • あまり一般的に使用されない:ReducingStateおよびAggregationState

キー付き状態はRichFuctionでのみ使用できます。通常の関数や従来の関数と比較すると、RichFuctionの最大の違いは、独自のライフサイクルがあることです。キー状態の使用方法は、次の4つのステップに分かれています。

  • 最初のステップは、RichFunctionでStateをインスタンス変数として宣言することです。
  • 2番目のステップは、RichFunctionに対応するopenメソッドでStateの初期化代入操作を実行することです。割り当て操作には2つのステップがあります。最初にStateDescriptorを作成し、作成中にStateの名前を指定する必要があります。次に、RichFuntionでgetRuntimeContext()。getState(...)を呼び出し、定義したStateDescriptorを渡します。状態を取得します。

注意:このストリーミングアプリケーションを初めて実行すると、取得した状態は空になります。状態を中間段階から再開すると、構成と以前に保存したデータに基づいて復元されます。

image.png

  • 3番目のステップでは、Stateオブジェクトを取得した後、RichFunctionで対応するStateを読み書きできます。ValueStateの場合は、そのValueメソッドを呼び出して、対応する値を取得できます。Flinkフレームワークは、すべての状態への同時アクセスを制御および制限するため、ユーザーは同時実行の問題を考慮する必要はありません。

フリンクタイム

時間もFlinkにとって非常に重要なポイントであり、時間と状態は補完的です。一般的に、Flinkエンジンで提供される時間には2つのタイプがあります。1つは処理時間、2つ目はイベント時間です。処理時間は実世界の時間を表し、イベント時間はデータに含まれる時間です。データ生成の過程で、タイムスタンプなどのフィールドが運ばれます。これは、データで運ばれるタイムスタンプを参照として使用する必要があるため、データは時間内に処理されるためです。

image.png

処理時間は、無秩序などの問題を考慮する必要がないため、処理が比較的簡単ですが、イベント時間は処理が比較的複雑です。また、Processing Timeは、マルチスレッドまたは分散システムの不確実性を考慮して、システムが使用されたときにシステムの時間を直接取得するため、各実行の結果が不確実になる可能性があります。逆に、イベント時間のタイムスタンプは次のように記述されます。データの各部分であるため、特定のデータが複数の処理のために再生される場合、運ばれるタイムスタンプは変更されません。処理ロジックが変更されない場合、最終結果は比較的確実です。

処理時間とイベント時間の違い。

image.png

上図のデータを例にとり、時間1〜7に従って整理します。マシン時間の場合、各マシンの時間は単調に増加します。この場合、Processing Timeで取得された時間は、最小から最大まで時間でソートされた完全なデータです。イベント時間については、遅延や配信などの理由により、データの到着順序が実際に生成される順序と異なる場合があり、データの順序がある程度ずれている場合があります。このとき、データに含まれるタイムスタンプを駆使して、データを大まかに分割する必要があります。たとえば、データは3つのグループに分割でき、最初のグループの最小時間は1、2番目のグループの最小時間は4、3番目のグループの最小時間は7です。この分割後、データはグループ間で小さいものから大きいものの順に並べられます。

システム全体が基本的にデータが入っているように見えるように、ある程度の障害を完全に解決するにはどうすればよいですか?1つの解決策は、透かしと呼ばれるメタデータをデータの中央に挿入することです。上の図の例では、最初の3つのデータが到着した後、3以下のデータがないと仮定して、データ全体に透かし3を挿入できます。システムは透かし3を検出すると認識します。 3以下のデータは存在せず、独自の処理ロジックの一部を安全かつ大胆に実行できます。

要約すると、処理時間は使用時に厳密に増加します。イベント時間には特定の障害があり、ウォーターマーク方式で軽減する必要があります。

APIの観点からは、タイムスタンプの割り当てや透かしの生成は比較的簡単です。2つの方法があります。

1つ目は、SourceFunctionで内部的に提供されているcollectWithTimestampメソッド呼び出して、タイムスタンプを含むデータを抽出することです。SourceFunctionでemitWatermarkメソッドを使用して透かしを生成し、それをデータストリームに挿入することもできます。

image.png

次に、SourceFunctionにない場合は、DateStream.assignTimestampsAndWatermarksメソッドを呼び出して、2種類のウォーターマークジェネレーターを同時に渡すことができます。

最初のタイプは通常の生成です。これは、システムがWatermar生成戦略を自動的に呼び出す頻度(リアルタイムを参照)など、環境で値を構成することと同じです。

2番目のタイプは、特別なレコードに基づいて生成されます。特別なデータが見つかった場合は、AssignWithPunctuatedWatermarksメソッドを使用してタイムスタンプと透かしを割り当てることができます。

注意:Flinkに組み込まれている一般的に使用されるAssigner、つまりWatermarkAssignerがいくつかあります。たとえば、固定データの場合、透かしとしてデータに対応するタイムスタンプから固定時間を減算します。タイムスタンプの割り当てと透かしの生成インターフェイスに関しては、以降のバージョンで特定の変更が行われる可能性があります。上記の2種類のジェネレーターは、新しいバージョンのFlinkに統合されていることに注意してください。

時間関連のAPI

Flinkは、ロジックを作成するときに時間関連のAPIを使用します。次の図は、イベント時間と処理時間に対応するAPIをまとめたものです。

image.png

アプリケーションロジックのインターフェイスサポートを介して、次の3つのことを実行できます。

  • まず、記録された時間を取得します。イベント時間は、context.getTimestampを調整したり、SQL演算子のデータフィールドから対応する時間を抽出したりできます。Processing Timeは、currentProcessingTimeを直接呼び出して呼び出しを完了することができます。その内部は、静的メソッドを直接呼び出してシステム時間を取得することによって返される値です。
  • 次に、透かしを取得します。実際、透かしの概念はイベント時間でのみ使用でき、処理時間では使用できません。しかし、処理時間では、何かを透かしと見なす必要がありますが、実際にはデータ時間そのものです。つまり、timerService.currentProcessingTimeメソッドを初めて呼び出した後に取得された値です。この値は、現在の記録時間だけでなく、現在のウォーターマーク値でもあります。これは、時間が常に順方向に流れるため、この値への最初の呼び出しの後、2番目の呼び出しが最初の値よりも悪くなることはありません。
  • 第三に、タイマーを登録します。タイマーの目的はクリーンアップすることです。たとえば、キャッシュは将来のある時点でクリーンアップする必要があります。クリーンアップ作業は将来のある時点で発生するはずなので、timerServicerEventTimeTimerまたはProcessingTimeTimerメソッドを呼び出してタイマーを登録し、メソッド全体のタイマーコールバックに処理ロジックを追加できます。対応するイベント時間または処理時間がタイマー設定時間を超えると、メソッドを呼び出してタイマーの破棄ロジックを記述します。

上記はApacheFlinkを使用したStreamProcessの紹介です。次のコンテンツでは、Flinkランタイムアーキテクチャの紹介に焦点を当てます。

アクティビティの推奨事項:

ApacheFlinkをベースにしたAlibabaCloudのエンタープライズレベル製品のリアルタイムコンピューティングFlinkバージョンをわずか99元で体験できます!イベントの詳細については、以下のリンクをクリックしてください:https//www.aliyun.com/product/bigdata/sc?utm_content = g_1000250506

おすすめ

転載: blog.csdn.net/weixin_43970890/article/details/115132149