YARN Container 启动流程分析

来源:互联网 发布:linux acl 编辑:程序博客网 时间:2024/06/05 14:36

YARN Container 启动流程分析

本文档从代码出发,分析了 YARN 中 Container 启动的整个过程,希望给出这个过程的一个整体的概念。

文档分为两个部分:第一部分是全局,从头至尾地把 Container 启动的整个流程串联起来;第二部分是细节,简要分析了 Container 启动流程中涉及到的服务、接口和类。

注意:

  • 基于 hadoop-2.6.0 的代码
  • 只写了与 Container 启动相关的逻辑,并且还大量忽略了很多细节,目的是为了得到一个整体的概念。
  • 为了让分析更具体,采用了这样的具体场景:
    • App 使用原生的 distributedShell
    • 调度器使用 FifoScheduler

第一部分:Container 启动流程

ApplicationMaster的主要逻辑

AM 与 NM 通信

AM 与 NM 们通过 NMClientAsync 通信,后者需要调用方提供一个回调类,NM 会在合适的时机调用回调类中的方法来通知 AM 。回调类 被AM实现为 NMCallbackHandler ,其中最重要的两个函数是:

  • onContainerStarted() ,当 NM 新启动了 Containers 时,会调用改方法,把 Container 列表传给它。
  • onContainerStopped() ,当 NM 停止了一些 Containers 时,会调用改方法,把 Container 列表传给它。

AM 与 RM 通信

AM 与 RM 通过 AMRMClientAsync 通信。

首先,通过 AMRMClientAsync.registerApplicationMaster() 向 RM 注册自己。

然后 AM 开始提交对 Container 的需求,在申请到需要数量的Container之前,先调用 setupContainerAskForRM() 设置对 Container 的具体需求(优先级、资源等),然后调用 AMRMClientAsync.addContainerRequest() 把需求提交给 RM ,最终该方法会把需求存到一个集合(AMRMClient.ask)里面。

AMRMClientAsync 同样需要调用方提供一个回调类,AM 实现为 RMCallbackHandler 。这个回调类主要实现了两个方法:

  • onContainersAllocated(), 获得新申请的 Container ,创建一个新线程,设置 ContainerLaunchContext , 最终调用 NMClientAsync.startContainerAsync() 来启动 Container。
  • onContainersCompleted(), 检查已完成的 Container 的数量是否达到了需求,没有的话,继续添加需求。

AM 需要与 RM 进行心跳,对于使用了 AMRMClientAsync 的 AM (如 DistributedShell ),心跳是通过 AMRMClientAsync 的一个线程实现。最终调用了 AMRMClientImpl.allocate() ,其主要动作就是从 ask 集合中拿到 Container 需求,组装成一个 AllocateRequest ,再通过 RPC 调用 RM 的相关方法进行申请。

具体一点,是通过ApplicationMasterProtocol.allocate() ,用 「Google protocol Buffer」格式进行 RPC 调用。

AM的三个主流程

总结上面说的,AM 有三个主要流程与 Container 的创建密切相关,这两个流程并行:

  1. 提交需求,通过心跳,把需求发送给 RM;
  2. 获取Container,通过心跳,拿到申请好的 Container;
  3. 每申请到一个 Container ,与 NM 通信,启动这个Container;

分析清楚了这三个主流程,也就清楚了 YARN Container 的启动逻辑。

Application 与 ResourceManager 的心跳

再看 RM 这边,在 AM 向 RM 注册时,RM 最终会生成一个代表这个 APP 的实例,我们先不分析注册的具体过程,只要知道在我们的情景下,最终是生成了一个 FicaSchedulerApp 。

AM 与 RM 进行心跳,发送的信息中含有:

  • AM 告诉 RM 两个信息: a) 自己对Container的要求,b) 已经用完的待回收的Container列表。
  • RM 给 AM 的回应:a) 新申请的 Container,b) 已经完成的 Container 的状态。

ApplicationMasterService 是 RM 的一个组成部分。RM启动时,会初始化这个服务,并根据配置,把相应的调度器 YarnScheduler 传进来。它实现了 ApplicationMasterProtocol 接口,负责对来自 AM 的 RPC 请求进行回应。在我们的情景中, ApplicationMasterService.allocate() 方法会被调用,核心逻辑是:

  • 触发 RMappAttemptStatusupdateEvent 事件。
  • 调用 YarnScheduler.allocate() 方法,把执行的结果封装起来返回。YarnScheduler 是与调度器通信的接口。所以,最后调用的是具体调度器的 allocate() 方法。

我们使用的是 FIFO 调度器,FifoScheduler.allocate() 方法的主要做两件事情:

  • 调用 FicaSchedulerApp.updateResourceRequests() 更新 APP (指从调度器角度看的 APP) 的资源需求。
  • 通过 FicaSchedulerApp.pullNewlyAllocatedContainersAndNMTokens() 把 FicaSchedulerApp.newlyAllocatedContainers 这个 List 中的Container取出来,封装后返回。

FicaSchedulerApp.newlyAllocatedContainers 这个数据结构中存放的,正是最近申请到的 Container 。那么,这个 List 中的元素是怎么来的呢,这要从 NM 的心跳说起。

NodeManager 与 ResourceManager 的心跳

NM 需要和 RM 进行心跳,让 RM 更新自己的信息。心跳的信息包含:

  • Request(NM->RM) : NM 上所有 Container 的状态,
  • Response(RM->NM) : 已待删除和待清理的 Container 列表

NM 启动时会向 RM 注册自己,RM 生成对应的 RMNode 结构,代表这个 NM ,存放了这个 NM 的资源信息以及其他一些统计信息。

负责具体心跳的,在 NM 这边是 NodeStatusUpdater 服务,在 RM 那边则是 ResourceTrackerService 服务。心跳的信息包括这个 NM 的状态,其中所有 Container 的状态等。

心跳最终通过 RPC 调用到了 ResourceTrackerService.nodeHeartbeat() 。其核心逻辑就是触发一个 RMNodeStatusEvent(RMNodeEventType.STATUS_UPDATE) 事件,这个事件由 NM 注册时生成的 RMNode 处理。

RMNode 接收 RMNodeStatusEvent(RMNodeEventType.STATUS_UPDATE) 消息,更新自己的状态机,然后调用 StatusUpdateWhenHealthyTransition.transition ,该方法从参数中获得这个 NM 所有的 Container 的信息,根据其状态分成两组:a) 刚申请到还未使用的,b) 运行完毕需要回收的,这两组 Container 的信息存放在 RMNode 的一个队列中。接着,发出一个消息: NodeUpdateSchedulerEvent(SchedulerEventType.NODE_UPDATE) 。这个消息,由调度器处理。

ResourceManager 处理NODE_UPDATE消息

RM 接收到 NM 的心跳后,会发出一个 SchedulerEventType.NODE_UPDATE 的消息,改消息由调度器处理。FifoScheduler 接收到这个消息后,调用了 FifoScheduler.nodeUpdate() 方法。与 Container 申请相关的主要逻辑如下:

获取已申请到的

从 RMNode 中获取出那些「刚申请还未使用」的 Container (NM 与 RM 心跳是获得),发出消息:RMContainerEventType.LAUNCHED,该消息由 RMContainer 处理;

回收已完成的

从 RMNode 中获取出那些「已经使用完待回收」的 Container,进行回收(具体回收过程略);

申请新的

在这个 NM 上申请新的 Container:

  • 通过 FicaSchedulerApp.getResourceRequest() 拿到资源请求(ResourceRequest)
  • 计算可申请的资源,调用 FicaSchedulerApp.allocate(),根据传进来的参数,封装出一个 RMContainer 添加到 newlyAllocatedContainers 中。然后触发事件 RMContainerEventType.START。该事件之后会由 RMContainer 处理。
  • 调用 FicaSchedulerNode.allocateContainer()

RMContainer 对 RMContainerEventType 事件进行处理处理:

  • RMContainerEventType.START : 状态从 NEW 变为 ALLOCATED,最终触发事件 RMAppAttemptEvent(type=CONTAINER_ALLOCATED), 改事件由 RMAppAttemptImpl 处理。
  • RMContainerEventType.LAUNCHED : 状态从 ACQUIED 变为 RUNNING 。

RMAppAttemptImpl 对 RMAppAttemptEvent 事件进行处理,该事件告诉就是告诉 AppAttempt ,你这个APP有 Container 申请好了,AppAttempt 检查自己的状态,如果当前还没有运行 AM ,就把这个 Container 拿来运行 AM。

到此,我们已经理清楚了 FicaSchedulerApp.newlyAllocatedContainers 中元素的来源,也就理清楚了,AM 与 RM 心跳中获得的那些「新申请」的 Container 的来源。

ApplicationMaster 与 NodeManager 通信启动 Container

回顾一下:

AM的三个主流程

总结上面说的,AM 有三个主要流程与 Container 的创建密切相关,这两个流程并行:

  1. 提交需求,通过心跳,把需求发送给 RM;
  2. 获取Container,通过心跳,拿到申请好的 Container;
  3. 每申请到一个 Container ,与 NM 通信,启动这个Container;

基于上面的分析,第1,2两个流程已经清楚。下面我们来具体看看 NM 具体是怎么启动一个 Container的。

AM 设置好 ContainerLaunchContext , 调用 NMClientAsync.startContainerAsync() 启动Container。

NMClientAsync 中有一个名叫 events 的事件队列,同时,NMClientAsync 还启动这一个线程,不断地从 events 中取出事件进行处理。

startContainerAsync() 方法被调用时,会生成一个 ContainerEvent(type=START_CONTAINER) 事件放入 events 队列。对于这个事件,处理逻辑是调用 NMClient.startContainer() 同步地启动 Container ,然后调用回调类中的 onContainerStarted() 方法。

NMClient 最终会调用 ContainerManagementProtocol.startContainers() ,以 Google Protocol Buffer 格式,通过 RPC 调用 NM 的对应方法。NM 处理后会返回成功启动的 Container 列表。

NodeManager 中启动 Container

ContainerManagerImpl

NM 中负责响应来自 AM 的 RPC 请求的是 ContainerManagerImpl ,它是 NodeManager 的一部分,负责 Container 的管理,在 Nodemanager 启动时,该服务被初始化。该类实现了接口 ContainerManagementProtocol ,接到 RPC 请求后,会调用 ContainerManagerImpl.startContainers() 。改函数的基本逻辑是:

  1. 首先进行 APP 的初始化(如果还没有的话),生成一个 ApplicationImpl 实例,然后根据请求,生成一堆 ContainerImpl 实例
  2. 触发一个新事件:ApplicationContainerInitEvent ,之前生成的 ApplicationImpl 收到改事件,又出发一个 ContainerEvent(type=INIT_CONTAINER) 事件,这个事件由 ContainerImpl 处理
  3. ContainerImpl 收到事件, 更新状态机,启动辅助服务,然后触发一个新事件 ContainersLaucherEvent(type=LAUNCH_CONTAINER) ,处理这个事件的是 ContainersLauncher 。

ContainerLauncher 是 ContainerManager 的一个子服务,收到 ContainersLaucherEvent(type=LAUNCH_CONTAINER) 事件后,组装出一个 ContainerLaunch 类并使用 ExecutorService 执行。

ContainerLaunch 类负责一个 Container 具体的 Lanuch 。基本逻辑如下:

  • 设置运行环境,包括生成运行脚本,Local Resource ,环境变量,工作目录,输出目录等
  • 触发新事件 ContainerEvent(type=CONTAINER_LAUNCHED),该事件由 ContainerImpl 处理。
  • 调用 ContainerExecutor.launchContainer() 执行 Container 的工作,这是一个阻塞方法。
  • 执行结束后,根据执行的结果设置 Container 的状态。

ContainerExecutor

ContainerExecutor 是 NodeManager 的一部分,负责 Container 中具体工作的执行。该类是抽象类,可以有不同的实现,如 DefaultContainerExecutor ,DockerContainerExecutor ,LinuxContainerExecutor 等。根据 YARN 的配置,NodeManager 启动时,会初始化具体的 ContainerExecutor 。

ContainerExecutor 最主要的方法是 launchContainer() ,该方法阻塞,直到执行的命令结束。

DefaultContainerExecutor 是默认的 ContainerExecutor ,支持 Windows 和 Linux 。它的 launchContainer() 的逻辑是:

  • 创建 Container 需要的目录
  • 拷贝 Token、运行脚本到工作目录
  • 做一些脚本的封装,然后执行脚本,返回状态码

至此,Container 在 NM 中已经启动,AM 中 NMCallback 回调类中的 onContainerStarted() 方法被调用。

第二部分:各部分代码分析

NMClientAsync

AM 异步与 NM 通信的类,使用方法见注释:

/** * <code>NMClientAsync</code> handles communication with all the NodeManagers * and provides asynchronous updates on getting responses from them. It * maintains a thread pool to communicate with individual NMs where a number of * worker threads process requests to NMs by using {@link NMClientImpl}. The max * size of the thread pool is configurable through * {@link YarnConfiguration#NM_CLIENT_ASYNC_THREAD_POOL_MAX_SIZE}. * * It should be used in conjunction with a CallbackHandler. For example * * <pre> * {@code * class MyCallbackHandler implements NMClientAsync.CallbackHandler { *   public void onContainerStarted(ContainerId containerId, *       Map<String, ByteBuffer> allServiceResponse) { *     [post process after the container is started, process the response] *   } * *   public void onContainerStatusReceived(ContainerId containerId, *       ContainerStatus containerStatus) { *     [make use of the status of the container] *   } * *   public void onContainerStopped(ContainerId containerId) { *     [post process after the container is stopped] *   } * *   public void onStartContainerError( *       ContainerId containerId, Throwable t) { *     [handle the raised exception] *   } * *   public void onGetContainerStatusError( *       ContainerId containerId, Throwable t) { *     [handle the raised exception] *   } * *   public void onStopContainerError( *       ContainerId containerId, Throwable t) { *     [handle the raised exception] *   } * } * } * </pre> * * The client's life-cycle should be managed like the following: * * <pre> * {@code * NMClientAsync asyncClient = *     NMClientAsync.createNMClientAsync(new MyCallbackhandler()); * asyncClient.init(conf); * asyncClient.start(); * asyncClient.startContainer(container, containerLaunchContext); * [... wait for container being started] * asyncClient.getContainerStatus(container.getId(), container.getNodeId(), *     container.getContainerToken()); * [... handle the status in the callback instance] * asyncClient.stopContainer(container.getId(), container.getNodeId(), *     container.getContainerToken()); * [... wait for container being stopped] * asyncClient.stop(); * } * </pre> */

回调类 CallbackHandler

调用方提供的回调类。

Container 事件队列 BlockingQueue<ContainerEvent> events

一个名为 events 的队列,存放 Container 的事件。入队由方法 startContainerAsync() 和 stopContainerAsync() 负责,出队由下面说的「Container 事件执行线程」负责。

Container Map ConcurrentMap<ContainerId, StatefulContainer> containers

存放 Container 的 Map。新增由方法 startContainerAsync() 负责,删除由下面说的「Container 事件执行线程」负责。

Container 事件执行线程

该线程不断地从 events 队列中取出事件,根据事件类型做不同的操作,主要的两个操作是:

  • 事件类型是 ContainerEventType.START_CONTAINER,调用 NMClient.startContainer() 同步地启动 Container ,然后调用回调类中的 onContainerStarted() 方法,如果 Container 状态已经完成,则从 containers 中删除。
  • 事件类型是 ContainerEventType.STOP_CONTAINER,调用 NMClient.stopContainer() 同步地停止 Container ,然后调用回调类中的 onContainerStopped() 方法,如果 Container 状态已经完成,则从 containers 中删除。

startContainerAsync()

异步地启动一个 Container 。首先把参数中的 Container 放入 Map containers 中,然后往 events 队列添加一个事件:ContainerEvent(type=START_CONTAINER)

stopContainerAsync()

异步地停止一个 Container 。往 evnets 队列中添加一个事件 ContainerEvent(type=STOP_CONTAINER)

NMClient

真正负责与 NM 通信的类。

Container Map ConcurrentMap<ContainerId, StartedContainer> startedContainers

名为 startedContainers 的 Map ,存放启动了的 Container 。

startContainer()

同步地启动一个 Container 。首先把 Container 添加到 startedContainers 中,然后调用 ContainerManagementProtocol.startContainers() 。

AM 必须提供 Container 启动需要的所有信息,包括 ID,Node Id,Token 以及 ContainerLaunchContext 等。

NM 则返回成功启动的 Container 列表。

stopContainer()

同步地停止一个 Container , 先调用 ContainerManagementProtocol.stopContainers() 停止 Container, 然后从 startedContainers 中删除。

ContainerManagementProtocol

AM 与 NM 直接的通信接口,通过改接口,可以启动、停止 Container ,获取 Container 状态等等。

startContainers() 和 stopContainers()

基于 Google Protocol Buffer 协议,通过 RPC 调用 NM 中对应的方法,实现 Container 的启动。 NM 中对应的方法是 ContainerManagerImpl.startContainers() 和 ContainerManagerImpl.stopContainers() 。

AM 或者说 NMClient 提供需要启动或停止的 Container 列表(如果是启动的话还需要提供具体的启动信息),NM 则返回成功启动或停止的 Container 列表。

ContainerManagerImpl

NodeManager 的一部分,负责 Container 的管理,在 Nodemanager 启动时,该服务被初始化。

该类实现了接口 ContainerManagementProtocol ,它对来自于 AM (具体是来自于NMClient)的 RPC 调用进行回应。

startContainers()

启动传入的 Container 列表。主要逻辑是:

  1. 拿到 ContainerLaunchContext ,创建出 ContainerImpl
  2. 如果这个 APP 在 NM 中还没有实例, 则触发一个 ApplicationInitEvent 事件进行 APP 的初始化,这里不详细分析,最终是为 APP 生成了一个 ApplicationImpl 实例。
  3. 触发一个 ApplicationContainerInitEvent 事件,该事件由 ApplicationImpl 处理。

ApplicationContainerInitEvent

初始化一个 Container 的事件,改事件由 ContainerManager 触发,由 ApplicationImpl 处理。事实上是封装了这个事件:ApplicationEvent(type=INIT_CONTAINER)

具体参考注释:

/** * Event sent from {@link ContainerManagerImpl} to {@link ApplicationImpl} to * request the initialization of a container. This is funneled through * the Application so that the application life-cycle can be checked, and container * launches can be delayed until the application is fully initialized. * * Once the application is initialized, * {@link ApplicationImpl.InitContainerTransition} simply passes this event on as a * {@link ContainerInitEvent}. * */

ApplicationImpl

NM 视角下的一个 Application 。

Container Map Map<ContainerId, Container> containers

存放该 APP 的 Container 。

事件处理

处理 ApplicationEvent 事件,根据事件的类型,几个主要的处理逻辑如下:

  • INIT_CONTAINER :如果 APP 已经完成初始化,就触发一个新事件 ContainerEvent(type=INIT_CONTAINER) ,该事件由 ContainerImpl 处理。
  • INIT_APPLICATION :初始化 ApplicationImpl 的一些元素和服务。

ContainerImpl

NodeManager 视角下的一个 Container 。该类的实例由 AM 通过 RPC 调用 ContainerManagerImpl.startContainers() 方法创建。

事件处理

处理 ContainerEvent 事件,根据事件类型,几个主要的处理逻辑如下:

INIT_CONTAINER

注释如下:

* State transition when a NEW container receives the INIT_CONTAINER* message.** If there are resources to localize, sends a* ContainerLocalizationRequest (INIT_CONTAINER_RESOURCES)* to the ResourceLocalizationManager and enters LOCALIZING state.** If there are no resources to localize, sends LAUNCH_CONTAINER event* and enters LOCALIZED state directly.** If there are any invalid resources specified, enters LOCALIZATION_FAILED* directly.

主要逻辑就是:

  • 跟新状态机
  • 通过触发辅助服务事件 AuxServicesEvent 启动辅助服务(不仔细分析)
  • 如果有本地资源需求,获取本地资源(不仔细分析)
  • 如果没有本地资源需求,触发一个新事件 ContainersLaucherEvent(type=LAUNCH_CONTAINER) ,该事件由 ContainersLauncher 处理。

ContainerLauncher

ContainerLauncher 是 ContainerManager 的一个子服务,负责 Launch Container 。该服务处理 ContainersLauncherEvent 事件。

根据事件类型,主要处理逻辑是:

  • LAUNCH_CONTAINER :组装出一个 ContainerLaunch 类并使用 ExecutorService 执行。

ContainerLaunch

负责一个 Container 具体的 Lanuch 。基本逻辑如下:

  • 设置运行环境,包括生成运行脚本,Local Resource ,环境变量,工作目录,输出目录等
  • 触发新事件 ContainerEvent(type=CONTAINER_LAUNCHED),该事件由 ContainerImpl 处理。
  • 调用 ContainerExecutor.launchContainer() 执行 Container 的工作,这是一个阻塞方法。
  • 执行结束后,根据执行的结果设置 Container 的状态。

ContainerExecutor

ContainerExecutor 是 NodeManager 的一部分,负责 Container 中具体工作的执行。该类是抽象类,可以有不同的实现,如 DefaultContainerExecutor ,DockerContainerExecutor ,LinuxContainerExecutor 等。根据 YARN 的配置,NodeManager 启动时,会初始化具体的 ContainerExecutor , 默认是 DefaultContainerExecutor 。

最主要的方法是 launchContainer() ,改方法阻塞,直到执行的命令结束。

DefaultContainerExecutor

默认的 ContainerExecutor ,支持 Windows 和 Linux 。

launchContainer()

  • 创建 Container 需要的目录
  • 拷贝 Token、运行脚本到工作目录
  • 做一些脚本的封装,然后执行脚本,返回状态码

AMRMClientAsync

AMRMClient 的异步版本。

用法见注释:

/** * <code>AMRMClientAsync</code> handles communication with the ResourceManager * and provides asynchronous updates on events such as container allocations and * completions.  It contains a thread that sends periodic heartbeats to the * ResourceManager. * * It should be used by implementing a CallbackHandler: * <pre> * {@code * class MyCallbackHandler implements AMRMClientAsync.CallbackHandler { *   public void onContainersAllocated(List<Container> containers) { *     [run tasks on the containers] *   } * *   public void onContainersCompleted(List<ContainerStatus> statuses) { *     [update progress, check whether app is done] *   } * *   public void onNodesUpdated(List<NodeReport> updated) {} * *   public void onReboot() {} * } * } * </pre> * * The client's lifecycle should be managed similarly to the following: * * <pre> * {@code * AMRMClientAsync asyncClient = *     createAMRMClientAsync(appAttId, 1000, new MyCallbackhandler()); * asyncClient.init(conf); * asyncClient.start(); * RegisterApplicationMasterResponse response = asyncClient *    .registerApplicationMaster(appMasterHostname, appMasterRpcPort, *       appMasterTrackingUrl); * asyncClient.addContainerRequest(containerRequest); * [... wait for application to complete] * asyncClient.unregisterApplicationMaster(status, appMsg, trackingUrl); * asyncClient.stop(); * } * </pre> */

回应队列responseQueue

一个名为 responseQueue 的队列(LinkedBlockingQueue<AllocateResponse> responseQueue),存放从 RM 获得的回应。

心跳线程 HeartbeatThread

与 RM 进行心跳的线程。改线程根据设置的心跳间隔,循环地调用 AMRMClient.allocate() 方法与 RM 进行通信,得到 RM 的回应后,把回应放到 responseQueue 队列中。

回调类工作线程 CallbackHandlerThread

在 AMRMClientAsync 的的构造方法中,使用方必须提供一个「回调类」。该线程循环从 responseQueue 队列中获取来自 RM 的回应,根据回应的类型,调用回调类中相应的方法。比如当发现回应中包含有「已经分配好的 Container」,就调用回调类中的 onContainerAllocated() 方法。

registerApplicationMaster()

调用方(AM)初始化 AMRMClientAsync 后,首先要通过改方法注册自己。
该方法中,调用 AMRMClient.registerApplicationMaster() 进行真正的注册,然后开启心跳线程
HeartbeatThread。

addContainerRequest()

调用链是: AMRMClientImpl.addContainerRequest() -> AMRMClientImpl.addResourceRequest() ->
addResourceRequestToAsk() , 最终会把对 Container 的要求加到一个类型为 ContainerRequest 的集合里面,该集合的名字,叫 ask

AMRMClient

实际与 RM 进行心跳通信的类。

一个名为 ask 的集合,保存着 AM 对 Container 的需求, 这些需求通过 addResourceRequest() 方法添加到该集合中。

每次心跳的执行方法是 allocate() ,其注释如下:

* Request additional containers and receive new container allocations.* Requests made via <code>addContainerRequest</code> are sent to the* <code>ResourceManager</code>. New containers assigned to the master are* retrieved. Status of completed containers and node health updates are also* retrieved. This also doubles up as a heartbeat to the ResourceManager and* must be made periodically. The call may not always return any new* allocations of containers. App should not make concurrent allocate* requests. May cause request loss.

该方法的具体实现是 AMRMClientImpl.allocate() ,主要逻辑是从 ask 集合中拿到 Container 需求,组装
成一个 AllocateRequest ,最终调用 ApplicationMasterProtocol.allocate() 申请 Container 。

ApplicationMasterProtocol

AM 与 RM 通信接口。

其中最重要的方法是 allocate() ,该方法同时也承担着 AM 与 RM 心跳的责任。该方法的主要逻辑,就是把
AllocateRequest 转换为「Google protocol Buffer」格式,通过 RPC 调用传到 RM 。在 RM 一端,则是由
ApplicationMasterService 接收消息。该类同样也是实现了接口 ApplicationMasterProtocol ,所以这时 ApplicationMasterService.allocate() 会被执行。

传递的与 Container 有关的信息:

  • Request(AM->RM) : Container 的需求列表,待回收的 Container 列表
  • Response(RM->AM) : 新申请的 Container,可用的资源,已经完成的 Container 的状态

ApplicationMasterService

ApplicationMasterService 是 RM 的一个组成部分。它实现了 ApplicationMasterProtocol 接口,负责对来自 AM 的 RPC 请求进行回应。

RM启动时,会初始化 ApplicationMasterService ,并根据配置,把相应的调度器 YarnScheduler 传进来。

allocate()

核心逻辑是:

  • 发出 RMappAttemptStatusupdateEvent 的消息。
  • 调用 YarnScheduler.allocate() 方法,把执行的结果封装起来返回。YarnScheduler 是与调度器通信的接口。所以,最后调用的是具体调度器的 allocate() 方法。

FicaSchedulerApp

表示在 FIFO|Capacity 调度器视角下的一个「Application Attempt」,继承自
SchedulerApplicationAttempt 。

APP 调度相关信息 AppSchedulingInfo

这个类跟踪一个 APP 调度相关的信息,包括 Container 需求列表,优先级等等。

updateResourceRequests()

更新位于 AppSchedulingInfo 中的 Container 需求列表。

getResourceRequest()

获取位于 AppSchedulingInfo 中的 Container 需求列表。

新申请的 Container 列表 newlyAllocatedContainers

List<RMContainer> newlyAllocatedContainers ,里面存放的就是新近申请到的 Container 。List 元素的增加和获取,主要分别通过下面两个方法实现。

pullNewlyAllocatedContainersAndNMTokens()

基本上就是从 newlyAllocatedContainers 中把 Container 取出来,封装后返回。

allocate()

基本上就是根据传进来的参数,封装出一个 RMContainer 添加到 newlyAllocatedContainers 中。然后发出
一个 RMContainerEventType.START 的消息,该消息之后会由 RMContainer 处理。

FicaSchedulerNode

继承自 SchedulerNode 表示在 FIFO|Capacity 调度器视角下的一个 Node Manager 。

launchedContainers

Map<ContainerId, RMContainer> launchedContainers ,存放 Launched 的 Container 。

allocateContainer()

把 RMContainer 添加到 launchedContainers 中。

ResourceTrackerService

与 NM 进行心跳。

nodeHeartbeat()

接收来自 NM 的 RPC 心跳请求。核心逻辑就是触发出一个「更新 NM 信息」事件 :RMNodeStatusEvent(RMNodeEventType.STATUS_UPDATE) ,这个消息由 RMNode处理。

RMNode

表示在 RM 的视角下的一个 NM ,存放了这个 NM 的资源信息以及其他一些统计信息。

RMNode 接收 RMNodeStatusEvent(RMNodeEventType.STATUS_UPDATE) 消息,主要处理逻辑是:

  • 更新自己的状态机
  • 从参数中获得这个 NM 所有的 Container 的信息,根据其状态分成两组:a) 刚申请到还未使用的,b) 运行完毕需要回收的,这两组 Container 的信息存放在 RMNode 的一个队列中。接着,发出一个消息: NodeUpdateSchedulerEvent(SchedulerEventType.NODE_UPDATE) 。改消息会由具体的调度器处理。

RMContainer

在 RM 视角下的某个 APP 的 Container。维护了一个状态机,处理各种发给自己代表的 Container 的 RMContainerEvent 事件。

RMContainer 对 RMContainerEventType 事件进行处理处理,根据事件类型,部分处理逻辑如下:

  • RMContainerEventType.START : 状态从 NEW 变为 ALLOCATED,最终触发事件 RMAppAttemptEvent(type=CONTAINER_ALLOCATED), 该事件由 RMAppAttemptImpl 处理。
  • RMContainerEventType.LAUNCHED : 状态从 ACQUIED 变为 RUNNING 。

RMAppAttemptImpl 对 RMAppAttemptEvent 事件进行处理,该事件就是告诉 AppAttempt ,你这个APP有 Container 申请好了,AppAttempt 检查自己的状态,如果当前还没有运行 AM ,就把这个 Container 拿来运行 AM。

FifoScheduler

FIFO调度器,继承和接口关系是:

interface YarnSchedulerinterface ResourceScheduler extends YarnSchedulerabstract class AbstractYarnScheduler implements ResourceSchedulerclass FifoScheduler extends AbstractYarnScheduler

allocate()

来自 AMRMClient(或者说来自 AM )的 Container 分配申请最终会传到这里。主要逻辑是:

  • 通过 FicaSchedulerApp.updateResourceRequests() 更新 APP (指从调度器角度看的 APP) 的资源需求。
  • 通过 FicaSchedulerApp.pullNewlyAllocatedContainersAndNMTokens() 把新近申请到的 Containner 拿到。核心逻辑是把 FicaSchedulerApp.newlyAllocatedContainers 中的Container取出来。
  • 把新申请到的 Container 组装到一个 Allocation 类中返回。

nodeUpdate()

处理 SchedulerEventType.NODE_UPDATE 消息:

  • 经过 AbstractYarnScheduler.containerLaunchedOnNode() -> SchedulerApplicationAttempt.containerLaunchedOnNode(),发出消息:RMContainerEventType.LAUNCHED,该消息由 RMContainer 处理;
  • 从 RMNode 中获取出那些「已经使用完待回收」的 Container,进行回收(具体回收过程略);
  • 经过 assignContainers() -> assignContainersOnNode() -> assignOffSwitchContainers() -> assignOffSwitchContainers() -> assignContainer() 等函数调用,在这个 NM 上申请新的 Container 。整个过程的主要逻辑是:
    • 通过 FicaSchedulerApp.getResourceRequest() 拿到资源请求(ResourceRequest)
    • 计算可用资源,调用 FicaSchedulerApp.allocate(),基本上就是根据传进来的参数,封装出一个 RMContainer 添加到 newlyAllocatedContainers 中。然后发出一个 RMContainerEventType.START 的消息,该消息之后会由 MContainer 处理。
    • 调用 FicaSchedulerNode.allocateContainer()`

distributedshell.ApplicationMaster

参见注释:

 * The <code>ApplicationMaster</code> needs to send a heartbeat to the * <code>ResourceManager</code> at regular intervals to inform the * <code>ResourceManager</code> that it is up and alive. The * {@link ApplicationMasterProtocol#allocate} to the <code>ResourceManager</code> from the * <code>ApplicationMaster</code> acts as a heartbeat. * * <p> * For the actual handling of the job, the <code>ApplicationMaster</code> has to * request the <code>ResourceManager</code> via {@link AllocateRequest} for the * required no. of containers using {@link ResourceRequest} with the necessary * resource specifications such as node location, computational * (memory/disk/cpu) resource requirements. The <code>ResourceManager</code> * responds with an {@link AllocateResponse} that informs the * <code>ApplicationMaster</code> of the set of newly allocated containers, * completed containers as well as current state of available resources. * </p> * * <p> * For each allocated container, the <code>ApplicationMaster</code> can then set * up the necessary launch context via {@link ContainerLaunchContext} to specify * the allocated container id, local resources required by the executable, the * environment to be setup for the executable, commands to execute, etc. and * submit a {@link StartContainerRequest} to the {@link ContainerManagementProtocol} to * launch and execute the defined commands on the given allocated container. * </p> *
4 0
原创粉丝点击