最近工作中遇到RM(ResourceManager)性能慢的问题,研究了一下RM的资源分配流程,因为内部流程还是比较复杂的,为了以后方便查阅,记录一下。在研究YARN容器的生命周期的过程中,从Applicaiton的提交,到最后的完成,整个流程中容器的分配和删除主要是ApplicaitonMaster发起的,由RM内部的Scheduler来管理分配。本篇文章还是聚焦在spark的executor的分配逻辑上, 关于YARN内部container的分配流程计划单独写一篇文章记录,这里就不过多介绍,简要的说明下YARN的RM内部的一些组件,对于后面executor的分配流程的理解会有帮助。
在RM内部主要是三个对外协议,
1. ApplicationClientProtocal 是负责跟client交互,client提交application, getApplicationReport/killApplication等. 对应RM中的组件是ClientRMService, 对应默认端口是 8032
2. ResourceTracker, nodeManager用来注册,和heatBeat的协议,在RM中的组件是ResourceTrackerService, 对应默认端口是 8031
3. ApplicationMasterProtocal, applicationMaster交互的协议,分有三个方法 registerApplicationMaster/finishApplicaitonMaster/allocate, 在RM中的组件是ApplicationMasterService, 对应的默认端口是 8030
我研究版本是基于spark3.1.1 以及hadoop cdh 2.6.0。
在spark3.1.1中,executor的申请我们生产上配置的是动态申请,管理executor申请的类是ExecutorAllocationManager,在SparkContext初始化的时候,就初始化好了,这个组件是在Spark的Driver端初始化的。
ExecutorAllocationManager里面包含了两个SparkListener, ExecutorAllocationListener 和ExecutorMonitor, 这两个listener监听spark RDD执行过程中的stage和task事件,汇总task和executor的信息。
ExecutorAllocationManager 在初始好之后,启动一个内部线程,定期的执行schedule方法,
在schedule方法内部会根据ExecutorMonitor计算executor是否timeout, 如果有timeout , 就会调用killExectors释放executor.
executor的timeout的计算逻辑是根据executor是否有task在运行,是否有cache rdd block或者cache shuffle data来判定的。 如果有task运行,不计算该executor的timeout, 否则计算cache shuffle data 和 cache rdd block的timeout时间,取两者最小的。
在shecule方法内部还有另外一个逻辑是处理updateAndSyncNumExecutorsTarget. 这个方法里会计算targetNum of executor,这个是spark程序运行当前需要的executor的总量。计算的依据就是当前pendingTask的数量,这里主要就是依赖ExecutorAllocationListener 了,ExecutorAllocationListener 会监听Spark作业运行过程中的事件,统计pendingTask的数量,相关事件处理逻辑如下:
- 当stageSubmitted的时候,会把stage的task数量存放到stageAttemptToNumTasks中
- 当stageCompleted的时候,会从stageAttemptToNumTasks中删除该stage
- 当taskStart的时候,会把stageAttemptToNumRunningTask +1, 并根据task是否是speculative,把taskIndex加入到stageAttemptToSpeculativeTaskIndices中 或 stageAttemptToTaskIndices中。
- 当taskEnd的时候,如果task是失败的,并且不是specilative的,会把taskindex从stageAttempttoTaskIndices中删除。
- 还有其他监听speculative task的事件,就不一一列举了。。。
当ExecutorAllocationManager计算maxNumExecutorsNeededPerResourceProfile的时候,maxNeeded = (pending + running) * executorAllocationRatio / taskPerExecutor, 其中
pending 是所有stageAttempt的 pendingTask + pendingSpeculativeTasks.
pendingTask = totalNumTask - numRunning(保存在stageAttempToTaskIndices中的taskindex的数量)
pendingSpeculativeTasks 是 numTotalTaskOfSpeculative – numRunningOfSpeculative.
这些数值都是在监听staage/task事件过程中更新的。
maxNeeded的第二种算法是 unschedulableTaskSets * executorAllocationRatio / tasksPerExecutor + executorMonitor.exe