Spark カーネル分析 - ノード起動 4 (6)

1. マスターノードの起動

Endpoint の具体例として Master が挙げられますが、以下では OnStart コマンド後の Master の起動と関連作業を紹介します。

1.1 スクリプトの概要

以下に例を示します。

/opt/jdk1.7.0_79/bin/java
-cp /opt/spark-2.1.0/conf/:/opt/spark-2.1.0/jars/*:/opt/hadoop-2.6.4/etc/hadoop/
-Xmx1g
-XX:MaxPermSize=256m
org.apache.spark.deploy.master.Master
--host zqh
--port 7077

1.2 起動プロセス

マスターの起動プロセスは次のとおりです:
ここに画像の説明を挿入します
1) SparkConf: キーがsparkで始まるシステム プロパティを読み込みます (Utils.getSystemProperties)
2) MasterArguments:
a) マスターの起動パラメーターを解析します (–ip -i --host -h --port) -p --webui-port --properties-file)
b) spark. で始まる設定を –properties-file (設定なしのデフォルトは conf/spark-defaults.conf) を SparkConf に保存します
3) NettyRpcEnv の内部処理は次のとおりですRpcEndpoint 処理の統合。ここでは詳細には触れません。
4) BoundPortsResponse は、rpcEndpointPort、webUIPort、restPort の実ポートを返します。
5) 最後のデーモンは常に存在し、終了メッセージ awaitTermination を待ちます。

4.3OnStartリスニングイベント

マスターが開始された後の非同期実行作業は次のようになります。
ここに画像の説明を挿入します
1) [dispatcher-event-loop] スレッドは OnStart 命令をスキャンし、関連する MasterWebUI (デフォルト ポート 8080) を開始し、それに応じて ResetServer (デフォルト ポート 6066) のインストールを選択します。 2)さらに
、新しい [master-forward-message-thread] スレッドは、ワーカーのハートビートがタイムアウトするかどうかを定期的にチェックします
3) ワーカーのハートビート検出がタイムアウトした場合、発行されたすべてのタスクに属するドライバーに ExecutorUpdated を送信しますWorker の下にあると同時に、Driver を再起動します

4.4RpcMessage処理(receiveAndReply)

ここに画像の説明を挿入します
OneWayMessage処理(受信)
ここに画像の説明を挿入します
ここに画像の説明を挿入します

4.5RpcMessage/OneWayMessage のマスターの処理ロジック

この部分はマスター全体の理解にはあまり役に立たず、比較的抽象的なので、最初に以降の内容を読んでから、この部分を後で読むことを検討することもできますし、読めなくても構いません。

ここに画像の説明を挿入します

2. 作業ノードを開始します

Worker は Endpoint の特定のインスタンスです。以下では、Worker の起動後の追加作業と OnStart 命令を紹介します。

2.1 スクリプトの概要

/opt/jdk1.7.0_79/bin/java
-cp /opt/spark-2.1.0/conf/:/opt/spark-2.1.0/jars/*:/opt/hadoop-2.6.4/etc/hadoop/
-Xmx1g
-XX:MaxPermSize=256m
org.apache.spark.deploy.worker.Worker
--webui-port 8081
spark://master01:7077

2.2 起動プロセス

 Worker的启动流程如下:

ここに画像の説明を挿入します
1) SparkConf: キーがsparkで始まるシステム プロパティをロードします (Utils.getSystemProperties)
2) WorkerArguments:
a) マスター起動パラメータを解析します (–ip -i --host -h --port -p --cores -c - - Memory -m --work-dir --webui-port --properties-file)
b) spark.in –properties-file (未設定の場合はデフォルトで conf/spark-defaults.conf) で始まる設定を SparkConf
cに保存します。 ) 設定がない場合、コアはデフォルトでサーバーの CPU コア数になります。
d) 設定がない場合、メモリはデフォルトでサーバー メモリから 1G を引いた値になります。1G 未満の場合は 1G になります。
e) webUiPort のデフォルトは 8081 です
。 NettyRpcEnv の内部処理は RpcEndpoint に準じて一律に処理されるため、ここでは詳しく説明しません
4) 最終デーモン プロセスは常に存在し、終了メッセージ awaitTermination を待ちます。

2.3OnStartリスニングイベント

Worker の起動が完了した後の非同期実行作業は次のようになります。
ここに画像の説明を挿入します
1) [dispatcher-event-loop] スレッドは OnStart コマンドをスキャンし、関連する WorkerWebUI (デフォルト ポート 8081) を開始します。
2) Worker は RegisterWorker コマンドを開始して、マスター
3) 別の [master-forward -message-thread] を開始します スレッドは定期的に ReregisterWithMaster タスクを実行します。登録が成功した場合 (RegisteredWorker)、スキップされます。そうでない場合は、RegisterWorker 命令がマスターに対して再度開始されます。エラーの最大数を超えています (デフォルトは 16 回)
4) マスターが登録できる場合、対応する WorkerInfo オブジェクトは永続化されます。完了後、RegisteredWorker 命令がワーカーに発行されます。マスターが登録されている場合、マスターは、対応する WorkerInfo オブジェクトを保持します。スタンバイ状態では、MasterInStandby 命令が Worker に発行されます
。5) Worker が RegisteredWorker を受け入れた後、[master-forward-message-thread] スレッドを送信して SendHeartbeat タスクを定期的に実行します。完了後、WorkerlatestState を開始します。 6)ワーカーは
ハートビート検出を送信し、マスターに対応する WorkerInfo オブジェクトの更新をトリガーします。マスターが例外を検出すると、ワーカーに対して ReconnectWorker コマンドを開始し、ワーカーは ReregisterWithMaster を実行します。また仕事。

2.4RpcMessage処理(receiveAndReply)

ここに画像の説明を挿入します

2.5OneWayMessage処理(受信)

ここに画像の説明を挿入します
ここに画像の説明を挿入します

3. クライアント起動処理

Client は Endpoint の特定のインスタンスです。以下では、Client の起動と OnStart 命令後の追加作業を紹介します。

3.1 スクリプトの概要

以下に例を示します。

/opt/jdk1.7.0_79/bin/java
-cp /opt/spark-2.1.0/conf/:/opt/spark-2.1.0/jars/*:/opt/hadoop-2.6.4/etc/hadoop/
-Xmx1g
-XX:MaxPermSize=256m
org.apache.spark.deploy.SparkSubmit
--master spark://zqh:7077
--class org.apache.spark.examples.SparkPi
../examples/jars/spark-examples_2.11-2.1.0.jar 10

3.2SparkSubmit起動処理

   SparkSubmit的启动流程如下:

ここに画像の説明を挿入します
1)SparkSubmitArguments:
a) クライアント起動のパラメータを解析します。
i.–name --master --class --deploy-mode
ii.–num-executors --executor-cores --total-executor-cores --executor-memory
iii .–driver-memory --driver-cores --driver-class-path --driver-java-options --driver-library-path
iv.–properties-file
v.–kill --status --supervise -- queue
vi.–files --py-files
vii.–archives --jars --packages --exclude-packages --repositories
viii.–conf (分析は Map:sparkProperties に保存されます)
ix.–proxy-user --プリンシパル - -keytab --help --verbose --version --usage-error
b) –properties-file (設定されていない場合はデフォルトで conf/spark-defaults.conf) ファイル設定項目 (-conf にない設定) をマージします。スパークプロパティ
c) スパークで始まらない構成アイテムをスパークプロパティから削除します
d) スパークプロパティから空の起動パラメータを持つ構成アイテムをマージします
e) 各必須パラメータにアクション (SUBMIT、KILL、REQUEST_STATUS) に応じた値があるかどうかを確認します
2) ケースサブミット:
a ) Get childMainClass
i.[–deploy-mode] = clent (デフォルト): ユーザー タスク起動クラス mainClass (–class)
ii.[–deploy-mode] = クラスター & [–master] = スパーク:* & useRest :org.apache.spark.deploy.rest.RestSubmissionClient
iii.[–deploy-mode] = クラスター & [–master] = スパーク:* & !useRest : org.apache.spark.deploy.Client
iv.[–deploy- mode] = クラスター & [–master] = 糸: org.apache.spark.deploy.yarn.Client
v. [–deploy-mode] = クラスター & [–master] = mesos:*: org.apache.spark.deploy b) childArgs
の取得 (子ランタイムに対応するコマンド ライン アセンブリ パラメーター)

i.[–deploy-mode] = クラスター & [–master] = スパーク:* & useRest: PrimaryResource と mainClass が含まれます。
ii.[–deploy-mode] = クラスター & [-master] = スパーク:* & !useRest: が含まれます。 –supervise --memory --cores launch [childArgs]、primaryResource、mainClass
iii.[–deploy-mode] = クラスター & [–master] = 糸: –class --arg --jar/–primary-py-file/ –primary-r-file
iv.[–deploy-mode] = クラスター & [–master] = mesos:*:primaryResource
c) childClasspath を取得する
i.[–deploy-mode] = clent: –jars 設定を読み取り、primaryResource 情報を含む(.../examples/jars/spark-examples_2.11-2.1.0.jar)
d) sysProps を取得します
i.sparkPropertie 内のすべての設定を新しい sysProps オブジェクトにカプセル化し、追加の設定項目を追加します
e) 現在のクラスを介して childClasspath をロードしますローダー
f) sysProps を現在の JVM 環境に設定します。
g) 最後に、childMainClass を反映して実行し、パラメータを childArgs として渡します。

3.3クライアント起動処理

  Client的启动流程如下:

ここに画像の説明を挿入します
1)SparkConf:spark でキーが始まるシステム プロパティを読み込みます (Utils.getSystemProperties)
2)ClientArguments:
a) クライアント起動パラメータを解析します
i.–cores -c --memory -m --supervise -s --verbose -v
ii .launch jarUrl master mainClass
iii.kill master driverId
b) –properties-file の spark. で始まる設定を SparkConf に保存します (設定されていない場合、デフォルトは conf/spark-defaults.conf)
c) 設定がない場合、コアはデフォルトになります1 コアです
d) 設定なしの場合、メモリはデフォルトで 1G になります
e) NettyRpcEnv の内部処理は RpcEndpoint の統一処理に従いますが、ここでは説明しません
3) 最終デーモンは常に存在し、終了メッセージ awaitTermination を待ちます

3.4クライアントのOnStartリスニングイベント

 Client的启动完成后异步执行工作如下: 

ここに画像の説明を挿入します
1) リリースタスク (ケース起動) の場合、クライアントは DriverDescription を作成し、マスターへの RequestSubmitDriver リクエストを開始します。 a
ここに画像の説明を挿入します
) コマンドの mainClass は org.apache.spark.deploy.worker.DriverWrapper です。
b) 引数コマンド内の内容は次のとおりです: Seq ("{ {WORKER_URL}}", "{ {USER_JAR}}", driverArgs.mainClass)
2) マスターが RequestSubmitDriver リクエストを受け入れた後、DriverDescription を DriverInfo にカプセル化します。

ここに画像の説明を挿入します
a) startTime と submitDate は両方とも現在時刻です。
b) driverId の形式は次のとおりです: driver-yyyyMMddHHmmss-nextId、nextId はグローバルに一意です。
3) マスターは DriverInfo を永続化し、それを待機中のドライバー リスト (waitingDrivers) に追加して、パブリック リソースのスケジュール ロジックをトリガーします。
4) マスター パブリック リソースのスケジューリングが完了すると、SubmitDriverResponse がクライアントに返されます。

3.5RpcMessage処理(receiveAndReply)

3.6OneWayMessage処理(受信)

ここに画像の説明を挿入します

4. ドライバーとドライバーランナー

クライアントはマスターへの RequestSubmitDriver リクエストを開始し、マスターは DriverInfo を待機リスト (waitingDrivers) に追加します。ドライバーの概要をさらに詳しく説明します。

4.1マスターがドライバーのリソースを割り当てる

 大致流程如下:

ここに画像の説明を挿入します
waitDrivers とaliveWorkers の間のリソースの一致
。1)waitingDrivers ループで、すべての生きているWorkers をポーリングします
。2)aliveWorker が現在のwaitingDriver リソース要件を満たしている場合は、LaunchDriver コマンドをワーカーに送信し、waitingDriver からwaitingDriver を削除してから、次のwaitingDriver ポーリングを実行します。 work
3) ポーリング後にすべてのaliveWorkersがwaitingDriverリソース要件を満たさない場合、次のwaitingDriverポーリング作業が実行されます
4) 開始されたすべてのポーリング開始ポイントは、最後のポーリング終了ポイントの次のポイントから開始されます。

4.2Worker が DriverRunner を実行する

ドライバーの起動のプロセスは次のとおりです。
ここに画像の説明を挿入します
1) ワーカーが LaunchDriver 命令に遭遇すると、DriverRunner を作成して起動します。
2) DriverRunner は、ドライバーの起動作業を処理するスレッド [DriverRunner for [driverId]] を開始します。
3) [DriverRunner for [ driverId]]:
a) JVM フックを追加し、各 driverId の一時ディレクトリを作成します。
b) Netty を介して DriverDesc.jarUrl を Driver マシンからリモートでコピーし
ます。 c) DriverDesc.command テンプレートに基づいてローカルで実行されるコマンドを構築し、プロセスを開始します。 d)コマンドに対応する
プロセスの出力ストリームをファイル stdout/stderror に出力します。プロセスの起動に失敗した場合は、起動が成功するまで 1 ~ 5 秒間起動作業を繰り返し、その後、コマンドの DriverRunner リソースを解放します。ワーカーノード。

4.3DriverRunner は DriverWrapper を作成して実行します

 DriverWrapper的运行,流程如下:

ここに画像の説明を挿入します
1) DriverWapper は RpcEndpoint と RpcEnv を作成します
2) RpcEndpoint は WorkerWatcher であり、主な目的は Worker ノードが正常かどうかを監視し、例外が発生した場合は直接終了することです
3) その後、現在の ClassLoader が userJar をロードし、同時に userMainClass を実行します
4 ) ユーザーのメインメソッド実行後、workerWatcher を閉じる

5、SparkContext解析

5.1SparkContext解析

SparkContext は、Spark クラスターへのユーザーの唯一の入り口です。Spark を使用する必要がある場所ではどこでも、最初に SparkContext を作成する必要があります。では、SparkContext は何をするのでしょうか?
まず、SparkContext は Driver プログラムで開始されます。これは Driver プログラムと Spark クラスター間の接続とみなすことができます。初期化中に、SparkContext は多くのオブジェクトを作成します。上の図は、
ここに画像の説明を挿入します
初期化中の SparkContext のいくつかの主要コンポーネントの構造を示しています。創作です。

5.2SparkContextの作成プロセス

作成プロセスは次のとおりです。 SparkContext は
ここに画像の説明を挿入します
、新規作成時に
内部的に SparkEnv を作成し、SparkEnv 内
で内部的に RpcEnv を作成します。 a) RpcEnv 内で内部的に MapOutputTrackerMasterEndpoint を作成して登録します (この Endpoint はまだ導入されません)
2) その後、DAGScheduler を作成します。 TaskSchedulerImpl、および SchedulerBackend
a) SchedulableBuilder の作成時に TaskSchedulerImpl を作成します。 SchedulableBuilder は、タイプに応じて FIFOSchedulableBuilder と FairSchedulableBuilder の 2 つのカテゴリに分けられます。
3) 最後に TaskSchedulerImpl を開始し、TaskSchedulerImpl は SchedulerBackend を開始します。
a) SchedulerBackend の開始時に ApplicationDescription、DriverEndpoint、StandloneAppClient を作成します。
b) StandloneAppClient ClientEndpoint が内部に含まれています

5.3SparkContextの単純な構造と相互作用関係

ここに画像の説明を挿入します
1)SparkContext: ユーザー Spark がタスクを実行するコンテキストであり、ユーザー プログラムは Spark が提供する API を使用して直接または間接的に SparkContext を作成します
2)SparkEnv: 通信関連のエンドポイントを含むユーザー実行の環境情報
3 )RpcEnv: SparkContext のリモート通信環境
4)ApplicationDescription: 主に appName、maxCores、memoryPerExecutorMB、coresPerExecutor、Command (
CoarseGrainedExecutorBackend)、appUiUrl などを含むアプリケーションの説明情報
5)ClientEndpoint: クライアント エンドポイント。起動後のマスター
6)マスター: RegisterApplication リクエストを受け付けた後、ワーカー リソースを割り当て、割り当てられたリソースに対して LaunchExecutor 命令を開始します。
7) ワーカー: LaunchExecutor 命令を受け付けた後、ExecutorRunner を実行します。
8) ExecutorRunner: アプリケーションの Command コマンドを実行します説明、最後に Executor を追加し、同時に DriverEndpoint に Executor 情報を登録します

5.4マスターがアプリケーションにリソースを割り当てる

マスターがドライバーの RegisterApplication リクエストを受け入れると、そのリクエストは waitDrivers キューに置かれ、同じスケジュールでリソースの割り当てが実行されます。

ここに画像の説明を挿入します
waitingApps と生きているWorkers 間のリソースの一致
1)waitingApp が app.desc.coresPerExecutor で構成されている場合:
a) すべての有効な割り当て可能なワーカーをポーリングし、毎回 1 つのエグゼキュータを割り当てます。エグゼキュータのコア数は minCoresPerExecutor (app.desc.coresPerExecutor) です。有効な割り当て可能なリソースがなくなるまで、またはアプリが依存するすべてのリソースが割り当てられるまで
2) waitApp が app.desc.coresPerExecutor を構成しない場合:
a) すべての有効な割り当て可能なワーカーをポーリングし、各ワーカーにエグゼキュータが割り当てられます。エグゼキューター コアの数は、有効な割り当て可能なリソースがなくなるか、アプリが依存するすべてのリソースが割り当てられるまで、minCoresPerExecutor (固定値 1) から増加し始めます。3
) 有効な割り当て可能なワーカーは次のように定義されます。 1 つのリソース割り当てを満たすワーカー:
a) コアが満たされる: unavailableWorkers( pos).coresFree - assignCores(pos) >= minCoresPerExecutor、
b) メモリが満たされる (新しい Executor の場合): unavailableWorkers(pos).memoryFree - assignExecutors( pos) *memoryPerExecutor >=memoryPerExecutor
注: マスターは applicationInfo にリソースを割り当てます。有効で利用可能なリソースがある場合、それらは直接割り当てられ、残りの app.coresLeft は次回割り当てられます。

5.5ワーカーがエグゼキューターを作成する

ここに画像の説明を挿入します
(図: オレンジ色のコンポーネントは Endpoint コンポーネントです)
Worker が Executor を開始します
1) Worker の tempDir の下にアプリケーションと executor ディレクトリを作成し、chmod700 操作権限を
与えます 2) ExecutorRunner を作成して起動し、Executor を作成します
3) Executor のステータスをマスター
ExecutorRnnerに送信します
1) 新しいスレッド [ExecutorRunner for [executorId]] は ApplicationDescription を読み取り、コマンドをローカル コマンド コマンドに変換します。
2) コマンドを呼び出し、executor ディレクトリ内の stdout および stderr ログ ファイルにログを出力します。コマンドに対応する Java クラスは次のとおりです。 1) SparkEnv

作成し、ExecutorEndpoint (CoarseGrainedExecutorBackend) と WorkerWatcher を作成します
2) ExecutorEndpoint が作成されて開始されたら、RegisterExecutor リクエストを DriverEndpoint に送信し、戻りを待ちます
3) DriverEndpoint は RegisterExecutor リクエストを処理し、 ExecutorEndpointRegister の結果
4) 登録が成功した場合、ExecutorEndpoint 内部 次に Executor 処理オブジェクトを作成します
この時点で、Spark 実行タスクのコンテナ フレームワークが完成します。

6. ジョブのサブミットとタスクの分割

クライアントの読み込みに関する前の章で、Spark の DriverRunner はユーザー タスク クラス (例: org.apache.spark.examples.SparkPi) の実行を開始しました。次に、ユーザー タスク クラス (またはタスク コード) の分析を開始します。

6.1 全体的なプレビュー

ここに画像の説明を挿入します
1) Code: ユーザーが記述したコードを指します
2) RDD: Elastic Distributed Data Set. ユーザーコーディングは SparkContext と RDD の API に従って Code を RDD データ構造に変換できます (変換の詳細は後述します)
3 ) DAGScheduler : 有向非循環グラフ スケジューラ、RDD を JobSubmitted オブジェクトにカプセル化し、EventLoop (実装クラス DAGSchedulerEventProcessLoop) キューに保存します。
4) EventLoop: 未処理の JobSubmitted オブジェクトを定期的にスキャンし、JobSubmitted オブジェクトを DAGScheduler に送信します。
5) DAGScheduler: JobSubmitted プロセスをターゲットにし、最後に、RDD を実行済みの TaskSet に変換し、TaskSet を TaskScheduler に送信します。
6) TaskScheduler: TaskSet に基づいて TaskSetManager オブジェクトを作成し、ScheduledBuilder のデータ プール (Pool) に保存し、DriverEndpoint を呼び出して消費を呼び出します。 (ReviveOffers) 操作
7) DriverEndpoint: Accept ReviveOffers 命令の後、TaskSet 内のタスクは、関連するルールに従って Executor に均等に分配されます。
8) Executor: TaskRunner を開始してタスクを実行します。

6.2初期RDDに変換されたコード

ユーザー コードは Spark の API (例: SparkSession.builder.appName("Spark Pi").getOrCreate()) を呼び出し、これにより Spark コンテキスト (SparkContext) が作成されます。変換クラス メソッドを呼び出すとき (例:Parallelize () 、map()) は、Spark データ構造 (RDD) を作成 (または既存の修飾) します。アクション タイプの操作 (reduce() など) の場合、最後にカプセル化された RDD がジョブとして送信され、保存されます。スケジューリング キュー (DAGSchedulerEventProcessLoop) で後続の非同期処理を待機しています。
アクション クラスの操作が複数回呼び出された場合、複数のカプセル化された RDD が複数のジョブとして送信されます。
プロセスは次のとおりです。

ここに画像の説明を挿入します
ExecuteEnv (実行環境)
1) これは、spark-submit を通じて送信された MainClass にすることも、spark-shell スクリプトにすることもできます
2) MainClass: SparkContext がコード内で作成または取得されます3) スパークシェル: SparkContext RDD
デフォルトで作成されます
(復元力のある分散データ セット)


1)create: 直接作成することもできます (sc.Parallelize(1 until n,スライス) など)、または別の場所で読み取ることもできます (sc.textFile("README.md") など)。: rdd によって提供されます 一連の API を使用して、既存の RDD を新しい RDD に繰り返しカプセル化できます。ここではデコレータ デザイン パターンが使用されます。以下は部分的なデコレータ クラス図です。

ここに画像の説明を挿入します
3) アクション: RDD のアクション クラス操作メソッドが呼び出されると (収集、削減、検索、保存)、DAGScheduler のジョブ送信がトリガーされます。
4) DAGScheduler: DAGSchedulerEventProcessLoop ブロッキング メッセージ キュー (LinkedBlockingDeque) に JobSubmitted という名前のメッセージを作成します。
5 )DAGSchedulerEventProcessLoop : [dag-scheduler-event-loop] という名前のスレッドを開始して、メッセージ キューをリアルタイムで消費します
6) [dag-scheduler-event-loop] 処理完了後に JobWaiter をコールバックします
7) DAGScheduler: ジョブの実行結果を出力します
8) JobSubmitted: 関連 コードは次のとおりです (jobId は DAGScheduler グローバル増分 ID です)。

eventProcessLoop.post(JobSubmitted(
        jobId, rdd, func2, partitions.toArray, callSite, waiter,
        SerializationUtils.clone(properties)))

ここに画像の説明を挿入します
最終的に変換された RDD は 4 つの層に分割されます。各層は上位層の RDD に依存します。ShffleRDD はジョブにカプセル化され、処理のために DAGSchedulerEventProcessLoop に保存されます。コード内に上記のコード例が複数ある場合は、対応するものがいくつかあります。 ShffleRDDはそれぞれDAGSchedulerEventProcessLoopに格納されます

6.3RDD は実行されるタスクのセット (TaskSet) に分解されます

ジョブが送信されると、DAGScheduler はジョブとステージ間の関係を維持しながら、RDD 階層関係に基づいてジョブを対応するステージに解析します。
同時実行関係 (findMissingPartitions) に基づいて最上位ステージを複数のタスクに分解し、これらの複数のタスクを TaskSet にカプセル化し、TaskScheduler に送信します。非トップレベル Stage は処理リスト (waitingStages += stage) に格納されます。
処理は次のとおりです:
ここに画像の説明を挿入します
1) DAGSchedulerEventProcessLoop で、スレッド [dag-scheduler-event-loop] が JobSubmitted まで処理されます
2) DAGScheduler を呼び出しますa) まず、RDD の依存関係
に従って、Stage ファミリを順番に作成し、ShuffleMapStage と ResultStage に分割します。

ここに画像の説明を挿入します
b) jobId と StageId の間の関係のマップを更新します
c) ActiveJob を作成し、LiveListenerBug を呼び出し、SparkListenerJobStart コマンドを送信します
d) 送信する最上位の Stage を見つけ、下位の Stage は後続の処理のために waitingStage に保存されます
i. stageStart に対して OutputCommitCoordinator を呼び出します() 処理
ii. LiveListenerBug を呼び出し、SparkListenerStageSubmitted 命令を送信し
、SparkContext のブロードキャスト メソッドを呼び出して Broadcast オブジェクトを取得します。

ここに画像の説明を挿入します
ステージの種類に応じて、対応するタスクを複数作成します。ステージは、findMissingPartitions に従って、対応する複数のタスクに分割されます。タスクは、ShuffleMapTask と ResultTask に分割されます。
iv. タスクを TaskSet にカプセル化し、タスクのスケジュール設定のために TaskScheduler.submitTasks(taskSet) を呼び出します。キーコードは次のとおりです。

taskScheduler.submitTasks(new TaskSet(
        tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))

6.4TaskSet は TaskSetManager にカプセル化され、ドライバーに送信されます

TaskScheduler は、TaskSet を TaskSetManager(new TaskSetManager(this, taskSet, maxTaskFailures, blacklistTrackerOpt)) にカプセル化し、保留中のタスク プール (Pool) に保存し、消費を呼び出すための DriverEndpoint コマンドを送信します (ReviveOffers)

ここに画像の説明を挿入します
1) DAGSheduler は、TaskSet を TaskScheduler の実装クラスに送信します。ここでは TaskChedulerImpl です。
2) TaskSchedulerImpl は、TaskSet を管理するための TaskSetManager を作成します。キー コードは次のとおりです:
new TaskSetManager(this, taskSet, maxTaskFailures, blacklistTrackerOpt)
3) 同時に、 TaskSetManager をタスク プールに追加 SchedduableBuilder のポーリング 4
) SchedulerBackend の実装クラスを呼び出して、reviveOffers を実行します。これがスタンドロン モードの実装クラスです StandaloneSchedulerBackend
5) SchedulerBackend が ReviveOffers コマンドを DriverEndpoint に送信します

6.5Driver は TaskSetManager を TaskDescription に分解し、タスクを Executor に発行します

ドライバーが呼び出し消費命令を受け入れた後、すべての保留中の TaskSetManager をドライバーに登録されている Executor リソースと照合します。最後に、TaskSetManager は複数の TaskDescription オブジェクトを取得し、TaskDescription に従って対応する Executor に LaunchTask 命令を送信します。
ここに画像の説明を挿入します
ドライバーが ReviveOffers を取得すると、 (リクエスト消費) 命令
1) まず、executorDataMap キャッシュ情報に基づいて、使用可能な Executor リソース情報 (WorkerOffer) を取得します。キーコードは次のとおりです。

val activeExecutors = executorDataMap.filterKeys(executorIsAlive)
val workOffers = activeExecutors.map {
    
     case (id, executorData) =>
  new WorkerOffer(id, executorData.executorHost, executorData.freeCores)
}.toIndexedSeq

2) 次に、リソース照合のために TaskScheduler を呼び出します。メソッドは次のように定義されています:
def resourceOffers(offers: IndexedSeq[WorkerOffer]): Seq[Seq[TaskDescription]] = synchronized {…}
a) WorkerOffer リソースをシャッフルします (val shuffledOffers = Random) .shuffle (offers))
b) Poo で保留中の TaskSetManager を取り出します (valsortedTaskSets = rootPool.getSortedTaskSetQueue)、
c) shuffledOffers(i) に十分な Cpu リソースがある場合、sortedTaskSets をループして shuffledOffers ループと一致します (if (availableCpus( i) >= CPUS_PER_TASK))、TaskSetManager を呼び出して TaskDescription オブジェクト (taskSet.resourceOffer(execId, host, maxLocality)) を作成し、最後に複数の TaskDescription を作成します。TaskDescription は次のように定義されます。

new TaskDescription(
        taskId,
        attemptNum,
        execId,
        taskName,
        index,
        sched.sc.addedFiles,
        sched.sc.addedJars,
        task.localProperties,
        serializedTask)

3) TaskDescriptions が空でない場合は、TaskDescriptions をループし、TaskDescription オブジェクトをシリアル化し、LaunchTask 命令を ExecutorEndpoint に送信します。キー コードは次のとおりです。

for (task <- taskDescriptions.flatten) {
    
    
        val serializedTask = TaskDescription.encode(task)
        val executorData = executorDataMap(task.executorId)
        executorData.freeCores -= scheduler.CPUS_PER_TASK
        executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
}

7. タスクの実行と受信

DriverEndpoint は最終的に複数の実行可能な TaskDescription オブジェクトを生成し、LaunchTask 命令を各 ExecutorEndpoint に送信します。このセクションでは、ExecutorEndpoint が LaunchTask 命令を処理する方法、処理完了後に DriverEndpoint にフィードバックする方法、ジョブ全体が最終的に終了するまで複数回スケジュールされる方法に焦点を当てます。 。

7.1タスク実行処理

Executor は LaunchTask 命令を受け入れると、新しいスレッド TaskRunner を開始して RDD を解析し、RDD の compute メソッドを呼び出し、関数をマージして最終的なタスクの実行結果を取得します。

ここに画像の説明を挿入します
1) LaunchTask 命令を受信した後、ExecutorEndpoint は TaskDescription をデコードし、Executor の launchTask メソッドを呼び出し、
Executor は TaskRunner スレッドを作成し、スレッドを開始し、スレッドを Executor のメンバー オブジェクトに追加します。コードは次のとおりです。 :

private val runningTasks = new ConcurrentHashMap[Long, TaskRunner]
runningTasks.put(taskDescription.taskId, taskRunner)

TaskRunner
1) まず、タスクの最新ステータスを RUNNING として DriverEndpoint に送信します。
2) TaskDescription からタスクを解析し、タスクの run メソッドを呼び出します。
Task1
) TaskContext および CallerContext (HDFS と対話するコンテキスト オブジェクト) を作成
します。 2)タスクの runTask メソッド
a) Task インスタンスが ShuffleMapTask の場合: RDD および ShuffleDependency 情報を解析し、RDD の compute() メソッドを呼び出し、結果を Writer に書き込みます (Writer はここでは紹介されていません。Writer はb) Taskインスタンスが ResultTask の場合:
RDD を解析して関数情報をマージすると、呼び出し関数は呼び出し後に結果を返します。TaskRunner は
シリアル化します。タスクの実行結果を確認し、再度 DriverEndpoint にタスクを送信します。最新のステータスは FINISHED です。

7.2タスクフィードバックプロセス

TaskRunner の実行が完了すると、実行ステータスが DriverEndpoint に送信され、DriverEndpoint は最終的にコマンド CompletionEvent を DAGSchedulerEventProcessLoop にフィードバックします。

ここに画像の説明を挿入します
1) DriverEndpoint は、StatusUpdate メッセージを受信した後、TaskScheduler の statusUpdate(taskId, state, result) メソッドを呼び出します
2) TaskScheduler のタスク結果が完了したら、タスク処理のステータスをクリアし、TaskResultGetter を動員します関連メソッド。キーコードは次のとおりです。

val taskSet = taskIdToTaskSetManager.get(tid)

taskIdToTaskSetManager.remove(tid)
        taskIdToExecutorId.remove(tid).foreach {
    
     executorId =>
    executorIdToRunningTaskIds.get(executorId).foreach {
    
     _.remove(tid) }
}
taskSet.removeRunningTask(tid)
if (state == TaskState.FINISHED) {
    
    
    taskResultGetter.enqueueSuccessfulTask(taskSet, tid, serializedData)
} else if (Set(TaskState.FAILED, TaskState.KILLED, TaskState.LOST).contains(state)) {
    
    
    taskResultGetter.enqueueFailedTask(taskSet, tid, state, serializedData)
}

TaskResultGetterはスレッドを起動し、関連処理のためのスレッド[task-result-getter]を起動します
1) 解析またはリモート取得によりTaskのTaskResultオブジェクトを取得します
2) TaskSetのhandleSuccessfulTask​​メソッドを呼び出します。 TaskSet の handleSuccessfulTask​​ メソッドを直接呼び出します。TaskSetManager 1) 内部の TaskInfo オブジェクトのステータスを更新し、実行中のタスクのコレクションからタスクを削除します。コードは次のとおりです

val info = taskInfos(tid)
info.markFinished(TaskState.FINISHED, clock.getTimeMillis())
removeRunningTask(tid)

2) DAGScheduler の taskEnded メソッドを呼び出します。キーコードは次のとおりです。

sched.dagScheduler.taskEnded(tasks(index), Success, result.value(), result.accumUpdates, info)

DAGScheduler は、CompletionEvent 命令を DAGSchedulerEventProcessLoop に保存します。CompletionEvent オブジェクトは次のように定義されます。

private[scheduler] case class CompletionEvent(
task: Task[_],
reason: TaskEndReason,
result: Any,
accumUpdates: Seq[AccumulatorV2[_, _]],
taskInfo: TaskInfo) extends DAGSchedulerEvent

7.3タスクの反復プロセス

DAGSchedulerEventProcessLoopのCompletionEvent命令では、DAGSchedulerが呼び出されて処理されます。DAGSchedulerは、StageとTaskの関係状況を更新します。Stage配下のタスクがすべて返されると、次のステージのタスクの分解と計算作業が完了するまで行われます。ジョブが実行されます:

ここに画像の説明を挿入します
1) DAGSchedulerEventProcessLoop は CompletionEvent 命令を受信した後、DAGScheduler の handleTaskCompletion メソッドを呼び出します。
2) DAGScheduler はタスクの種類に応じて個別に処理します。
3) タスクが ShuffleMapTask の場合
a) フィードバックされるパーティションは現在のパーティション ID から減算されます。
b) すべてのタスクが返された場合は、MapOutputTrackerMaster に MapOutputs 情報を登録しながら、markStageAsFinished( shuffleStage) を実行し、markMapStageJobAsFinished を
実行します。 c) submitWaitingChildStages(shuffleStage) を呼び出して下位ステージを処理します。これにより、反復処理が最終的に ResultTask に処理され、ジョブが終了します。キーコードは次のとおりです。

private def submitWaitingChildStages(parent: Stage) {
    
    
    ...
    val childStages = waitingStages.filter(_.parents.contains(parent)).toArray
    waitingStages --= childStages
    for (stage <- childStages.sortBy(_.firstJobId)) {
    
    
        submitStage(stage)
    }
}

4) タスクが ResultTask の場合
a) ジョブのパーティションを変更し、すべてが返された後、markStageAsFinished(resultStage)、および cleanupStateForJobAndIndependentStages(job) のキー コードは次のとおりです。

for (stage <- stageIdToStage.get(stageId)) {
    
    
    if (runningStages.contains(stage)) {
    
    
        logDebug("Removing running stage %d".format(stageId))
        runningStages -= stage
    }
    for ((k, v) <- shuffleIdToMapStage.find(_._2 == stage)) {
    
    
        shuffleIdToMapStage.remove(k)
    }
    if (waitingStages.contains(stage)) {
    
    
        logDebug("Removing stage %d from waiting set.".format(stageId))
        waitingStages -= stage
    }
    if (failedStages.contains(stage)) {
    
    
        logDebug("Removing stage %d from failed set.".format(stageId))
        failedStages -= stage
    }
}
// data structures based on StageId
stageIdToStage -= stageId
jobIdToStageIds -= job.jobId
jobIdToActiveJob -= job.jobId
activeJobs -= job

この時点で、ユーザーが作成したコードは最終的に Spark 分散コンピューティングを呼び出します。

おすすめ

転載: blog.csdn.net/qq_44696532/article/details/135390525