目录
1. HTTP请求流程
- 应用层:分解url(服务器和请求资源);生成http请求消息;DNS域名解析;发给操作系统;
- 传输层:添加TCP头部;三次握手建立连接;操作系统缓存区累积一个网络包大小的数据或最大等待时间后发送给IP模块;
- 网络层:添加IP头部和MAC头部;(如果为局域网会发送给路由器,路由器拆封到Ip层,根据路由控制表,封装上目标局域网的头信息后转发给目标局域网上的主机);IP中包含网络ID和主机ID,可通过子网掩码与IP按位与操作获取网络ID;
- 链路层:添加报头、帧校验序列;变为电信号;
- 物理层:最终网卡中的PHY模块会将通用电信号转换成网络传输所需的格式,通过网线发送出去。经过网络转发后,最终到达服务器;
- 服务器: 服务器反向重复上述步骤,把电信号逐步转换回http请求信息并进行处理;
- 服务器 服务器返回请求响应,先发送头信息,然后发送一个空白行来表示头信息的发送到此结束,最后以Content-Type应答头信息所描述的格式发送用户所请求的实际数据;
- 服务器关闭TCP连接或者保持连接。
2. MapReduce作业流程(Yarn)
下图中重要的实体:
-
客户端:用于提交job
-
Yarn的资源管理器(ResourceManager,RM):负责协调集群上的资源分配(资源调度器)
-
Yarn的节点管理器(NodeManager,NM):负责节点内所有容器的生命周期的管理,监视资源和跟踪节点健康并上报给AM。
-
Yarn的应用管理器(ApplicationMaster,AM):负责协调运行MapReduce作业的任务,向RM申请资源,跟踪job状态和进度。
-
HDFS:用来与其他实体共享作业文件。
- 作业提交(
RM分配ID,客户端计算分片和上传作业资源
)
- client 调用 job.waitForCompletion 方法,向整个集群提交 MapReduce 作业 (第 1 步) 。
- 新的作业 ID(应用 ID) 由RM分配 (第 2 步)。
- 作业的 client 核实作业的输出, 计算输入的 split, 将作业的资源 (包括 Jar 包,配置文件, split 信息) 拷贝给 HDFS(第 3 步)。
- 最后, 通过调用RM的 submitApplication() 来提交作业 (第 4 步)。
- 作业初始化(
启动AM,AM创建map/reduce任务对象,AM创建输出路径
)
- 当RM收到 submitApplciation() 的请求时, 就将该请求发给调度器 (scheduler), 调度器分配 container, 然后RM在该 container 内启动AM, 由节点管理器监控 (第 5 步)。
- AM通过创造一些 bookkeeping 对象来监控作业的进度, 得到任务的进度和完成报告 (第 6 步)。
- 然后AM通过分布式文件系统得到由客户端计算好的输入 split(第 7 步),然后为每个输入 split 创建一个 map 任务, 根据
mapreduce.job.reduces
创建 reduce 任务对象。 - 最后,在任何任务运行之前,application master调用setupJob()方法设置OutputCommitter,创建输出路径。
- 任务分配(
AM向RM申请资源,RM调度器提供最优资源分配
)
- 如果作业很小, AM会选择在其自己的 JVM 中运行任务。
- 如果不是小作业, 那么AM向RM请求 container 来运行所有的 map 和 reduce 任务 (第 8 步)。这些请求是通过心跳来传输的, 包括每个 map 任务的数据位置,比如存放输入 split 的主机名和机架 (rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入 split 的节点相同机架的节点。
- 任务运行(
AM启动container,资源本地化,运行map/reduce
)
- 当一个任务由RM的调度器分配给一个 container 后,AM通过联系NM来启动 container(第 9 步)。
- 任务由一个主类为 YarnChild 的 Java 应用执行, 在运行任务之前首先本地化任务需要的资源(从HDFS下载),比如作业配置,JAR 文件, 以及分布式缓存的所有文件 (第 10 步)。
- 最后, 运行 map 或 reduce 任务 (第 11 步)。
-
进度和状态更新
YARN 中的任务将其进度和状态 (包括 counter) 返回给应用管理器, 客户端每秒 (通 mapreduce.client.progressmonitor.pollinterval 设置) 向AM请求进度更新, 展示给用户。 -
作业完成
除了向应用管理器请求作业进度外, 客户端每 5 分钟都会通过调用 waitForCompletion() 来检查作业是否完成,时间间隔可以通过 mapreduce.client.completion.pollinterval 来设置。作业完成之后, 应用管理器和 container 会清理工作状态, OutputCommiter 的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。
4. Spark提交作业流程(Yarn)
4.1. Yarn Client模式
7. Driver在任务提交的本地机器上运行,Driver启动后会和RM通讯申请启动AM.
8. 随后RM分配container,在合适的NM上启动AM,此时的AM的功能相当于一个ExecutorLaucher,只负责向ResourceManager申请Executor内存。
9. RM接到AM的资源申请后会分配container,然后AM在资源分配指定的NM上启动Executor进程,
10. Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job,并根据宽依赖开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行
4.2. Yarn Cluster模式
- 任务提交后会和RM通讯申请启动AM,随后RM分配container,在合适的NM上启动AM,此时的AM就是Driver.
- Driver启动后向RM申请Executor内存,RM接到AM的资源申请后会分配container,然后在合适的NM上启动Executor进程.
- Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job,并根据宽依赖开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行。
4.3. Stage和task级调度
- 以下为Stage级调度
- Job由最终的RDD和Action方法封装而成,SparkContext将Job交给DAGScheduler提交,它会根据RDD的血缘关系构成的DAG进行切分,将一个Job划分为若干Stages,具体划分策略是,由最终的RDD不断通过依赖
回溯判断父依赖是否是宽依赖
,即以Shuffle为界,划分Stage,窄依赖的RDD之间被划分到同一个Stage中,可以进行pipeline式的计算。 - 一个Stage是否被提交,需要判断它的父Stage是否执行,只有在父Stage执行完毕才能提交当前Stage,如果一个Stage没有父Stage,那么从该Stage开始提交(
拓扑排序
)。 - Stage提交时会将Task信息(分区信息以及方法等)序列化并被打包成TaskSet交给TaskScheduler,
一个Partition对应一个Task
,另一方面TaskScheduler会监控Stage的运行状态,只有Executor丢失或者Task由于Fetch失败才需要重新提交失败的Stage以调度运行失败的任务,其他类型的Task失败会在TaskScheduler的调度过程中重试。 - TaskScheduler会将TaskSet封装为TaskSetManager加入到调度队列中,TaskScheduler初始化后会启动SchedulerBackend,它负责跟外界打交道,接收Executor的注册信息,并维护Executor的状态。
- 以下为Task级调度
-
TaskScheduler是以树的方式来管理任务队列,树中的节点类型为Schdulable,叶子节点为TaskSetManager,非叶子节点为Pool。
-
TaskScheduler支持两种调度策略,一种是FIFO,也是默认的调度策略,另一种是FAIR。
FIFO调度策略:
-
FIFO调度策略,则直接简单地将TaskSetManager按照先来先到的方式入队,出队时直接拿出最先进队的TaskSetManager
FAIR调度策略:
-
FAIR模式中有一个rootPool和多个子Pool,各个子Pool中存储着所有待分配的TaskSetMagager。在FAIR模式中,需要先对子Pool进行排序,再对子Pool里面的TaskSetMagager进行排序,因为Pool和TaskSetMagager都继承了Schedulable特质,因此使用相同的排序算法。
-
排序过程的比较是基于Fair-share来比较的,每个要排序的对象包含三个属性:
runningTasks
值(正在运行的Task数)、minShare
值、weight
值。整体上来说就是通过minShare和weight这两个参数控制比较过程,可以做到让minShare使用率和权重使用率少(实际运行task比例较少)的先运行
- 从调度队列中拿到TaskSetManager后,由于TaskSetManager封装了一个Stage的所有Task,并负责管理调度这些Task,那么接下来的工作就是TaskSetManager按照一定的规则一个个取出Task给TaskScheduler,TaskScheduler再交给SchedulerBackend去发到Executor上执行。
本地化调度
- DAGScheduler切割Job,划分Stage, 通过调用submitStage来提交一个Stage对应的tasks,submitStage会调用submitMissingTasks,submitMissingTasks 确定每个需要计算的 task 的
preferredLocations
,task优先位置与其对应的partition对应的优先位置一致。根据每个task的优先位置,确定task的Locality级别,Locality一共有五种,优先级由高到低顺序:
在调度执行时,Spark调度总是会尽量让每个task以最高的本地性级别来启动,当一个task以X本地性级别启动,但是该本地性级别对应的所有节点都没有空闲资源而启动失败,此时并不会马上降低本地性级别启动而是在某个时间长度内再次以X本地性级别来启动该task,若超过限时时间则降级启动,去尝试下一个本地性级别,依次类推。
可以通过调大每个类别的最大容忍延迟时间,在等待阶段对应的Executor可能就会有相应的资源去执行此task,这就在在一定程度上提高了运行性能。
失败重试与黑名单
- 除了选择合适的Task调度运行外,还需要监控Task的执行状态,前面也提到,与外部打交道的是SchedulerBackend,Task被提交到Executor启动执行后,Executor会将执行状态上报给SchedulerBackend,SchedulerBackend则告诉TaskScheduler,TaskScheduler找到该Task对应的TaskSetManager,并通知到该TaskSetManager,这样TaskSetManager就知道Task的失败与成功状态,对于失败的Task,会记录它失败的次数,如果失败次数还没有超过最大重试次数,那么就把它放回待调度的Task池子中,否则整个Application失败。
- 在记录Task失败次数过程中,会记录它上一次失败所在的Executor Id和Host,这样下次再调度这个Task时,会使用黑名单机制,避免它被调度到上一次失败的节点上,起到一定的容错作用。黑名单记录Task上一次失败所在的Executor Id和Host,以及其对应的“拉黑”时间,“拉黑”时间是指这段时间内不要再往这个节点上调度这个Task了。