Yarn作业生命周期分享

App生命周期分享

本文主要对App在Yarn状态机中的流转过程进行分析。

由于涉及的状态机较多,事件流转颇为复杂,故将全流程分为三个阶段:

  1. App初始化,即:客户端提交任务,经过Router转发至RM,在RM端进行RMApp、RMAppAttempt状态机的初始化,并添加到调度器,为AM申请资源。
  2. App启动,即:调度器为AM调度资源,初始化RMContainer状态机,在NM端启动Application、Container。并进行资源本地化,启动Container。
  3. App结束,即:AMContainer正常运行结束,在NM端更新状态并清理资源,并通过AM协议与节点心跳汇报给RM端,更新状态并清理资源,结束所有状态机的流转。

大致思路整合在三张图上,见上述每个阶段的大标题。

本文仅讨论【执行符合预期】的App,即App的运行不出现异常。

由于App全生命周期链路过长,故本次分享主要专注在全局的流程与思路上,即状态机的流转,以及状态机流转依赖的核心组件,不过多涉及实现细节与代码逻辑。

对细节理解有不足之处,正好也请大家讨论补充。

一 App初始化

在这里插入图片描述
客户端提交App,落到ApplicationClientProtocol的submitApplication()方法上。

1 转发与拦截

1.1 Router转发 & FederationClientInterceptor拦截

一句话概括:Router作为门面,需要把App请求转给相应的集群RM。

协议的实现类:RouterClientRMService。线上的代码应该是使用Router转发的。

@Override
public SubmitApplicationResponse submitApplication(
    SubmitApplicationRequest request) throws YarnException, IOException {
  // 适配器模式?里面先初始化一个责任链ClientRequestInterceptor。责任链的Pipeline是在配置中配的。在router中找到该配置:
  // <name>yarn.router.clientrm.interceptor-class.pipeline</name>
  // <value>org.apache.hadoop.yarn.server.router.clientrm.FederationClientInterceptor</value>
  // 后面初始化,配了一些诸如重试次数之类的参数。
  // 然后把ClientRequestInterceptor塞到Wrapper里面。
  // 这个拦截器也实现了ApplicationClientProtocol接口。
  RequestInterceptorChainWrapper pipeline = getInterceptorChain();
 // ClientRequestInterceptor接口,继承了ApplicationClientProtocol接口。
  // 也就是Router对协议作了一个转发。
  return pipeline.getRootInterceptor().submitApplication(request);
}

1.2 找到subCluster

一句话概括:Router从router-policy.xml中读文件,找到App -> Queue -> Cluster

而FederationClientInterceptor又做了什么?先是检验了一下queue的格式,然后开一个for循环重试。

每个循环中,从getQueueHomeSubcluster中拿queue配置的subCluster,是通过FederationStateStoreFacade。

其实这部分使用的不是zk,而是CompositeFederationStateStore,将请求转发给了XMLPolicyConfigStateStore。XMLPolicyConfigStateStore读取的是router-policy.xml。具体的实现类配置在这:

<property>
<name>yarn.federation.state-store.class</name>
<value>org.apache.hadoop.yarn.server.federation.store.impl.CompositeFederationStateStore</value>
<source>yarn-site.xml</source>
</property>

在XMLPolicyConfigStateStore中,根据router-policy的配置来找到queue对应的subCluster。

2 与RM建立连接

一句话概括:Router -> RM。底层代理逻辑实现

找到SubCluster后,终于要提交Application了。构建一个ApplicationClientProtocol clientRMProxy,调用getClientRMProxyForSubCluster方法。

方法内部,先用federationFacade检验下subClusterId,再看看缓存Map<SubClusterId**,** ApplicationClientProtocol> clientRMProxies中有没有。没有的话,使用FederationProxyProviderUtil.createRMProxy实例化。

转了一连串,转到了RMProxy,RetryProxy。这部分细节暂时不看。【todo】

创建完成后,把它放进缓存。

Yarn Federation源码串读

使用clientRMProxy提交app,提交到ResourceManager端。

3 RM处理App

一句话概括:ClientRMService接收请求,处理,构造RMAppImpl,放在Map里。

resourcemanager包中的ClientRMService作为ApplicationClientProtocol的实现类,提交任务。在方法里,一直构造ApplicationSubmissionContext submissionContext这个对象,往里面塞参数。随后调用rmAppManager的submitApplication方法。这个对象在初始化ClientRMService时就已经被塞进来了。

RMAppImpl application = createAndPopulateNewRMApp(submissionContext, submitTime, user, false);

它里面有个RMContext,实现类是RMContextImpl,里面又有一个RMActiveServiceContext activeServiceContext,里面存了一堆东西

private final ConcurrentMap<ApplicationId, RMApp> applications = new ConcurrentHashMap<>();
private final ConcurrentMap<NodeId, RMNode> nodes = new ConcurrentHashMap<>();
private final ConcurrentMap<String, RMNode> inactiveNodes = new ConcurrentHashMap<>();

其中我们的RMApp被提交后,就存在这里面。

RMAppManager的提交过程,其实就是看需不需要安全认证。需要的话就比较麻烦,用DelegationTokenRenewer的addApplicationAsync方法搞,还得起一个DelegationTokenRenewerRunnable线程,在这里面处理。安全不通过就拒绝,RMAppEventType.APP_REJECTED,否则成功,RMAppEventType.START。不需要安全认证的话,直接RMAppEventType.START

RMAppState.NEW, RMAppState.NEW_SAVING, RMAppEventType.START, new RMAppNewlySavingTransition()

这个里面要把RMAppImpl落地到RMStateStore中,RMStateStore#storeApplication,构造了ApplicationStateData实例,并触发了RMStateStoreAppEvent,类别为RMStateStoreEventType.STORE_APP

RMStateStoreState.ACTIVE, EnumSet.of(RMStateStoreState.ACTIVE, RMStateStoreState.FENCED), RMStateStoreEventType.STORE_APP, new StoreAppTransition()

4 & 5 存储到StateStore

一句话概括:存到StateStore(ZK)中。

在StoreAppTransition()中,若开启恢复(yarn.resourcemanager.recovery.enabled),调用storeApplicationStateInternal(ApplicationId appId, ApplicationStateData appStateData),这是个骨架方法。

我们的实现类是zk。在yarn.resourcemanager.store.class配置中决定。ZKRMStateStore实现该方法。在yarn.resourcemanager.zk-state-store.parent-path中配置zk内部的存储路径。查看zk中的数据:ls /rmstore-2.7.1-rm01/ZKRMStateRoot/RMAppRoot,里面都是app。方法中,调用zk原生api重试存储app数据。随后埋点上传监控数据。

存储完之后,发出RMAppEventType.APP_NEW_SAVED事件。

6 添加App调度

一句话概括:FairScheduler接受App,创一个SchedulerApplication(里面的FSAppAttempt是空的)

RMAppState.NEW_SAVING, RMAppState.SUBMITTED, RMAppEventType.APP_NEW_SAVED, new AddApplicationToSchedulerTransition()

在AddApplicationToSchedulerTransition中,顾名思义,将app加入调度器,提交了一个AppAddedSchedulerEvent事件,类型为SchedulerEventType.APP_ADDED

在FairScheduler中,通过switch-case对事件进行处理,调用addApplication方法。

  • check队列,不符合格式,RMAppEventType.APP_REJECTED
  • 调用assignToQueue,把app分到指定的队列上,分不成功拒绝。怎么分?其实就rmApp.setQueue了一下。
  • 如果不是Recover的app,就需要进行acl检查,检查不成功拒绝。
  • SchedulerApplication application = new SchedulerApplication(queue**,** user**,** priority**,** rmApp.getNodeConstraints());
  • 放进ConcurrentMap<ApplicationId**,** SchedulerApplication> applications

最后发送RMAppEventType.APP_ACCEPTED事件。

埋点,记录App添加事件。

7 添加App调度成功

一句话概括:FairScheduler接受App后返回,RMApp处理,创RMAppAttempt实例。

RMAppState.SUBMITTED, RMAppState.ACCEPTED, RMAppEventType.APP_ACCEPTED, new StartAppAttemptTransition()

首先createNewAttempt(),创建一个RMAppAttempt实例。

放到private final Map<ApplicationAttemptId**,** RMAppAttempt> attempts = new LinkedHashMap<>();中

这里面涉及yarn.resourcemanager.am.max-attempts配置,也就是最大重试次数。

创建完成后,发送RMAppAttemptEventType.START事件。

8 创建AppAttempt

一句话概括:将AppAttempt注册到ApplicationMasterService中

RMAppAttemptState.NEW, RMAppAttemptState.SUBMITTED, RMAppAttemptEventType.START, new AttemptStartedTransition()

将AppAttempt注册到ApplicationMasterService中。

这部分重点关注一下,也就是AllocateResponse的作用。这个应该是留着后面使用,保留AppAttempt -> AllocateResponse的map关系。

public void registerAppAttempt(ApplicationAttemptId attemptId) {
  AllocateResponse response =
      recordFactory.newRecordInstance(AllocateResponse.class);
  // set response id to -1 before application master for the following
  // attemptID get registered
  response.setResponseId(-1);
  LOG.info("Registering app attempt : " + attemptId);
  responseMap.put(attemptId, new AllocateResponseLock(response));
  rmContext.getNMTokenSecretManager().registerApplicationAttempt(attemptId);
}

最后,发送SchedulerEventType.APP_ATTEMPT_ADDED事件,再次转到调度器侧。

9 添加AppAttempt调度

一句话概括:将AppAttempt加到调度器侧:创建FSAppAttempt这个核心数据结构,并与FSLeafQueue关联起来。

和添加App调度相似。也有埋点。进入到内部:

protected void addApplicationAttempt(
    ApplicationAttemptId applicationAttemptId,
    boolean transferStateFromPreviousAttempt, boolean isAttemptRecovering) {
  lock.lock();
  try {
    // 从之前的map中取出SchedulerApplication<FSAppAttempt> application。
    SchedulerApplication<FSAppAttempt> application =
        applications.get(applicationAttemptId.getApplicationId());
    String user = application.getUser();
    FSLeafQueue queue = (FSLeafQueue) application.getQueue();
// 实例化FSAppAttempt

最终发送RMAppAttemptEventType.ATTEMPT_ADDED事件。

10 & 11 申请资源

一句话概括:RMAppAttempt被加到调度器了,但只是进去。所以开始申请具体的资源。

RMAppAttemptState.SUBMITTED, EnumSet.of(RMAppAttemptState.LAUNCHED_UNMANAGED_SAVING, RMAppAttemptState.SCHEDULED)**,
**RMAppAttemptEventType.ATTEMPT_ADDED, new ScheduleTransition()

逻辑转回RMAppAttempt侧。在这个状态转移函数中,有两种情况。如果开启了UnmanagedAM,则落地到StateStore,然后状态转移为RMAppAttemptState.LAUNCHED_UNMANAGED_SAVING

UnmanagedAM是什么?参考文档:http://dongxicheng.org/mapreduce-nextgen/yarn-unmanaged-am-implemention/ 具体使用暂待调研。

如果未开启,则正常逻辑往下走。

  • 埋点,addAMSubmittedToScheduledDelay
  • 往RMAppAttempt的ResourceRequest amReq中添加参数:Container数量、优先级、资源名称、relaxLocality。
  • 向调度器为当前AppAttempt申请资源。申请的资源就是amReq。

申请资源部分是核心。

  • 先看看applications这个map中有没有这个FSAppAttempt。有的话就拿出来:FSAppAttempt application。
  • 对申请的资源作Sanity Check。
  • 判断本次AppAttempt是不是第一次请求。如果是,application.setAMResource,分配AM资源。
  • 释放Container资源。这个和本次调用无关。
  • showRequest(),如果日志级别为debug,就按照优先级打印出资源请求。
  • 更新资源请求,即实际的分配资源。这部分较为复杂,拉出来。【11.1】
  • showRequest()
  • 获取所有抢占的Container。
  • pullNewlyAllocatedContainersAndNMTokens。【11.2】
  • 获取headroom,即为可使用的剩余资源量。

最后返回的是Allocation。

11.1 application.updateResourceRequests(ask)

一句话概括:在调度器中,更新资源需求

进入到SchedulerApplicationAttempt中。这个类做了什么?从调度器的视角。每一个运行的AppAttempt都关联一个SchedulerApplicationAttempt实例。

SchedulerApplicationAttempt其实就是FSAttempt的父类。使用AppSchedulingInfo来处理:

appSchedulingInfo.updateResourceRequests(requests**,** false)**;
**这个方法的描述:AM为app更新资源需求。

对每个ResourceRequest:

Priority priority = request.getPriority(); // 优先级
String resourceName = request.getResourceName(); // 资源名称
boolean updatePendingResources = false;
ResourceRequest lastRequest = null;

if (resourceName.equals(ResourceRequest.ANY)) {
  if (LOG.isDebugEnabled()) {
    LOG.debug("update:" + " application=" + applicationId + " request="
        + request);
  }
  // 这个不是特别理解
  updatePendingResources = true;

  if (request.getNumContainers() > 0) {
    // 这个是用来管理用户的,暂且不管
    activeUsersManager.activateApplication(user, applicationId);
  }
}
// 获取当前优先级的资源请求:资源名称 -> ResourceRequest
Map<String, ResourceRequest> asks = this.requests.get(priority);

if (asks == null) {
  // 之前没有的话,添加到里面
  asks = new ConcurrentHashMap<String, ResourceRequest>();
  this.requests.put(priority, asks);
  this.priorities.add(priority);
}
// 上一次对该资源的请求
lastRequest = asks.get(resourceName);

if (recoverPreemptedRequest && lastRequest != null) {
	// Increment the number of containers to 1, as it is recovering a single container.
  // 如果是recover请求的话,则加一个
	request.setNumContainers(lastRequest.getNumContainers() + 1);
}

// container级别设置NodeConstraint需要保留以下信息
// 将上一次请求未满足的都保留?
if (lastRequest != null) {
  request.setSchedulingAttempts(lastRequest.getSchedulingAttempts());
  request.setPreemptionAttempts(lastRequest.getPreemptionAttempts());
  request.setLastSchedulingCycle(lastRequest.getLastSchedulingCycle());
}
// 将本次请求记录在Map中
asks.put(resourceName, request);
if (updatePendingResources) {

  // Similarly, deactivate application? 不太懂
  if (request.getNumContainers() <= 0) {
    LOG.info("checking for deactivate of application :"
        + this.applicationId);
    checkForDeactivation();
  }

  int lastRequestContainers = lastRequest != null ? lastRequest
      .getNumContainers() : 0;
  Resource lastRequestCapability = lastRequest != null ? lastRequest
      .getCapability() : Resources.none();
  metrics.incrPendingResources(user, request.getNumContainers(),
      request.getCapability());

  metrics.decrPendingResources(user, lastRequestContainers,
      lastRequestCapability);
  // appPendingResource,当前app正在等待的资源
  // 只算这次的。上次没拿到的当然就不算了
  Resources.addTo(appPendingResource, Resources
      .multiply(request.getCapability(), request.getNumContainers()));
  Resources.subtractFrom(appPendingResource,
      Resources.multiply(lastRequestCapability, lastRequestContainers));
  // 当前app正在等待的container
  pendingContainers += request.getNumContainers();
  pendingContainers -= lastRequestContainers;
}

11.2 pullNewlyAllocatedContainersAndNMTokens

一句话概括:把之前申请过的资源拿走

创建Container的token和NM的token。如果任意一项失败,则不返回这个Container,而是将它放在刚分配的Container中,等待被获取。

返回的是ContainersAndNMTokensAllocation实例。

  • List containerList ;
  • List nmTokenList ;
public synchronized ContainersAndNMTokensAllocation
    pullNewlyAllocatedContainersAndNMTokens() {
  // 先初始化再说
  List<Container> returnContainerList =
      new ArrayList<Container>(newlyAllocatedContainers.size());
  List<NMToken> nmTokens = new ArrayList<NMToken>();
  // 遍历,在当前流程中,应该啥都没有。其实应该就是把该队列申请到的Container申领走。
  for (Iterator<RMContainer> i = newlyAllocatedContainers.iterator(); i.hasNext();) {
    RMContainer rmContainer = i.next();
    Container container = rmContainer.getContainer();
    try {
      // create container token and NMToken altogether.
      container.setContainerToken(rmContext.getContainerTokenSecretManager()
        .createContainerToken(container.getId(), container.getNodeId(),
          getUser(), container.getResource(), container.getPriority(),
          rmContainer.getCreationTime(), this.logAggregationContext));
      NMToken nmToken =
          rmContext.getNMTokenSecretManager().createAndGetNMToken(getUser(),
            getApplicationAttemptId(), container);
      if (nmToken != null) {
        nmTokens.add(nmToken);
      }
    } catch (IllegalArgumentException e) {
      // DNS might be down, skip returning this container.
      LOG.error("Error trying to assign container token and NM token to" +
          " an allocated container " + container.getId(), e);
      continue;
    }
    returnContainerList.add(container);
    i.remove();
    // 处理Container的事件。在当前流程中应该还没有,毕竟AM都没起起来。后续会用到
    rmContainer.handle(new RMContainerEvent(rmContainer.getContainerId(),
      RMContainerEventType.ACQUIRED));
  }
  return new ContainersAndNMTokensAllocation(returnContainerList, nmTokens);
}

此时,RMAppAttempt才正式转为RMAppAttemptState.SCHEDULED,即RMAppAttempt进入了调度器。

  • RMApp:ACCEPTED

  • RMAppAttempt:SCHEDULED

二 App启动

在这里插入图片描述

RM侧启动

1 FairScheduler调度

一句话概括:核心逻辑,由调度器分配,分到了就发事件,提醒AppAttempt去要资源

这个阶段,就是APP被动等FairScheduler给它启动AM了。RMApp卡在ACCEPTED,RMAppAttempt卡在SCHEDULED。

调度器什么时候分配资源?在FSAppAttempt中,这部分设计到调度的逻辑。

核心方法是attemptScheduling。queueMgr.getRootQueue().assignContainer(node);//队列递归进行资源分配

进入到FSParentQueue的assignContainer中,这个方法对参数传入的Node尝试进行分配。细节见上述文档。

  • 检查队列的资源使用量,过滤掉无pending或超限的子队列

  • 对队列进行排序

  • 对移除节点和资源预留节点进行判断

  • 获取锁,使用FSParentQueue的innerAssign进行分配,遍历子队列进行分配调度。

    • 对子队列,调用FSLeafQueue的assignContainer进行分配

    • 先作前置检查

    • 将有资源需求(app.getAppSchedulingInfo().getPendingContainers() > 0)的app放到排序列表中

    • 为App分配资源,进入到FSLeafQueue#innerAssign中

      • 遍历排序后的app,调用FSAppAttempt的assignContainer方法。

      • 使用getPriorities(),从appSchedulingInfo中获取所有的优先级,并遍历:

        • 从AppSchedulingInfo中的requests获取ResourceName为ResourceRequest.ANY的ResourceRequest

        • 再获取Node所在机架的ResourceRequest、Node所在Node的ResourceRequest

        • NODE_LOCAL的ResourceRequest进行分配,同样是assignContainer方法,但这个方法是ResourceRequest维度的。

          • 如果没有预留,使用createContainer实例化一个Container。这个对象反应了指定Application在指定节点上分配的资源、优先级。

          • 判断Container是否可以分配到这个节点上。分配不了,就埋点,返回none资源,或者FairScheduler.CONTAINER_RESERVED

          • 能分配的话,通过Container创建一个RMContainer。即FSAppAttempt的allocate方法

            • 该RMContainer关联了Container、ApplicationAttempt、RMContext、Node。
            • 将其加入newlyAllocatedContainers。就是之前在allocate()中看到的那个。
            • 将其加入liveContainers,这是一个map:containerId -> RMContainer
            • 再使用appSchedulingInfo来分配,返回一个List。其实只是追踪资源请求。
            • 发送RMContainerEventType.START事件
          • 通知NM启动了Container。SchedulerNode.allocateContainer。在对象的内部记录Container数量,放入launchedContainers Map中。

          • 如果当前启动的Container是AM,则setAmRunning(true)

这部分逻辑比较复杂,待梳理下。基本逻辑大致有了。

总之,经过一次分配,Container被实例化了,RMContainer状态机也启动了。

RMContainerState.NEW, RMContainerState.ALLOCATED, RMContainerEventType.START, new ContainerStartedTransition()

上述状态转换,发送RMAppAttemptEventType.CONTAINER_ALLOCATED事件。

2 & 3 Container分配到资源后,AppAttempt请求资源

一句话概括:AppAttempt去找调度器要资源

RMAppAttemptState.SCHEDULED, EnumSet.of(RMAppAttemptState.ALLOCATED_SAVING, RMAppAttemptState.SCHEDULED),

RMAppAttemptEventType.CONTAINER_ALLOCATED, new AMContainerAllocatedTransition()

  • 同前文申请资源流程,向FairScheduler中申请资源,返回Allocation,即本次调度分配的Container。
    • 过程和上面是一样的,但是这次能拿到Container了。对每个Container的拉取,都会触发RMContainerEventType.ACQUIRED事件。【3.1】
  • retryFetchingAMContainer方法,会起一个线程,停留0.5秒,然后发送RMAppAttemptEventType.CONTAINER_ALLOCATED事件,目的是继续触发当前逻辑,调用allocate方法。
  • 如果本次请求,一个Container都没拿到,那状态依旧停留在RMAppAttemptState.SCHEDULED状态
  • 设置AppAttempt的MasterContainer为获取到的第一个Container,设置这个Container的AMContainer状态为true。
  • 清空NMTokenManager中所有该AppAttempt的node
  • 将AppAttempt落地到StateStore。【4】
3.1 发送APP_RUNNING_ON_NODE

一句话概括:和Node相关联?

  • RMContainer已经启动起来了,不需要资源了,设置ResourceRequest为null。
  • 将当前Container注册到ContainerAllocationExpirer中。
  • 发送RMAppEventType.APP_RUNNING_ON_NODE事件。

RMAppState.ACCEPTED, RMAppState.ACCEPTED, RMAppEventType.APP_RUNNING_ON_NODE, new AppRunningOnNodeTransition()

这个事件不转移状态,只把当前Container所处的Node放入上下文,调用processNodeUpdate方法,将Container所在的节点加入updatedNodes中

4 存储AppAttempt

一句话概括:AppAttempt要到资源了才算生效,才落地ZK。

RMStateStoreEventType.STORE_APP_ATTEMPT

和存储App的大体逻辑一样。路径: /rmstore-2.7.1-rm01/ZKRMStateRoot/RMAppRoot/{app}

完成后,发送事件:RMAppAttemptEventType.ATTEMPT_NEW_SAVED

RMAppAttemptState.ALLOCATED_SAVING, RMAppAttemptState.ALLOCATED, RMAppAttemptEventType.ATTEMPT_NEW_SAVED, new AttemptStoredTransition()

5 启动器启动AM

一句话概括:开始起AM了,放一个任务到队列中,等着异步跑。

上述状态转换,先注册客户端的token,否则客户端在RM重启时,会持有无效的token,在ClientToAMTokenSecretManagerInRM中注册。

随后调用launchAttempt方法,发送AMLauncherEventType.LAUNCH事件

ApplicationMasterLauncher服务接收该事件,起了一个AMLauncher线程,并且将其放在masterEvents中。

这是一个阻塞队列,由LauncherThread在一个循环中,不断从队列中拿任务,并启动任务。LauncherThread在ApplicationMasterLauncher服务启动时启动。

也就是,目前资源都分配了,RMAttempt卡在ALLOCATED,RMContainer卡在ACQUIRED。只等AM启动了。

——————————————————————————————————————————————

AMLauncher启动

AMLauncher有两种事件。LAUNCH或CLEANUP。在run方法中判定,然后启动。

  • 创建对NM的连接,即初始化containerMgrProxy。这个其实就是ContainerManagementProtocol协议的实现。

  • 构建ContainerLaunchContext,从applicationContext中拉参数。

  • 构建StartContainerRequest,传入上面的Context和ContainerToken。再加入一个List,塞到StartContainersRequest中。注意两者差了一个s。

  • 调用containerMgrProxy.startContainers,此时进入NM侧的逻辑。

启动完之后,发送RMAppAttemptEventType.LAUNCHED事件。

  • 落地到RMStateStore中,具体同上。

1 NM处理startContainers

一句话概括:ContainerManager接收RM请求,初始化Container、Application对象

  • 首先处理一系列权限问题
  • 遍历所有的StartContainerRequest,核心逻辑在startContainerInternal中
    • 保存NM token到NMTokenSecretManager,校验Container token。
    • 获取ContainerLaunchContext,并对里面的服务进行校验,看NM有没有提供这个服务。
    • 启动Container实例,也就是NM侧的Container,传入各种信息,包括配置、dispatcher、nmStateStore、启动上下文等
    • 将Container放进Context中。如果里面已经有了,说明该节点上已经有这个Container了,报错。
    • 搞一个读锁,防止其它Container启动影响。
    • 创建一个Application,也放进Context中。如果当前App是第一次运行在该节点上:
      • 获取LogAggregationContext和Acl信息
      • 落地NMStateStore,存储app信息。此时进入到NMStateStoreService#storeApplication。使用LevelDB实现。就简单的put,然后埋点记录信息。
      • 发送ApplicationEventType.INIT_APPLICATION事件。【2.1】
    • Container信息落地落地NMStateStore。
    • 发送ApplicationEventType.INIT_CONTAINER事件【1.1】
    • NMContainerTokenSecretManager,存储token。也落地在NMStateStore中
    • NMAuditLogger记录启动成功的信息
    • 埋点,记录信息
  • 记录在succeededContainers中,构造Response。
1.1 Application的INIT_CONTAINER

不影响Application的状态。

  • 把Container放在Application的containers map里面。
  • 然后视当前Application的情况而定。
    • 如果是NEW或者INITING,说明当前状态不对,则什么都不做。但是已经放进containers里面了,后续APP启动的时候会处理。
    • 如果是RUNNING,说明当前App已经起起来了,这是一个新的Container。则发送ContainerEventType.INIT_CONTAINER事件。

2.1 Application新建

ApplicationState.NEW, ApplicationState.INITING, ApplicationEventType.INIT_APPLICATION, new AppInitTransition()

  • 往app里塞acl、logAggregation的上下文。
  • 发送LogHandlerEventType.APPLICATION_STARTED事件

3 & 4 LogAggregationService处理App启动相关逻辑

一句话概括:Application启动,依赖日志聚合服务。

  • 由NM中的LogAggregationService来处理该事件。调用initApp方法
    • 进入initAppAggregator方法,先获取UserGroupInformation
    • 再实例化AppLogAggregator对象,放在appLogAggregators里面。
    • 再实例化一个AggregatorWrapper,把它放线程池里,用来包装AppLogAggregator的run方法。
  • 初始化成功,则发送ApplicationEventType.APPLICATION_LOG_HANDLING_INITED事件
    • 这个状态转移不影响状态,就不单独拉出来了。
    • 发送LocalizationEventType.INIT_APPLICATION_RESOURCES事件。【5】
  • 否则发送ApplicationEventType.APPLICATION_LOG_HANDLING_FAILED事件

5 初始化App资源

一句话概括:Application启动,开始下载资源

  • 这个是由ResourceLocalizationService处理。和LocalizedResource里的状态机不一样。
  • 有两个map:ConcurrentMap<String**,**LocalResourcesTracker> privateRsrc/appRsrc
  • 对user和appid,都实例化LocalResourcesTracker,并往上面两个里面塞。
  • 发送ApplicationEventType.APPLICATION_INITED事件【11】

6 Application初始化成功

一句话概括:Application初始化成功,开始起Container

ApplicationState.INITING, ApplicationState.RUNNING, ApplicationEventType.APPLICATION_INITED, new AppInitDoneTransition()

遍历当前Application实例中所有的Container,即app.containers,对每个Container都发送ContainerEventType.INIT_CONTAINER事件。

7 Container初始化

一句话概括:Container实例启动,请求下载资源

ContainerState.NEW, EnumSet.of(ContainerState.LOCALIZING, ContainerState.LOCALIZED, ContainerState.LOCALIZATION_FAILED, ContainerState.DONE)**,
**ContainerEventType.INIT_CONTAINER, new RequestResourcesTransition()

  • 检查当前Container的recover状态,如果是已完成,就直接退出。
  • 发送AuxServicesEventType.CONTAINER_INIT等事件
  • 请求资源
    • 如果已经有资源,则发送ContainersLauncherEventType.LAUNCH_CONTAINER事件。
      • 该事件交给ContainersLauncher处理。构造ContainerLaunch实例,并交给线程池处理,并记录在running map中。【12】
      • 最后转为ContainerState.LOCALIZED状态
  • 没有资源,则请求public/private资源。
  • 发送LocalizationEventType.INIT_CONTAINER_RESOURCES事件,同样交给ResourceLocalizationService处理。
    • 对每个资源请求,构造LocalResourcesTracker对象,并对每个都发送ResourceEventType.REQUEST事件。
    • ResourceState.INIT, ResourceState.DOWNLOADING, ResourceEventType.REQUEST, new FetchResourceTransition()
    • 交给ResourceLocalizationService中的LocalizerTracker服务来处理,起LocalizerRunner。
      • 这部分详细分析一下:LOCALIZING什么时候转为LOCALIZED?
      • LocalizedRunner会处理心跳,也就是从ResourceLocalizationService传过来的心跳。
      • PublicLocalizer线程的循环中会从线程池中拉取任务,执行成功就发送。
  • 最终转为ContainerState.LOCALIZING状态

这部分主要还是以AMContainer的视角。如果是AM启动之后,AM向NM发送startContainers请求?这个逻辑我们知道,但是流程是怎样的?这个由具体的引擎侧去决定。

startContainers -> startContainerInternal -> ApplicationEventType.INIT_CONTAINER

-> Application状态机在多个状态都接收这个事件。当且仅当Application处于RUNNING状态时,才处理事件,发送ContainerEventType.INIT_CONTAINE事件

8 请求下载Container资源

一句话概括:Container实例启动,请求下载资源,ResourceLocalizationService处理

ResourceLocalizationService接收INIT_CONTAINER_RESOURCES事件,进行处理。

事件携带过来的资源:Map<LocalResourceVisibility**,** Collection>

对这个Map进行遍历:不同的可见性,建立不同的LocalResourcesTracker。

再对指定可见性下面的Collection遍历,对每一个LocalResourceRequest,由上面的LocalResourcesTracker处理ResourceRequestEvent。

即为发送ResourceEventType.REQUEST事件。

9 创建具体的资源

一句话概括:每一个资源都对应一个LocalizedResource,都有各自的状态机。

ResourceEventType.REQUEST由谁接受?LocalResourcesTrackerImpl。

这里简单介绍一下:LocalResourcesTracker是一个接口,有以下功能:

  • 移除资源
  • 获取本地化路径。接收本地资源请求。
  • 获取用户名
  • 获取本地化资源。接收本地资源请求,返回资源。

它的实现是LocalResourcesTrackerImpl。

// 保存这个Tracker下面,所有资源请求对应的本地资源。

private final ConcurrentMap<LocalResourceRequest**,LocalizedResource> localrsrc;**

处理事件

ResourceEventType

每个事件都会附带一个资源请求LocalResourceRequest req

从维护的map里面拿:LocalizedResource rsrc = localrsrc.get(req);

事件名事件源场景描述
REQUESTResourceLocalizationService接收INIT_CONTAINER_RESOURCES事件Container启动,请求本地化。如果本地有这个资源,但是资源却实际不存在,则把它踢掉。然后再重新创一个LocalizedResource,放在map里。
LOCALIZEDPublicLocalizerLocalizerRunnnerPublicLocalizer,从queue中拿出已完成的任务,则发送该事件。LocalizerRunnner,接收ContainerLocalizer进程的心跳。如果资源状态为FETCH_SUCCESS,则说明获取成功。已经完成。落地到StateStore中。
RELEASEResourceLocalizationServiceResourceLocalizationService接收CLEANUP_CONTAINER_RESOURCES事件。如果需要释放的资源不存在,打日志。如果当前资源属于ResourceState.DOWNLOADING状态,且资源没人用,不为public,将其移除。
LOCALIZATION_FAILEDPublicLocalizerLocalizerRunnnerPublicLocalizer,addResource时出现异常,则发送本次事件。LocalizerRunner,处理心跳。状态为FETCH_FAILURE,则发送。本地化失败,则移除资源。
RECOVEREDContainerManager -> ResourceLocalizationServiceContainerManager的serviceInit时,调用recover()方法,依次往下传递。资源不为空,则打日志恢复资源,并且放进map。

执行完switch-case后,交付给请求对应的LocalizedResource去具体处理。也就是,LocalResourcesTracker虽然处理了事件,但是还需要传递给LocalizedResource。

最终交付给LocalizedResource进行处理,发送LocalizerEventType.REQUEST_RESOURCE_LOCALIZATION事件

ResourceState.INIT, ResourceState.DOWNLOADING, ResourceEventType.REQUEST, new FetchResourceTransition()

即为:请求LocalizerTracker去下载资源

10 资源下载

一句话概括:具体下载任务有具体的线程去负责。

对于不同的优先级,交付给PublicLocalizer或者LocalizerRunner去下载资源。细节见上面的文档。

下载完成后,由LocalizedResource,找到其对应的Container,给它发送事件,汇报资源下载完成。

ContainerState.LOCALIZING, EnumSet.of(ContainerState.LOCALIZING, ContainerState.LOCALIZED),
ContainerEventType.RESOURCE_LOCALIZED, new LocalizedTransition()

11 资源下载完成

一句话概括:每个资源下载完成都会通知Container侧,直到全部下完。

这个过程,当然不可能是一个下完了就OK。Container侧会检验,container.pendingResources.isEmpty()

如果不满足,则依然是ContainerState.LOCALIZING

如果满足,则发送LocalizationEventType.CONTAINER_RESOURCES_LOCALIZED事件。这个事件会让ResourceLocalizationService去销毁该Container对应的下载线程。

12 Container启动

一句话概括:Container启动,跑具体的业务逻辑。

在ContainerLaunch线程中。

在即将去执行具体的启动逻辑前,发送ContainerEventType.CONTAINER_LAUNCHED事件。

LinuxContainerExecutor实现ContainerExecutor抽象类的具体的启动逻辑:launchContainer。

里面再转到ContainerRuntime接口的launchContainer。

结束后,落地到NMStateStore。

ContainerState.LOCALIZED, ContainerState.RUNNING, ContainerEventType.CONTAINER_LAUNCHED, new LaunchTransition()

告知ContainersMonitorImpl启动对container的监控。进程树。

到这里:

  • RMApp ACCEPTED
  • RMAppAttempt LAUNCHED
  • RMContainer ACQUIRED
  • Application RUNNING
  • Container RUNNING

两条上报给RM的路径

1 通过NM的心跳汇报

一句话概括:NM与RM的心跳中会携带Container信息,加到RMNode中。调度器对新分的Container发送事件,通知RMContainer

ResourceTracker接口中的NodeHeartbeat,ResourceTrackerService实现。

RMNodeImpl,会处理RMNodeEventType.STATUS_UPDATE事件,调用handleContainerStatus处理汇报上来的Container。

之后在里面调用SchedulerEventType.NODE_UPDATE事件

这部分由FairScheduler来处理,然后从RMNode接口中拿newlyLaunchedContainers,

遍历,发送RMContainerEventType.LAUNCHED

RMContainerState.ACQUIRED, RMContainerState.RUNNING, RMContainerEventType.LAUNCHED, new LaunchedTransition()

2 通过AM注册

一句话概括:AM启动之后调接口,向RM侧注册,通知RMApp和RMAppAttempt

启动后的AM,调用RM的ApplicationMasterProtocol的registerApplicationMaster,注册,发送RMAppAttemptEventType.REGISTERED事件。

RMAppAttemptState.LAUNCHED, RMAppAttemptState.RUNNING, RMAppAttemptEventType.REGISTERED, new AMRegisteredTransition()

这个事件也发送RMAppEventType.ATTEMPT_REGISTERED

RMAppState.ACCEPTED, RMAppState.RUNNING, RMAppEventType.ATTEMPT_REGISTERED, new RegisterAppAttemptTransition()

以上结束后:

  • RMApp ACCEPTED -> RUNNING
  • RMAppAttempt LAUNCHED -> RUNNING
  • RMContainer ACQUIRED -> RUNNING
  • Application RUNNING
  • Container RUNNING

三 App完成

1.AMService的finish部分。核心逻辑。

2.NM端业务处理,清理Container

3.心跳汇报部分,RMContainer结束并汇报给RMAppAttempt

4.RM心跳传递给NM,结束App。

四者没有强顺序性

如果是异常退出?先不分析。

  • 客户端发送KILL,则从RM->NM
  • NM端出现问题,则从NM -> RM

在这里插入图片描述

1 AM结束,汇报给RM

1 & 2 AM取消注册

一句话概括:AM跑完之后,通知RM。

AM什么时候结束,如何结束?

这个流程中,有几种错误状态:

  • KILLING 任务直接被杀死
  • CONTAINER_CLEANEDUP_AFTER_KILL
  • EXITED_WITH_FAILURE

如果成功,直接EXITED_WITH_SUCCESS

最后统一转为DONE状态。

先梳理正常运行的流程。因此主要梳理EXITED_WITH_SUCCESS。

其实核心还是在ContainerLaunch的call()方法中:在ContainerExecutor:launchContainer中,会返回ret。

  • 如果在启动的整个大逻辑中出现异常,则抛出,并发送ContainerEventType.CONTAINER_EXITED_WITH_FAILURE事件
  • 如果正常返回,但结果为ExitCode.FORCE_KILLED或ExitCode.TERMINATED,发送ContainerEventType.CONTAINER_KILLED_ON_REQUEST事件
  • 如果结果不为0,说明执行失败,发送ContainerEventType.CONTAINER_EXITED_WITH_FAILURE事件。
  • 正常退出,发送ContainerEventType.CONTAINER_EXITED_WITH_SUCCESS事件

ContainerState.RUNNING, ContainerState.EXITED_WITH_SUCCESS, ContainerEventType.CONTAINER_EXITED_WITH_SUCCESS, new ExitedWithSuccessTransition(true)

  • 置exitCode为0、退出时间为当前时间
  • 如果是Docker任务,则移除DockerContainer。使用DeletionService来处理
  • 如果指定clCleanupRequired,则发送ContainersLauncherEventType.CLEANUP_CONTAINER事件
    • ContainersLauncher对事件进行处理。将ContainerLaunch实例从running这个map中移除
    • 再调用这个实例的cleanupContainer方法
      • 落地到NMStateStore:方法storeContainerKilled
      • 杀死进程
      • 如果出现异常,则发送ContainerEventType.UPDATE_DIAGNOSTICS_MSG事件。该事件不改变状态
  • 调用当前container的cleanup事件
    • 将public private application维度的资源全部都记录,并传递到LocalizationEventType.CLEANUP_CONTAINER_RESOURCES事件。
    • ResourceLocalizationService服务处理所有的资源,对每个资源,获取ResourceTracker,并对每个都调用ResourceEventType.RELEASE事件
    • localizerTracker.cleanupPrivLocalizers(locId),清理container资源
    • 再使用DeletionService,删除本地的文件。
    • 发送ContainerEventType.CONTAINER_RESOURCES_CLEANEDUP事件
    • ContainerState.EXITED_WITH_SUCCESS, ContainerState.DONE, ContainerEventType.CONTAINER_RESOURCES_CLEANEDUP, new ExitedWithSuccessToDoneTransition()
      • ApplicationEventType.APPLICATION_CONTAINER_FINISHED
      • ContainersMonitorEventType.STOP_MONITORING_CONTAINER
      • LogHandlerEventType.CONTAINER_FINISHED
      • 给AuxService发送AuxServicesEventType.CONTAINER_STOP事件

3 AppAttempt取消注册,准备结束

一句话概括:AppAttempt从RUNNING转为FINAL_SAVING,并通知RMApp、记录ZK。

RMAppAttemptEventType.UNREGISTERED

  • ApplicationMasterService#finishApplicationMaster

  • 如果是unmanagedAM,直接就返回FINISHED了。

  • 否则,存储app_attempt的状态。【4 5】

    • 记录下次状态:FINISHING(这部分不太理解)

    • 也就是:下次接收ATTEMPT_UPDATE_SAVED事件时,状态机可以从FINAL_SAVING转为FINISHING。

    • private void rememberTargetTransitions(RMAppAttemptEvent event,
          Object transitionToDo, RMAppAttemptState targetFinalState) {
        transitionTodo = transitionToDo;
        targetedFinalState = targetFinalState;
        eventCausingFinalSaving = event;
      }
      
  • 发送RMAppEventType.ATTEMPT_UNREGISTERED事件

转到FINAL_SAVING

4 & 5 AppAttempt状态存储

一句话概括:AppAttempt状态更新到ZK,返回时,AppAttempt转为FINISHING。

正常存储,没什么细节。

RMAppAttemptState.*FINAL_SAVING,
EnumSet.of(RMAppAttemptState.FINISHING, RMAppAttemptState.FAILED, RMAppAttemptState.KILLED, RMAppAttemptState.FINISHED)**,
**RMAppAttemptEventType.*ATTEMPT_UPDATE_SAVED, new FinalStateSavedTransition()

这个状态转移会触发当前RMAppAttempt的transitionTodo和targetedFinalState

6 & 7 & 8 App更新状态,更新到ZK

一句话概括:AppAttempt状态更新到ZK

RMAppEventType.ATTEMPT_UNREGISTERED。从RMAppAttemptEventType.UNREGISTERED中来。

RMAppState.RUNNING, RMAppState.FINAL_SAVING, RMAppEventType.ATTEMPT_UNREGISTERED

new FinalSavingTransition(new AttemptUnregisteredTransition(), RMAppState.FINISHING, RMAppState.FINISHED)

这一步也是用来记录后续状态的。

转为FINAL_SAVING

RMAppState.FINAL_SAVING, EnumSet.of(RMAppState.FINISHING, RMAppState.FAILED, RMAppState.KILLED, RMAppState.FINISHED),

RMAppEventType.APP_UPDATE_SAVED, new FinalStateSavedTransition()

落地到RMStateStore【7 & 8】

返回,转为FINISHING。

2 Container结束(NM端)

1 & 2 清理Container资源

一句话概括:Container资源清理

ContainerState.RUNNING, ContainerState.EXITED_WITH_SUCCESS, ContainerEventType.CONTAINER_EXITED_WITH_SUCCESS, new ExitedWithSuccessTransition(true)

  • 置exitCode为0、退出时间为当前时间
  • 如果是Docker任务,则移除DockerContainer。使用DeletionService来处理
  • 如果指定clCleanupRequired,则发送ContainersLauncherEventType.CLEANUP_CONTAINER事件
    • ContainersLauncher对事件进行处理。将ContainerLaunch实例从running这个map中移除
    • 再调用这个实例的cleanupContainer方法
      • 落地到NMStateStore:方法storeContainerKilled
      • 杀死进程
      • 如果出现异常,则发送ContainerEventType.UPDATE_DIAGNOSTICS_MSG事件。该事件不改变状态
  • 调用当前container的cleanup事件
    • 将public private application维度的资源全部都记录,并传递到LocalizationEventType.CLEANUP_CONTAINER_RESOURCES事件。
    • ResourceLocalizationService服务处理所有的资源,对每个资源,获取ResourceTracker,并对每个都调用ResourceEventType.RELEASE事件
    • localizerTracker.cleanupPrivLocalizers(locId),清理container资源
    • 再使用DeletionService,删除本地的文件。
    • 发送ContainerEventType.CONTAINER_RESOURCES_CLEANEDUP事件
    • ContainerState.EXITED_WITH_SUCCESS, ContainerState.DONE, ContainerEventType.CONTAINER_RESOURCES_CLEANEDUP, new ExitedWithSuccessToDoneTransition()
      • ApplicationEventType.APPLICATION_CONTAINER_FINISHED
      • ContainersMonitorEventType.STOP_MONITORING_CONTAINER
      • LogHandlerEventType.CONTAINER_FINISHED
      • 给AuxService发送AuxServicesEventType.CONTAINER_STOP事件

3 汇报给Application

一句话概括:Container资源清理完汇报给Application端。如果App还在RUNNING就不管。如果不在RUNNING就清理。

ApplicationEventType.APPLICATION_CONTAINER_FINISHED

ApplicationState.RUNNING, ApplicationState.RUNNING

ApplicationEventType.APPLICATION_CONTAINER_FINISHED

CONTAINER_DONE_TRANSITION

如果App还处于RUNNING状态,则说明:只是一个Container跑完了,不影响全局流程。

那这个时候会做什么?把该Container从ApplicationImpl的containers map中踢掉。(Map<ContainerId**,** Container> containers)

或者

ApplicationState.FINISHING_CONTAINERS_WAIT, EnumSet.of(ApplicationState.FINISHING_CONTAINERS_WAIT, ApplicationState.APPLICATION_RESOURCES_CLEANINGUP)**,
**ApplicationEventType.APPLICATION_CONTAINER_FINISHED

new AppFinishTransition()

如果当前属于FINISHING_CONTAINERS_WAIT状态,说明已经在清理了。

首先也是从apps中踢掉该Container。

判断:当前App下的Container是否都结束了

  • 如果都结束了,则转为APPLICATION_RESOURCES_CLEANINGUP
    • 并且发送LocalizationEventType.DESTROY_APPLICATION_RESOURCES事件
    • 发送AuxServicesEventType.APPLICATION_STOP事件
  • 如果还有的没结束,则依然保持在当前状态。

3 NM -> RM心跳汇报,结束RMContainer

一句话概括:和前面的汇报一样。

1 心跳

NodeStatusUpdaterImpl的startStatusUpdater,调用ResourceTrakcerService的nodeHeartBeat()。

2 汇报给RMNode

RMNodeImpl接收RMNodeEventType.STATUS_UPDATE

3 汇报给调度器

FairScheduler接收NODE_UPDATE事件,调用nodeUpdate方法

  • 处理新启动的Container
  • 处理结束的Container:通知RMContainer,发送RMContainerEventType.FINISHED事件

4 结束RMContainer

RMContainerState.RUNNING, RMContainerState.COMPLETED, RMContainerEventType.FINISHED, new FinishedTransition()

  • 更新退出状态
  • 更新AppAttempt的监控信息
  • 发送RMAppAttemptEventType.CONTAINER_FINISHED事件
  • 向RMApplicationHistoryWriter和SystemMetricsPublisher报告App结束。

5 结束RMAppAttempt

一句话概括:RMContainer汇报给RMAppAttempt。如果是AMContainer结束,那AppAttempt就跑完了。

RMAppAttemptState.FINAL_SAVING, RMAppAttemptState.FINAL_SAVING**,
** RMAppAttemptEventType.CONTAINER_FINISHED, new ContainerFinishedAtFinalSavingTransition()

如果当前还是FINAL_SAVING,说明AM并没有结束。

RMAppAttemptState.FINISHING, EnumSet.of(RMAppAttemptState.FINISHING, RMAppAttemptState.FINISHED)**,
** RMAppAttemptEventType.CONTAINER_FINISHED, new AMFinishingContainerFinishedTransition()

如果当前汇报的Container是AM的Container,那就转为FINISHED。否则为FINISHING

从RMContainer中接收AMContainer结束的记录。这样下次就可以将FINAL_SAVING转为FINISHED。

如果是先到了FINISHING状态,那下次接收CONTAINER_FINISHED,就可以到FINISHED。

  • 告知AMLauncher CLEANUP
  • 从AMLivenessMonitor,AMFinishingMonitor中取消该appattempt的注册。

6 结束RMApp

一句话概括:RMApp结束,并将信息汇报给RMNode,这样心跳的时候就可以传递到NM,这样NM端就可以结束Application状态机。

RMAppState.FINISHING, RMAppState.FINISHED, RMAppEventType.ATTEMPT_FINISHEDFINISHED_TRANSITION

  • 向app关联的所有RMNode发送RMNodeEventType.CLEANUP_APP

    • 这个后续会用到:往RMNodeImpl中的finishedApplications中添加当前app
  • 向FairScheduler发送SchedulerEventType.APP_REMOVED事件

  • 发送RMAppManagerEventType.APP_COMPLETED事件

  • 向RMApplicationHistoryWriter和SystemMetricsPublisher报告App结束。

4 RM -> NM心跳,决定清理NM端App

RM端状态机流转结束,更新RMNodeImpl中需要清理的app。在心跳中传递给,NM结束app。

1 心跳

NodeStatusUpdaterImpl的startStatusUpdater,调用ResourceTrakcerService的nodeHeartBeat()。

2 获取需要清理的App

调用RMNode的updateNodeHeartbeatResponseForCleanup方法,从finishedApplications中获取,放入NodeHeartbeatResponse。

再次返回心跳时,从NodeHeartbeatResponse#getContainersToCleanup中获取。

3 汇报给ContainerManager

在心跳过程中,如果要清理的app不为空,则发送ContainerManagerEventType.FINISH_APPS事件。

对需要清理的container也发送ContainerManagerEventType.FINISH_CONTAINERS事件

对所有的app循环处理,构造诊断信息,落地NMStateStore,发送ApplicationEventType.FINISH_APPLICATION事件

4 尝试结束Application

若处于RUNNING状态,则执行【5】

若处于后面几个状态,说明app已经收到过该消息,则什么都不做。

5 尝试资源清理,并kill剩下的container

  • 如果app的所有container都结束了,则转为APPLICATION_RESOURCES_CLEANINGUP
    • 并且发送LocalizationEventType.DESTROY_APPLICATION_RESOURCES事件
    • 发送AuxServicesEventType.APPLICATION_STOP事件
  • 如果还有没结束的Container,说明不太对,得处理掉这些。
  • 对每个Container发送ContainerEventType.KILL_CONTAINER事件,并附带前面的诊断信息。
  • 返回FINISHING_CONTAINERS_WAIT状态

6 清理App本地化资源

LocalizationEventType.DESTROY_APPLICATION_RESOURCES,使用Switch-case处理。

返回ApplicationEventType.APPLICATION_RESOURCES_CLEANEDUP

使用DeletionService删除本地全部路径。

7 清理完成,App结束

  • 发送LogHandlerEventType.APPLICATION_FINISHED事件
  • 向NMTokenSecretManagerInNM汇报App结束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值