ByteDance MapReduce-Spark のスムーズな移行の実践

要約: この記事は、CommunityOverCode Asia 2023 で ByteDance インフラストラクチャ エンジニアの Wei Zhongjia 氏が行った基調講演「ByteDance MapReduce - Spark Smooth Migration Practice」を編集したものです。
バイトビジネスの発展に伴い、同社は毎日約 100 万件以上の Spark ジョブをオンラインで実行していますが、対照的に毎日約 20,000 ~ 30,000 件の MapReduce タスクがオンラインに存在しており、ビッグデータの研究開発とユーザーの観点から見ると、また、MapReduce エンジンの運用、保守、使用における一連の問題も発生します。これに関連して、Bytedance Batch チームは、MapReduce タスクを Spark にスムーズに移行するためのソリューションを設計および実装しました。このソリューションにより、ユーザーは少数のパラメーターまたは環境変数を既存のジョブに追加するだけで、MapReduce から Spark へのスムーズな移行を完了できます。移行コストを大幅に削減し、優れたコストメリットを実現します。

背景の紹介

過去 1 年間で、Bytedance Spark ジョブの数は 100 万から 150 万に急激に増加し、日レベルのデータ Flink Batch は 200,000 から 250,000 に増加しましたが、MapReduce の使用は遅い状態にありました。上記の利用状況を踏まえると、当社が長年使用してきたバッチ処理フレームワークであるMapReduceもその歴史的使命を終え、間もなくオフラインとなります。
正式にオフライン化する前に、まずビジネス側と MapReduce タイプのジョブのタスク管理方法に関する統計を収集しました。
左側の円グラフはビジネス側の割合統計を示しています。最も大きな割合を占めるのは Hadoop ストリーミング ジョブで、全ジョブのほぼ 45% を占めます。2 番目に大きいのは Druid ジョブの 24%、3 番目はディストリビューションは 22% です。ここでDistcopyとHadoop Streamingを事業内容ごとに分けていないのは、これら2種類のジョブは全く同じコードを使用しており、アップグレードを進める際には同じジョブとしてみなせるためです。
右側の円グラフは保守方法の統計です。最も大きな割合を占めるのは「その他」で、60%を占めます。「その他」とは、ByteDance 内のどのプラットフォームでも管理されていないジョブを意味します。これは、MapReduce の特性ともよく一致しています。は長い歴史を持つフレームワークですが、多くの MapReduce ジョブが登場した当時はまだこれらのプラットフォームすら登場しておらず、その多くはユーザー自身が管理するコンテナや YARN クラスターに接続できる物理マシンから直接投入されていました。
 

MapReduce から Spark への移行を促進する必要がある理由

MapReduce をオフラインで駆動する理由は 3 つあります。
1 つ目の理由は、 MapReduce の動作モードでは、コンピューティング スケジューリング エンジンのスループットに対する要件が高すぎるためです MapReduce の動作モードでは、各タスクはコンテナに対応します。タスクの実行が終了すると、コンテナは解放されます。YARN のスループットは非常に高いため、この動作モードは YARN にとって問題ありません。しかし、社内業務を YARN から K8s クラスターに移行したとき、MapReduce ジョブが API サーバー アラームを頻繁にトリガーし、K8s クラスターの安定性に影響を与えることがわかりました。MapReduce タスクの完了後、多くの場合、100,000 を超えるリクエストを申請する必要がありますPOD; 同じスケールである一方で、Spark ジョブ内には別のスケジューリング層があるため、Spark ジョブには数千の POD のみが必要となる場合があります。Spark によって Executor として適用されたコンテナは、タスクの実行後に起動されません。代わりに、Sparkフレームワークは、継続的に使用できるように新しいタスクをスケジュールします。
2 番目の理由は、 MapReduce の Shuffle パフォーマンスが非常に低いこと です。内部で使用されているMapReduceはコミュニティベースのバージョン2.6です、Shuffleの実装が依存しているNettyフレームワークは約10年前のものです、現在のNettyと比べると大きなバージョン違いがあります、実際に使ってみるとそのパフォーマンスもわかりますパフォーマンスが低下し、物理マシン上で作成される接続が多すぎるため、物理マシンの安定性に影響します。
3 番目の理由は、開発エンジニアの観点から見ると、先ほど述べた K8 の変換や IPV6 への対応など、社内に多くの水平変換プロジェクトがあり、変換コストは実際には Spark と同じですが、変換の数は多くなります。 MapReduce タスク 現在、Spark の 1% にすぎませんが、 変換の ROI が 非常に低いだけでなく、MapReduce ジョブの履歴サーバーとシャッフル サービスを変換せずに維持するには多大な労力がかかります。そのため、MapReduceからSparkへの移行を推進する必要があります。

Spark のアップグレードの問題

まず、既存のタスクの割合が非常に低いです。現在、1 日あたり 10,000 件を超えるジョブしかありません。しかし、絶対値は依然として非常に大きく、多くのビジネス関係者が関与しています。それらの多くは、非常に長期間実行されるタスクです。この 5 年間、ユーザーに積極的にアップグレードを促すことは非常に困難でした。
第 2 に、実現可能性の観点から、ジョブの半分以上は、シェル、Python、さらには C++ プログラムを含む Hadoop ストリーミング ジョブです。Spark には Pipe オペレーターがありますが、ユーザーは既存のジョブを Spark Pipe に移行することができます。たくさんの仕事。
最後に、ユーザーが変換の開始を支援する場合、他の多くの問題に直面することになります。たとえば、メインのコンピューティング ロジックの移行に加えて、移行する必要がある周辺ツールが多数あります。特定の MapReduce パラメーターをどのように変換する必要があるかなどです。移行プロセス中? 同等の Spark パラメータ、および Hadoop ストリーミング ジョブ スクリプトが依存する環境変数インジェクションを Spark で同等に実装する方法 これらの問題の解決をユーザーに任せると、作業負荷が高くなるだけでなく、障害が発生します。率も高くなります。
 

プログラム全体

設計目標

以上が現状、動機、困難を整理しましたが、上記の情報を踏まえて、アップグレード前の目標は以下のとおりです。
  • これにより、ユーザーはコード レベルの変更を行うことができなくなり、ユーザーはまったく移動せずに、いくつかのジョブ パラメータを追加するだけでアップグレードを完了できるようになります。
  • Hadoop ストリーミング、Distcp、一般ユーザーが Java で作成したジョブなど、さまざまな種類のジョブをサポートする必要があります。このうち、Hadoop Streaming は MapReduce の古い API を使用しているのに対し、Distcp は新しい API を使用しているため、アップグレード プランではすべての MapReduce ジョブをサポートする必要があります。
 

ソリューションの解体

全体計画の解体は主に 4 つの部分に分かれています。
  • コンピューティング プロセスの適応には 、主に Mapreduce のコンピューティング ロジックと Spark のコンピューティング ロジックの調整が含まれます。
  • 構成の適応により、 ユーザーは Mapreduce パラメータを Spark パラメータに自動的に変換できます。
  • サブミット側の適応は、 ユーザーがサブミット コマンドを変更せずにアップグレードを完了できるように、真にスムーズな移行を実現するための重要なポイントです。
  • ユーザーがデータの正確性を検証できるようにツールと協力します
 

計算プロセスの適応

スクリーンショットは論文からのものです (https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf)。古典的な MapReduce プロセスは 5 つのステップに分かれています。
最初のステップは、入力データを処理してセグメント化することです。2 番目のステップは、ユーザーが提供した Map コードを実行することです。3 番目のステップは、Shuffle を実行することです。4 番目のステップは、ユーザーが提供した Reduce コードを実行することです。 5 番目のステップは、Reduce コードを次の形式に変換することです。コード処理の結果は、HDFS ファイル システムに書き込まれます。実際、MapReduce にはもう 1 つの非常に一般的な使用法があります。これは Map Only、つまり、以下の図の中央の 2 つのステップを省略した使用法です。
Spark に精通している学生は、MapReduce プロセス全体が Spark のサブセットとして、または特定の論理計算プロセスの Spark タスクとして理解できることを知っているはずです。図には、MapReduce 全体に完全に対応する疑似コードがリストされています。プロセス、プロセス。
最初のステップは、Hadoop RDD を作成することです。これは、Hadoop RDD 自体が Hadoop 自身の Inputformat コードに依存しているため、これは完全に適応可能です。2 番目のステップは、Spark の Map オペレーターを呼び出し、次に Spark の Map オペレーターでユーザーの Map 関数を呼び出します。 3 番目のステップでは、移行の汎用性を高めるために、RepartitionAndSortWithinPartitions メソッドを一律に使用します。このメソッドは、MapReduce の Shuffle プロセスに完全に対応します。4 番目のステップでは、Map オペレーターを使用して、ユーザーが提供した Reduce コードを実行します。5 番目のステップである SaveAsHadoopFile は、MapReduce の最後のストアド プロシージャに対応します。
上記の考え方は、ユーザーが MapReduce から Spark にアップグレードする際の従来の考え方ですが、一般的なアップグレード ソリューションを設計したい場合は、Spark 演算子を使用して MapReduce の計算プロセスをマッピングするだけでは十分ではありません。MapReduce と Spark フレームワークの分析を通じて、次のことがわかりました。
最下層は同じですが、どちらもリソース スケジューラ (YARN または K8s) に依存する必要があります。上位層の機能は同じか近いですが実装は全く異なります、例えばMapReduceではInputFormat、OutputFormatと呼ばれる名前ですが、SparkではHadoopRDD saveAsHadoopFileと呼ばれ、Mareduceではカウンターと呼ばれます。 Spark のアキュムレータに送信します。Shuffle、リソース スケジューリング、履歴、投機的実行などの他の機能はすべて揃っていますが、実装も異なるため、MapReduce の実装を Spark に置き換える必要があります。
設計目標で言及されている最上位層 - 実装層は完全に変更されていない必要があります。上に示したピンク色の層は Spark ベースで直接実行できないため、ユーザーのコードに適応する中間層を追加します。Spark コンピューティング インターフェイスは MapRunner を使用します。 Spark の Map オペレーターが Mapper と Reducer を実行できるように、Hadoop の Map メソッドと Reducer メソッドを適応させる ReduceRunner と、Counter アダプターを通じてユーザーの Counter 呼び出し動作を適応させます。ユーザーが Counter インターフェイスを通じて数値を増やすと、Spark へのアキュムレータ呼び出しに変換されます。設定に対応する Couf Translator もあります。つまり、タスクを送信すると、ユーザー用に Hadoop 設定が生成され、このトランスレータを使用して、対応する Spark パラメータに変換されます。これは、コンピューティング プロセス全体の適応です。この適応を通じて、全体のロジックを使用して、Spark ジョブを直接使用してユーザーのコードを実行できます。

構成の適応

設定は主に、変換が必要な設定、透過的に直接送信される設定、および無視する必要がある設定の 3 つのカテゴリに分類できます。
ジョブ リソース クラスのパラメーターなど、変換する必要がある最初のタイプの構成では、MapReduce と Spark の両方が、データを処理するために必要なコンテナーの種類をリソース フレームワークに伝える必要がありますが、使用するパラメーターは異なります。ジョブを送信するとき、パラメータを変換する必要があります。テーブルには環境変数、アップロードされたファイル、同時実行ジョブの数などもあります。これらのパラメータはすべて上記のように変換する必要があります。
2 番目のカテゴリは、直接透過的な送信を必要とする構成です。これは、Spark が Hadoop の多くのクラスに依存する必要があり、これらのクラスの多くも構成する必要があるためです。ここでは、変換中にプレフィックスを追加することで、 h adoop . Spark . 直接透過的に送信できます 。
3 番目のカテゴリは無視する必要がある構成です。これは、MapReduce では使用できるが、Spark では使用できない機能です。この場合、ユーザーマニュアルに記載し、この機能はサポートされていないことをユーザーに通知します。

提出側の適応

ユーザー エクスペリエンスのために、ユーザーが送信したスクリプトをまったく変更する必要がないことを願っています。ジョブは引き続き Hadoop を使用して送信され、Spark Submit に変更する必要はありません。したがって、実装では Hadoop にパッチを当てます。MapReduce ジョブがサブミットされると、サブミッター プログラムは特定のパラメーターまたは環境変数を識別します。識別されると、先ほど述べた構成変換機能が使用されて、この JobConf オブジェクトが構成を実行します。変換が完了すると、対応する Spark 送信コマンドが生成され、子プロセスを開始して Spark Submit コマンドを実行します。
同時に、MapReduce自体も常時ポーリングによりジョブの実行状況を検知する機能を備えています。子プロセスができたので、このモニターの動作は、RM または AM インターフェイスを呼び出して特定のアプリケーション ID のステータスを照会することから、子プロセスのステータスを照会することに変わりました。

正当性の検証

上記の 3 つの手順を完了すると、基本的にスムーズな移行が可能になりますが、オンラインに移行する前に、ユーザーにデュアル実行検証を行うことをお勧めします。ここで問題が発生しました。1 つの問題は、異なるデータ型に対して 2 つの比較方法を使用する必要があることです。ほとんどの出力形式では、CheckSum を直接比較できますが、少数の出力形式では、対応する入力リーダーを行に使用する必要があります。 - 行ごとの比較。出力形式によって生成されたファイルにはタイムスタンプやユーザーに関連する情報が含まれるため、実行ごとに生成されるファイルは異なる場合があります。この時点で比較したい場合は、対応するファイルを生成する必要があります。リーダー、1 行 ファイルを 1 行ずつ読み取り、1 行ずつ比較します。

問題と解決策

メモリ設定 - MapReduce メモリとSpark Executor メモリ間の 1 対 1 対応により、場合によってはOOMがトリガーされる可能性があります

前のセクションで説明したように、メモリの並列変換を行います。たとえば、4G メモリを使用する元の MapReduce タスクは、Spark に変換された後も 4G メモリを使用します。ただし、これにより、多くのジョブが OOM をトリガーします。主な理由は、MapReduce と Spark のメモリ モデルがまったく同じではないためです。MapReduce の Shuffle Spill のデフォルト キャッシュは 256M ですが、Spark ではメモリ マネージャーが実際にメモリを管理し、デフォルトの最大使用量は 60% です。総メモリ。同時に、MapReduce Shuffle で使用されるネットワーク プロトコルも Spark とは異なるため、同時実行性が高まり、より多くのメモリが使用されます。
この問題を解決するために、すべての並列移行タスクにパラメータ Spark.memory.fraction=0.4 を設定して、シャッフル スピル中のメモリを削減します。同時に、デフォルトでは、コアあたり 512M メモリを増加します。この戦略が完了した後は、オンライン、すべて スムーズな移行タスクの OOM 状況は解決されました。

同時実行設定 -ローカル ディレクトリを使用する場合、Hadoop ストリーミング ジョブによりディレクトリ名の競合が発生する可能性があります。

Spark ジョブは、コンテナー内で同時に実行される複数のタスクをサポートできます。一部の HadoopStreaming ジョブ スクリプトは、使用時にローカル ディレクトリに別のディレクトリを作成します。Spark を使用する場合、複数のタスクがこのディレクトリを同時に実行すると競合が発生します。MapReduce では、各タスクは新しいコンテナを使用するため、対応する競合は発生しません。
この問題には主に 2 つの解決策があります。
まず、アップグレードされた Spark ジョブの Executor 同時実行性を制御するパラメーターを追加します。デフォルトでは、シングルコア Executor としてユーザーに直接設定されます。これは、1 つのタスクを実行する 1 つの Executor に相当します。
次に、ユーザーがディレクトリ作成ロジックを変更することをお勧めします。ローカル ディレクトリを作成するときは、固定名のディレクトリを作成しないでください。代わりに、環境変数のタスク ID を読み取り、タスク ID を使用してローカル ディレクトリを作成してください。衝突。

クラスの読み込みの問題 -アップグレード後にユーザー ジョブを実行すると、クラスの読み込みの問題が発生する可能性があります

多くの Jar パッケージ タスクでは、アップグレード後にクラス読み込みの問題が発生します。この問題の根本的な原因は、Spark クラス ローダーがカスタム ClassLoader を使用していることです。カスタム ClassLoader は、クラス ロードを 2 つのカテゴリ (1 つはフレームワーク クラス ローダー、もう 1 つはユーザー クラス クラス ローダー) に分割します。Hadoop 自体は Spark Framework が依存するクラスであるため、フレームワークの ClassLoader を使用してロードされますが、同時にユーザーのタスク コードも Hadoop に依存しており、一部の依存関係はユーザー コードの ClassLoader によってロードされます。クラスローディングの問題など、様々な問題が発生します。
ほとんどの場合、この問題は、Spark.executor.userClassPathFirst=true パラメーターを設定して、Spark ジョブがデフォルトで最初にユーザーのクラスをロードするようにすることで回避できます。ただし、一部のユーザーはこのパラメータを設定した後に問題が発生することがありますが、この状況は手動で False に設定することで解決できます。

機能の調整の問題 - MapReduce 関数がSparkと完全に調整されていない

実際には、ユーザーは MapReduce の一部の関数が Spark で利用できないことを心配するでしょう。たとえば、MapReduce はパラメータを設定することで部分的なタスクの成功をサポートできます。タスクの失敗がこの比率を超えない限り、ジョブ全体は成功しますが、 Sparkにはそのような機能はありません。
解決策は、ほとんどの場合、ユーザーが自分で解決できることですが、少数のケースで、アップストリームに不良ファイルが存在することがユーザーにわかっている場合は、タスクの失敗を回避するために他の Spark パラメーターを提供します。

タスク試行 ID の問題 -一部のユーザーは環境変数のタスク試行 ID に依存しており、Sparkには対応する値がありません。

タスク試行 ID の問題は本質的に調整の問題です。一部のユーザー、特に HadoopStreaming ジョブは環境変数のタスク一時 ID に依存しており、この値には Spark に厳密に対応する概念がありません。タスク試行 ID は、MapReduce における特定のタスクの再試行回数です。Spark では、シャッフル失敗により、タスクの再試行ではなくステージの再試行が発生します。ステージの再試行中に、タスクのインデックスが変更されるため、再試行の回数は、特定のパーティション ID。
私たちは、Spark タスク コンテキストで提供される別のグローバルに増加する正の整数 (試行 ID) を使用して、さまざまなタスクを区別し、対応する値の問題を解決することで、この問題に対する近似的な解決策を実装しました。
 

所得

統計

上記のスムーズな移行ソリューションは、ユーザーの MapReduce から Spark へのアップグレードを促進し、全体的な効果は非常に良好です。完了したすべての移行における平均 MapReduce リソース アプリケーション量 (移行前) と Spark アプリケーション リソース量 (移行後) の比較です。過去 30 日間は次のとおりです。図からわかるように、1 日あたりに保存される CPU アプリケーションの数は 17,000 で、60% 増加しています。つまり、アップグレード後は、これらすべてのタスクを元のリソースの 40% で実行できるようになり、メモリ使用量は大幅に削減されます。 1日当たり約20,000GB削減でき、従来比約17%となります。

解釈

  • 上で紹介したように、これはスムーズな移行ソリューションです。ユーザーはタスクの書き換えに Spark を使用しません。Spark がメモリを有効活用できる静的エンジンであることは誰もが知っているため、スムーズな移行の利点はユーザーの手動移行よりも低いはずです。現在のアップグレードの利点は、実際には Spark オペレーター自体から得られるものではないためです。実際、ユーザーの処理ロジックは完全に変更されておらず、実行コードは依然として MapReduce コードです。Hadoop ストリーミング ジョブの場合、実行コードは依然としてスクリプトです。ユーザーによって書かれたものであるため、この利点は Spark オペレーター自体から得られるものではありません。
  • 利点は主に Shuffle 段階から得られます。つまり、ネットワーク フレームワークから実装の詳細に至るまで、Spark Shuffle は MapReduce Shuffle よりも優れています。また、Spark Shuffle のパフォーマンスを向上させるために、Spark Shuffle の徹底的なカスタム最適化も行っています。ご興味がございましたら、関連記事の推奨事項をご覧ください。シャッフルの最適化により、ジョブの実行時間が短縮されます。一部のジョブの平均マップ時間は 2 分、削減時間は 5 分で、シャッフル時間は 10 分を超えることがよくあります。Spark をアップグレードすると、シャッフル時間を直接 0 に減らすことができます。Spark の Shuffle は非同期 Shuffle であるため、データはメイン スレッドで計算され、他のスレッドで読み取られるため、中間ブロックの時間がミリ秒に短縮されます。
  • 収益は主に Shuffle から得られるため、マップのみのジョブのパフォーマンスの向上は明らかではありません。アップグレードが完了したすべての Map Only ジョブと Distcp ジョブでは、リソース アプリケーションのボリュームはあまり変化せず、90% ~ 110% の間で推移しています。
  • CPU の収益がメモリの収益より大幅に高い理由は、CPU は完全に並列マイグレーションですが、メモリは異なり、Map と Reduce は通常、メモリの最大値を取得するため、メモリの無駄が発生します。また、ボトルネック移行によるOOM問題を回避するため、各Coreに512Mメモリを追加したため、全体のメモリ使用量は増加しましたが、Shuffleステージの効果によりジョブの実行時間が短縮されたため、全体的なメモリ収益は依然としてプラスです。
 
Broadcom は、既存の VMware パートナー プログラム Deepin-IDE バージョン アップデートの終了を発表し 、新しい外観に なりました。WAVE SUMMIT は第 10 回を迎えます。Wen Xinyiyan が最新の情報を公開します。 周宏儀: 紅夢ネイティブは間違いなく成功するだろう GTA 5 の完全なソースコードが公開された ライナス: クリスマスイブにはコードを読まないつもりだ Java ツールセットの新バージョン Hutool-5.8.24をリリースする来年。一緒 にフリオンについて文句を言いましょう。商業探査: ボートは通過しました。万中山、v4.9.1.15 Apple、オープンソースのマルチモーダル大規模言語モデルをリリース フェレット ヤクルト会社、95G データが漏洩したことを確認
{{名前}}
{{名前}}

おすすめ

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