Flink に基づいてリアルタイム データ レイクを構築する実践

この記事は、CommunityOverCode Asia 2023 データ レイク スペシャル セッションでの、Volcano Engine クラウド ネイティブ コンピューティングの研究開発エンジニアである Wang Zheng と Min Zhongyuan の「Flink に基づくリアルタイム データ レイクの構築の実践」に関する基調講演を編集したものです。
 
リアルタイム データ レイクは、最新のデータ アーキテクチャのコア コンポーネントです。データ レイク テクノロジの発展に伴い、ユーザーはデータ レイク テクノロジに対するより高い要件を求めています。データは複数のデータ ソースからインポートする必要があり、データ レイクとデータ ソースはリアルタイムに維持される必要があります。一貫性があり、変更が発生したときに適時に同期できます。また、高性能のクエリ、数秒でのデータ返しなどが必要です。そこで、レイクおよび OLAP クエリの送受信に Flink を使用することにしました。フリンク バッチフロー統合 アーキテクチャ、 E 正確に O そう 保証 と完全な群集生態学が提供するもの 多くのCコネクタが以前のニーズを満たすことができます。フリンク OLAPクエリ にも適しています。これについては、後で詳しく説明します。この記事 。

全体構造

Flink に基づいてリアルタイム データ レイクを構築する全体的なアーキテクチャでは、K8s が最下層のコンテナ オーケストレーションおよび管理プラットフォームとして使用されます。ストレージ層は HDFS または S3 をサポートします。 Iceberg がテーブル形式として選択されたのは、その優れたファイル構成構造とエコロジーのためです。コンピューティング レイヤーは、Flink を使用してレイクに出入りします。Flink SQL は、レイクに出入りする最も一般的に使用される方法です。同時に、いくつかの高レベル関数は、Flink Datastream API を使用して開発されています。 lake は Flink アプリケーション モードを使用して K8 上で実行します。次に、セッション モードで Flink SQL Gateway および Flink Cluster を介して OLAP クエリが実行され、JDBC および REST API インターフェイスの戻り結果が提供されます。もちろん、カタログを使用してメタデータを管理する必要もあります。これには、Iceberg のメタデータを参照するだけでなく、他のサードパーティ データ ソースからのメタデータも含まれ、その後のデータ メンテナンスにはスケジュールされたタスクが使用されます。

湖の実践へのデータ入力

データがレイクに入力されると、Flink は左側のデータ ソースからデータを取得し、それをストリームまたはバッチ方式で Iceberg に書き込みます。 Iceberg 自体もデータ メンテナンスのためのいくつかのアクションを提供するため、テーブルごとに、データの有効期限、スナップショットの有効期限、孤立したファイルのクリーニング、小さなファイルのマージなどのタスクのスケジュールが設定されます。
目標 スキーマ は固定されています。宛先テーブルには宛先テーブル へのテーブルもあります。通常、データのインポートには Flink SQL が使用されます。とエクスポートします。、書き込むことができます 一時テーブル では、メタデータをカタログに保存し、カタログ テーブルを使用してデータをインポートすることもできます。しかし、より複雑なお客様のニーズに応えるため、実際にはデータベース全体の同期+テーブル自動作成の機能を実現できるDatastream APIをベースとしたCDCスキーマ自動変更を開発しました。

フリンク SQL

Iceberg コミュニティは、基本的な書き込み機能と読み取り機能をサポートしています。 Flink 1.17 では、行レベルの更新および削除機能 (FLIP-282) が導入されました。これに基づいて、RowLevelModificationScanContext インターフェイスを介して Iceberg の行レベルの更新を実装するバッチ更新および削除操作を追加しました。実際には、トランザクション開始時のスナップショット ID とバッチ更新および削除のトランザクション性を確保するための UPDATE/DELETE のフィルター条件という 2 つの情報がコンテキストに記録されます。

スキーマの進化

スキーマの進化はストリーム処理における一般的な問題であり、ストリーム ジョブの処理中に宛先のスキーマを動的に変更することでデータの正しい書き込みを保証します。 Iceberg 自体はスキーマ変更を適切にサポートしています。 Iceberg のストレージ アーキテクチャでは、カタログにはスキーマは保存されず、最新のメタデータ ファイルの場所のみが保存されます。メタデータ ファイルには、すべてのスキーマ ID とスキーマ情報のマッピング、および最新のスキーマ ID (Current-Schema-id) が保存されます。以下の各マニフェストにはスキーマ ID が記録されます。これは、マニフェスト内の Parquet ファイルが対応するスキーマを使用することを意味します。
Iceberg でスキーマが変更された場合、メタデータ ファイルは新しいスキーマを記録し、Current-Schema-id が新しいスキーマを指すようにします。後続の書き込みジョブは、新しいスキーマに従って、新しい Parquet データ ファイルと対応するマニフェスト ファイルを生成します。読み込み時は最新のSchema-idに基づいて読み込まれますが、下部に異なるSchemaのマニフェストファイルがあった場合でも、新しいSchema情報を使用して読み込まれます。
現在、Iceberg が提供する Flinksink はスキーマの変更をサポートしていません。Iceberg のデフォルトの Flinksink は、書き込む必要がある Parquet ファイルごとに Streamwrtier を作成し、この Streamwriter のスキーマは修正されます。そうでない場合は、Parquet ファイルの書き込み時にエラーが報告されます。上の例では、元のスキーマは id、name、age です。スキーマが一致する場合、書き込み時にエラーが報告されないため、行 1 を書き込むことができます。行 2 を書き込む場合、長さが一致しないため、エラーが報告されます: インデックスが範囲外です。 ; 行 3 を書き込むとき、データ型の不一致により、エラーが報告されます: クラス キャスト例外; 行 4 を書き込むとき、型と長さは一致しますが、スキーマの意味が異なります。そして最終的にはダーティ データが結果ファイルに書き込まれます。
スキーマの変更に関して解決すべき主な問題は 2 つあります。 1) 各行がどのスキーマに対応するかをどのように知るか? 2) 1 つのジョブで複数のスキーマ データを書き込むにはどうすればよいですか?
最初の質問では、各レコードのスキーマ情報を含めるように Flink CDC コネクタを設定できます。したがって、行とそれに対応するスキーマ情報 (図の紫色の部分) を含むレコードを出力する逆シリアル化メソッドを実装する必要があり、これにより最初の問題が解決されます。
2 番目の質問については、複数のスキーマの混合書き込みをサポートするには、スキーマごとに異なるストリームライターを作成する必要があり、各ストリームライターがスキーマに対応します。これにより、新しい FlinkSchemaEvolvingSink が Iceberg シンク コネクタに追加されます 受信データが現在のスキーマと一致するかどうかを判断します。一致しない場合は、新しいスキーマ情報を Iceberg にコミットし、スキーマ ID を返します。次に、新しいスキーマを押してデータを書き込み、データをコミットします。これは、上図の青線の説明です。スキーマが生成されている場合は、古いスキーマ ID が返されます。 FlinkSchemaEvolvingSinkはスキーマIDをキーとしたストリームライターマップを保持しており、スキーマを渡す際にそのスキーマのライターが含まれているかどうかを判定し、含まれていない場合はライターを作成することで複数種類の書き込みが可能となります。スキーマ情報。

データベース全体の同期とテーブルの自動作成

Flink タスク Jobgraph が生成される前に、カタログ モジュールが必要です ソース テーブルの情報を読み取り 、Iceberg 側で同期します。 に対応する宛先テーブルを作成または変更し、同時に Jobgraph で作成します。 対応するテーブルの Sインク 情報を追加します
Flink ジョブの実行中、各 Binlog レコードは逆シリアル化パーサーを通じてレコードを生成します。このレコードには、Tableid と Row (図の紫色の部分のレコード) の 2 つの部分が含まれます。次に、このレコードを分割し、テーブル ID に従って行を分割し、Keyby Partition 操作後にダウンストリーム テーブルに書き込みます。
プロセス全体は主に次の 4 つの部分で構成されます。
  1. デシリアライザーは、Event イベントとデータを解析します。転送プロセス中にクラス キャスト例外が発生しないようにするには、データ型をソース スキーマと同じままにする必要があります。これには、Flink CDC のテスト ケースを使用して各型をテストし、比較する必要があります。
  2. カタログ モジュールは主にテーブルの自動作成とテーブルの内容の更新を担当し、デシリアライザーとの一貫した型変換方法を維持する必要があります。
  3. Table Spilled は、Source 再利用の機能を実現し、テーブルごとに Sideoutput Tag を作成し、下流に出力することができます。
  4. Iceberg Sink は各パーティションに影響を与えるため 対応する F anout W riter は多くのメモリを消費します。したがって、OOM の数を減らすために、テーブルの Partition フィールドに対して Keyby 操作を実行する必要があります。 Iceberg には暗黙的パーティショニングの機能があるため、暗黙的パーティショニングのフィールドを変換してから Keyby 操作を実行する必要があります。

データクエリの実践

フリンクを選ぶ理由

  • アーキテクチャ的には、Flink は JDBC ドライバー、SQL ゲートウェイ、およびセッション モードをサポートしています。 Flink セッション クラスターは典型的な MPP (超並列処理) アーキテクチャであり、各クエリで新しいリソースを適用する必要はありません。ユーザーは、JDBC ドライバーを介して SELECT ステートメントを簡単に送信し、数秒または 1 秒未満で結果を取得できます。
  • 強力なバッチ処理機能。 Flink OLAP では、多くのバッチ操作と最適化を行うことができます。同時に、OLAP には多数のクエリもあり、Flink は他の OLAP エンジンのような外部バッチ処理エンジンを導入することなく、Flink のバッチ処理機能に基づいてクエリをサポートできます。
  • Flink は、OLAP ユーザーの対話型ニーズを満たすために、QUERY/INSERT/UPDATE などの標準 SQL 構文をサポートしています。
  • 強力なコネクタ エコシステム。 Flink は入出力用の包括的なインターフェイスを定義し、データベースやデータ レイク ウェアハウスなどの多くの組み込みコネクタを実装します。ユーザーは、これらのインターフェイスに基づいてカスタマイズされたコネクタを簡単に実装することもできます。

OLAP アーキテクチャ

Flink OLAP の全体的なアーキテクチャは、Flink SQL ゲートウェイと Flink セッション クラスターの 2 つの部分に分かれています。まず、ユーザーはクライアントを使用して、Rest インターフェイス経由でクエリを送信します。最初にゲートウェイの SQL 解析および最適化プロセスを経て、ジョブの実行計画を生成し、それを Flink セッション クラスター上のジョブマネージャーに送信します。効率的な Socket インターフェイスを介して、対応する TaskManager に接続され、実行後、結果がクライアントに返されます。ジョブマネージャー上のディスパッチャーは対応するジョブマスターを作成し、ジョブマスターはクラスター内のタスクマネージャーに従って特定のスケジュールルールに従ってタスクをデプロイします。

最適化施策

クエリ生成の最適化

  • Plan 缓存
最初の最適化ポイントはプランのキャッシュです。 OLAP シナリオでは、クエリには 2 つの典型的な特徴があります。1 つはストリーミングとは異なり、ビジネスでは繰り返しのクエリが多いことです。2 つ目は、クエリに時間がかかる要件があることです。 サブ 秒。分析の結果、計画フェーズに数十から数百ミリ秒かかり、比較的高い割合を占めていることがわかりました。したがって、プラン キャッシュをサポートすることにより、クエリのプラン結果変換がキャッシュされ、同じクエリに対するプランが繰り返されるという問題が回避されます。
さらに、メタ情報へのアクセスを高速化するためのカタログ キャッシュや、TPC-DS プランの時間を約 10% 削減する ExecNode の並列変換もサポートされています。
  • 算子 下推
2 番目の最適化は演算子プッシュダウンです。ストレージと計算の分離アーキテクチャの下では、演算子プッシュダウンは非常に重要なタイプの最適化です。その中心的なアイデアは、一部の演算子を計算のためにストレージ層にプッシュダウンすることで演算子の数を大幅に削減することです。スキャン データ量により外部 IO が削減され、Flink エンジンが処理する必要があるデータ量も削減されるため、クエリのパフォーマンスが大幅に向上します。
Byte の内部実務では、Query のほとんどが TopN データを使用する典型的な業務があるため、TopN のプッシュダウンをサポートしています。図からわかるように、Local の SortLimit 演算子、つまり Local TopN 演算子は Scan ノードにプッシュダウンされ、TopN 計算は最終的にストレージ層で実行されるため、ストレージから読み取られるデータ量が大幅に削減されます。最適化の効果は非常に明らかで、Scan ノードがストレージから読み取るデータ量は 99.9% 削減され、ビジネス クエリのレイテンシは約 90.4% 削減されました。
さらに、集計プッシュダウン、フィルター プッシュダウン、制限プッシュダウンなど、より多くの演算子プッシュダウンもサポートしています。

クエリ実行の最適化

  • クラスローダー 使用
クラスローダーの再利用では、まず、OLAP でクラスローダーが頻繁に作成されることによって過剰な CPU 使用率が発生する問題を分析しましょう。回線上で JM/TM の CPU 使用率が高いことがわかりました。フレーム グラフ分析により、JVM の Dictionary::find メソッドが CPU の 70% 以上を占有しており、さらに JVM ソース コードを分析すると、JVM がクラスをロードした後、クラスからの検索を高速化するために、クラス名をクラスローダーに設定すると、SystemDictionary. ハッシュ テーブルが維持されます (キーはクラス名、値はクラスローダー インスタンス)。クラスローダーの数が非常に多い場合、たとえば 20,000 を超えるクラスローダーがオンラインに表示される場合、ハッシュ テーブル内で多数の競合が発生し、検索プロセスが非常に遅くなり、全体の CPU のほとんどが使用されます。 JM はこのステップで消費されます。
配置を通じて、これらのクラスローダーはすべて、ユーザーの Jar パッケージを動的にロードするために使用される UserCodeClassloader であることがわかりました。各ジョブは、新しい UserCodeClassloader を作成します。下の図からわかるように、新しいジョブの JobMaster と、新しいジョブの Task TM 上のジョブ 新しい UserCodeClassloader が作成され、その結果、JM と TM 上のクラスローダーが多すぎます。さらに、クラスローダーが多すぎると JVM メタスペース領域が不足し、メタスペース フル GC が頻繁にトリガーされます。
したがって、クラスローダーの再利用を最適化しました。これは主に 2 つのステップに分かれています。まず、Jar への依存方法を最適化しました。OLAP シナリオで依存するサードパーティの Jar パッケージは比較的固定されているため、クラスパスの直下に配置できます。 JM と TM によって開始されるため、ジョブごとに個別の Jar パッケージを送信する必要はありません。次に、ジョブごとに、JobMaster およびタスクの初期化中にシステム クラスローダーが直接再利用されます。クラスローダーの再利用後、JM の Dictionary::find が占める CPU 使用率は 76% から 1% に低下し、同時に Metaspace Full GC の頻度も大幅に減少しました。
  • CodeGen 缓存优化
この最適化の前提は、OLAP での Codegen ソース コードのコンパイルが TM CPU を占有しすぎるという問題を発見したことです。現在の Codegen キャッシュ プロセスでは、Flink SQL の多数の演算子が Codegen を使用して計算ロジックを生成します。 Codegen オペレーター。クラス。ここで、Code は Codegen によって生成された Java ソース コードです。オペレーターが初期化されるとき、Java ソース コードはコンパイルされ、クラスにロードされる必要があります。コンパイルの繰り返しを避けるために、現在、クラス名をタスクで使用されるクラスローダーにマップし、次にコンパイルされたクラスにマップするキャッシュ メカニズムが存在します。
ただし、現在のキャッシュ メカニズムには 2 つの問題があります。まず、現在のメカニズムは、同じジョブ内の同じタスクの異なる同時再利用を実装するだけですが、同じクエリの複数の実行に対してコンパイルが繰り返されます。 Codegen が Java ソース コードを生成するときに名前の競合を避けるため、コードのクラス名と変数名のサフィックスにはプロセス レベルの自動インクリメント ID が使用され、その結果、同じクエリが複数回実行され、クラス名とコードの内容が変更されます。したがって、キャッシュにヒットできません。もう 1 つの問題は、クラスがコンパイルおよびロードされるたびに、新しい ByteArrayClassloader が作成されることです。Classloader を頻繁に作成すると、深刻なメタスペースの断片化が発生し、メタスペースのフル GC がトリガーされ、サービス ジッターが発生します。
ジョブ間コードの繰り返しコンパイルを回避し、ジョブ間クラス共有を実現するには、キャッシュ ロジックを最適化し、同じソース コードのコンパイル済みクラスへのマッピングを実現する必要があります。ここには 2 つの問題があります。
  1. 1 つ目は、同じロジックを持つ演算子が同じコードを生成するようにする方法です。
  2. 同じコードを一意に識別するキャッシュ キーを設計する方法。
1 つ目の困難については、Codegen コードを生成するときに、クラス名と変数名の自動インクリメント ID をグローバル粒度からローカル コンテキスト粒度に置き換えて、同じロジックを持つ演算子が同じコードを生成できるようにします。 2 番目の困難については、クラスローダーのハッシュ値 + クラス名 + コードの長さ + 同じコードを一意に識別するキャッシュ キーとしてのコードの md5 値に基づいて 4 つのタプルを設計しました。
Codegen キャッシュ最適化の効果は非常に明白で、TM 側のコード コンパイルの CPU 使用率は以前の 46% から約 0.3% に最適化され、クエリの E2E レイテンシーは約 29.2% 削減され、同時にメタスペースのフル GC 時間も短縮されました。も71.5%削減されています。

マテリアライズドビュー

  1. まず、ユーザーは Flink SQL を通じてマテリアライズド ビューを作成するリクエストをプラットフォームに送信します。
  2. プラットフォームは、Iceberg マテリアライズド ビューを作成し、Flink ジョブを開始してマテリアライズド ビューを更新し、このジョブをホストして確実に実行が継続されるようにする責任を負います。
  3. Flink リフレッシュ ジョブは、ソース テーブルから増分データをストリーミングし続け、増分計算を実行して増分結果を取得し、それをマテリアライズド ビューにストリーミングします。
  4. エンド ユーザーは、マテリアライズド ビューを確認することで、本来は完全な計算が必要だった結果を直接取得できます。
上記はマテリアライズド ビューを実装するための主なプロセスです。現在、Iceberg マテリアライズド ビューは単なる通常の Iceberg テーブルです。将来的には、データの鮮度の判断をサポートするために、より完全なメタデータが Iceberg レベルで記録される予定です。既存のマテリアライズド ビューに基づいて、ユーザー クエリを自動的に書き換えて最適化します。定期的なデータ メンテナンスには、期限切れデータのクリーニング、期限切れのスナップショットのクリーニング、孤立したファイルのクリーニング、データ/メタデータの小さなファイルのマージなどが含まれます。

概要と展望

フォローアップ作業の焦点は主に、マテリアライズド ビューの自動作成、マテリアライズド ビューのクエリの書き換え、データ メンテナンス タスクのパラメータの自動チューニング (実行頻度、マージされたファイル サイズなど)、およびホットおよび関連作業に焦点を当てます。コールド データ階層化/データ キャッシュ 展開します。
 
SenseTime 創設者、Tang Xiaoou 氏が 55 歳で死去 2023 年、PHP は停滞 Wi-Fi 7 が完全に利用可能になる2024 年初頭にデビュー、Wi-Fi 6 の 5 倍高速 Hongmeng システムが独立しつつあり、多くの大学が「Hongmeng クラス」を設立 Zhihui Jun の新興企業が借り換え、金額は 6 億元を超え、事前評価額は 35 億元 Quark Browser PC 版が内部テストを開始 AI コード アシスタントは人気があり、プログラミング言語のランキングはすべてです できることは何もありません Mate 60 Pro の 5G モデムと無線周波数技術ははるかに先を行っています MariaDB が SkySQL を分割し、確立されました独立した企業として<​​/span> Xiaomi、Yu Chengdong 氏の Huawei からの「キールピボット」盗作声明に対応
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/5941630/blog/10322189