Sparkカーネル分析(5)Sparkタスクスケジューリングの原理とメカニズムの詳細な分析

ファクトリ環境でSpark 集群的部署方式一般为YARN-Cluster 模式は、次のカーネル分析では、デフォルトのクラスターデプロイメント方法はYARN-クラスターモードです。

1. Sparkタスク送信プロセス

前の章では、次の図に示すように、Spark YARN-クラスターモードでのタスク送信プロセスについて説明しました。
ここに画像の説明を挿入

次のシーケンス図は、Sparkアプリケーションの
ここに画像の説明を挿入
送信から操作までのプロセス全体を明確に示しています。Sparkアプリケーションを送信し、最初にクライアントを介してResourceManagerにアプリケーションの起動を要求し、アプリケーションの要件を満たすのに十分なリソースがあるかどうかを確認します。条件が満たされると、ApplicationMasterの起動コンテキストが準備され、ResourceManagerに渡され、アプリケーションのステータスが周期的に監視されます。

送信されたリソースキューにリソースがある場合、ResourceManagerはNodeManagerでApplicationMasterプロセスを開始し、ApplicationMasterはドライバーバックグラウンドスレッドを個別に開始します。ドライバーが開始されると、ApplicationMasterはローカルRPCを介してドライバーに接続し、ResourceManagerからコンテナーリソースの要求を開始しますエグゼキュータプロセスを実行します(1つのエグゼキュータは1つのコンテナに対応します)。ResourceManagerがコンテナリソースを返すと、ApplicationMasterは対応するコンテナでエグゼキュータを開始します。

ドライバースレッドは、主にSparkContextオブジェクトを初期化し、実行に必要なコンテキストを準備し、一方でApplicationMasterとのRPC接続を維持し、ApplicationMasterを介してリソースを申請し、他方でユーザーのビジネスロジックに従ってタスクのスケジューリングを開始し、既存のアイドルにタスクを送信しますエグゼキューター。ResourceManagerがコンテナリソースをApplicationMasterに返すと、ApplicationMasterは対応するコンテナでエグゼキュータプロセスを開始しようとします。エグゼキュータプロセスが起動すると、逆方向にドライバに登録されます。登録が成功すると、ドライバがタスクを分散するのを待つ間、ドライバとのハートビートを維持します。タスクが実行されると、タスクのステータスがドライバーに報告されます。

上記のシーケンス図からわかるように、クライアントはアプリケーションの送信とアプリケーションのステータスの監視のみを担当します。
スパークのタスクスケジューリングのために主に二つの側面に集中している: 资源申请そして任务分发、その主要是通过ApplicationMaster、Driver 以及 Executor 之间来完成。

2. Sparkタスクスケジューリングの概要

ドライバーが起動すると、ドライバーはユーザープログラムロジックに従ってタスクを準備し、エグゼキューターリソースの状況に従ってタスクを徐々に分散します。タスクスケジューリングについて詳しく説明する前に、まずSparkのいくつかの概念を説明します。

Sparkアプリケーションには、ジョブ、ステージ、タスクの3つの概念があります。

(1)Jobアクションメソッドを境界として取り、アクションメソッドに遭遇したときにジョブをトリガーします。

(2)これStageはジョブのサブセットであり、RDDの広い依存関係(つまり、シャッフル)によって制限されており、シャッフルは分割のために1回発生します。

(3)Task並列度(パーティションの数)で測定されるステージのサブセットであり、パーティションの数はタスクの数です。

Spark 的任务调度总体来说分两路进行:一路是 Stage 级的调度,一路是 Task级的调度,总体调度流程如下图所示:
ここに画像の説明を挿入
Spark RDD 通过其 Transactions 操作,形成了 RDD 血缘关系图,即 DAG,最后通过 Action 的调用,触发 Job 并调度执行。
DAGScheduler 负责 Stage 级的调度,主要是将 job 切分成若干 Stages,并将每个 Stage 打包成 TaskSet 交给 TaskScheduler调度。
TaskScheduler 负责 Task 级的调度,将 DAGScheduler 给过来的 TaskSet 按照指定的调度策略分发到 Executor 上执行,调度过程中 SchedulerBackend 负责提供可用资源,其中 SchedulerBackend 有多种实现,分别对接不同的资源管理系统。

三、Spark Stage 级调度

Spark 的任务调度是从 DAG 切割开始,主要是由 DAGScheduler 来完成。当遇到一个 Action 操作后就会触发一个 Job 的计算,并交给 DAGScheduler 来提交,下图是涉及到 Job 提交的相关方法调用流程图。
ここに画像の説明を挿入
Job 由 最 终 的 RDD 和 Action 方 法 封 装 而 成 , SparkContext 将 Job 交 给DAGScheduler 提交,它会根据 RDD 的血缘关系构成的 DAG 进行切分,将一个 Job划分为若干 Stages,具体划分策略是,由最终的 RDD 不断通过依赖回溯判断父依赖是否是宽依赖,即以 Shuffle 为界,划分 Stage,窄依赖的 RDD 之间被划分到同一个Stage 中,可以进行 pipeline 式的计算,如上图紫色流程部分。划分的 Stages 分两类,一类叫做 ResultStage,为 DAG 最下游的 Stage,由 Action 方法决定,另一类叫做ShuffleMapStage,为下游 Stage 准备数据,下面看一个简单的例子 WordCount。
ここに画像の説明を挿入
Job 由 saveAsTextFile 触发,该 Job 由 RDD-3 和 saveAsTextFile 方法组成,根据RDD 之间的依赖关系从 RDD-3 开始回溯搜索,直到没有依赖的 RDD-0,在回溯搜索过程中,RDD-3 依赖 RDD-2,并且是宽依赖,所以在 RDD-2 和 RDD-3 之间划分Stage,RDD-3 被划到最后一个 Stage,即 ResultStage 中,RDD-2 依赖 RDD-1,RDD-1依赖 RDD-0,这些依赖都是窄依赖,所以将 RDD-0、RDD-1 和 RDD-2 划分到同一个 Stage,即 ShuffleMapStage 中,实际执行的时候,数据记录会一气呵成地执行RDD-0 到 RDD-2 的转化。不难看出,其本质上是一个深度优先搜索算法

一个 Stage 是否被提交,需要判断它的父 Stage 是否执行,只有在父 Stage 执行完毕才能提交当前 Stage,如果一个 Stage 没有父 Stage,那么从该 Stage 开始提交。Stage 提交时会将 Task 信息(分区信息以及方法等)序列化并被打包成 TaskSet 交给TaskScheduler,一个 Partition 对应一个 Task,另一方面 TaskScheduler 会监控 Stage的运行状态,只有 Executor 丢失或者 Task 由于 Fetch 失败才需要重新提交失败的Stage 以调度运行失败的任务,其他类型的 Task 失败会在TaskScheduler 的调度过程中重试。

相对来说 DAGScheduler 做的事情较为简单,仅仅是在 Stage 层面上划分 DAG,提交 Stage 并监控相关状态信息。TaskScheduler 则相对较为复杂,下面详细阐述其细节。

四、Spark Task 级调度

Spark Task 的调度是由 TaskScheduler 来完成,由前文可知,DAGScheduler 将Stage 打 包 到 TaskSet 交 给 TaskScheduler, TaskScheduler 会将TaskSet 封装为TaskSetManager 加入到调度队列中,TaskSetManager 结构如下图所示。
ここに画像の説明を挿入
TaskSetManager 负责监控管理同一个 Stage 中的 Tasks,TaskScheduler 就是以TaskSetManager 为单元来调度任务。

TaskScheduler 初始化后会启动 SchedulerBackend,它负责跟外界打交道,接收Executor 的注册信息,并维护 Executor 的状态。所以说 SchedulerBackend 是管“粮食”的,同时它在启动后会定期地去“询问”TaskScheduler 有没有任务要运行,也就是说,它会定期地 “问 ”TaskScheduler“我 有 这 么 余 量 , 你 要 不 要 啊 ”, TaskScheduler 在SchedulerBackend“ 问 ” 它的时候,会从调度队列中按照指定的调度策略选择TaskSetManager 去调度运行,大致方法调用流程如下图所示:
ここに画像の説明を挿入
上图中,将 TaskSetManager 加入 rootPool 调度池中之后,调用 SchedulerBackend的 riviveOffers 方法给 driverEndpoint 发送 ReviveOffer 消息;driverEndpoint 收到ReviveOffer 消息后调用 makeOffers 方法,过滤出活跃状态的 Executor(这些 Executor都是任务启动时反向注册到 Driver 的 Executor),然后将 Executor 封装成 WorkerOffer对 象 ; 准 备 好 计 算 资 源 ( WorkerOffer ) 后 , taskScheduler 基 于 这 些 资 源 调 用resourceOffer 在 Executor 上分配 task。

4.1スケジューリング戦略

前述のように、TaskSchedulerは最初にDAGSchedulerによって指定されたTaskSetをTaskSetManagerにカプセル化し、タスクキューにスローします。次に、特定のルールに従ってそれらをタスクキューから取り出し、SchedulerBackendによって指定されたエグゼキュータで実行します。这个调度过程实际上还是比较粗粒度的,是面向 TaskSetManager 的。

スケジューリングキューの階層を次の図に示します
ここに画像の説明を挿入
。TaskScheduler はタスクキュー管理し、ツリーのノードタイプは[スケジュール済み]、リーフノードはTaskSetManager、非リーフノードはプールです。次の図は、それらの間の継承関係を示しています。
ここに画像の説明を挿入
TaskSchedulerは2つのスケジューリング戦略をサポートしています。

(1)FIFOもデフォルトのスケジューリング戦略です

(2)フェア

TaskSchedulerの初期化中に、rootPoolがインスタンス化されます。これは、ツリーのルートノードを表し、タイプはプールです。

1. FIFOスケジューリング戦略

FIFOスケジューリング戦略の実行手順は次のとおりです。
(1)2つのScheduled s1およびs2の優先度(Scheduleクラスの属性。優先度としてマークされ、値が小さいほど優先度が高くなります)。

(2)2つのスケジュールの優先度が同じ場合、s1とs2が属するステージのIDを比較します(Scheduleクラスの属性、優先度としてマークされている、値が小さいほど優先度が高くなります)。

(3)比較の結果が0未満の場合、s1が最初にスケジュールされ、それ以外の場合はs2が最初にスケジュールされます。
ここに画像の説明を挿入

2. FAIRスケジューリング戦略

FAIRスケジューリング戦略のツリー構造を次の図に示し
ここに画像の説明を挿入
ます。FAIRモードでは、rootPoolと複数のサブプールがあり、各サブプールは割り当てられるすべてのTaskSetMagagerを格納します。

可以通过在 Properties 中指定 spark.scheduler.pool 属性,指定调度池中的某个调度池作为 TaskSetManager 的父调度池,如果根调度池不存在此属性值对应的调度池,会创建以此属性值为名称的调度池作为 TaskSetManager 的父调度池,并将此调度池作为根调度池的子调度池。

在 FAIR 模 式 中 , 需 要 先 对 子 Pool 进 行 排 序 , 再 对 子 Pool 里 面 的TaskSetMagager 进行排序,因为 Pool 和 TaskSetMagager 都继承了 Schedulable 特质,因此使用相同的排序算法。

排序过程的比较是基于 Fair-share 来比较的,每个要排序的对象包含三个属性:runningTasks 值(正在运行的 Task 数)、minShare 值、weight 值,比较时会综合考量 runningTasks 值,minShare 值以及 weight 值。

注意:minShare、weight 的值均在公平调度配置文件 fairscheduler.xml中被指定,调度池在构建阶段会读取此文件的相关配置。

(1) 如果 A 对象的 runningTasks 大于它的 minShare,B 对象的 runningTasks 小于它的 minShare,那么 B 排在 A 前面;(runningTasks 比 minShare 小的先执行

(2) 如果 A、B 对象的 runningTasks 都小于它们的 minShare,那么就比较runningTasks 与 minShare 的比值(minShare 使用率),谁小谁排前面;(minShare使用率低的先执行

(3) 如果 A、B 对象的 runningTasks 都大于它们的 minShare,那么就比较runningTasks 与 weight 的比值(权重使用率),谁小谁排前面。(权重使用率低的先执行

(4) 如果上述比较均相等,则比较名字。整体上来说就是通过 minShare 和 weight 这两个参数控制比较过程,可以做到让 minShare 使用率和权重使用率少(实际运行 task 比例较少)的先运行。

FAIR 模式排序完成后,所有的 TaskSetManager 被放入一个 ArrayBuffer 里,之后依次被取出并发送给 Executor 执行。

从调度队列中拿到 TaskSetManager 后,由于 TaskSetManager 封装了一个 Stage的所有 Task,并负责管理调度这些 Task,那么接下来的工作就是 TaskSetManager按照一定的规则一个个取出 Task 给 TaskScheduler , TaskScheduler 再交给SchedulerBackend 去发到 Executor 上执行。

4.2 本地化调度

DAGScheduler 切割 Job,划分 Stage, 通过调用 submitStage 来提交一个 Stage对应的 tasks,submitStage 会调用 submitMissingTasks,submitMissingTasks 确定每个需要计算的 task 的 preferredLocations,通过调用 getPreferrdeLocations()得到partition 的优先位置,由于一个 partition 对应一个 task,此 partition 的优先位置就是 task 的优先位置,对于要提交到 TaskScheduler 的 TaskSet 中的每一个 task,该 task优先位置与其对应的 partition 对应的优先位置一致。

从调度队列中拿到 TaskSetManager 后,那么接下来的工作就是 TaskSetManager 按照一定的规则一个个取出 task 给 TaskScheduler,TaskScheduler 再交给 SchedulerBackend 去发到Executor 上执行。前面也提到,TaskSetManager 封装了一个 Stage 的所有 task,并负责管理调度这些 task。

根据每个 task 的优先位置,确定 task 的 Locality 级别,Locality 一共有五种,优先级由高到低顺序:
ここに画像の説明を挿入
在调度执行时,Spark 调度总是会尽量让每个 task 以最高的本地性级别来启动,当一个 task 以 X 本地性级别启动,但是该本地性级别对应的所有节点都没有空闲资源而启动失败,此时并不会马上降低本地性级别启动而是在某个时间长度内再次以X 本地性级别来启动该 task,若超过限时时间则降级启动,去尝试下一个本地性级别,依次类推。可以通过调大每个类别的最大容忍延迟时间,在等待阶段对应的 Executor 可能就会有相应的资源去执行此 task,这就在在一定程度上提升了运行性能。

4.3 失败重试与黑名单机制

スケジューリングと実行に適切なタスクを選択することに加えて、タスクの実行ステータスを監視することも必要です。前述のように、外部を処理するのはSchedulerBackendです。タスクをExecutorに送信して実行を開始すると、ExecutorはSchedulerBackendに実行ステータスを報告し、SchedulerBackendはTaskScheduler、TaskSchedulerは、タスクに対応するTaskSetManagerを検索し、TaskSetManagerに通知して、TaskSetManagerがタスクの失敗と成功のステータスを認識できるようにします。对于失败的 Task,会记录它失败的次数,如果失败次数还没有超过最大重试次数,那么就把它放回待调度的 Task池子中,否则整个 Application 失败。

在记录 Task 失败次数过程中,会记录它上一次失败所在的 Executor Id 和 Host,这样下次再调度这个 Task 时,会使用黑名单机制,避免它被调度到上一次失败的节点上,起到一定的容错作用。ブラックリストは、タスクが最後に失敗したエグゼキューターIDとホスト、およびそれに対応する「ブラックアウト」時間を記録します。「ブラックアウト」時間は、この期間中にこのノードでこのタスクをスケジュールしないことを意味します。

おすすめ

転載: blog.csdn.net/weixin_43520450/article/details/108607827