App生命周期分享
本文主要对App在Yarn状态机中的流转过程进行分析。
由于涉及的状态机较多,事件流转颇为复杂,故将全流程分为三个阶段:
- App初始化,即:客户端提交任务,经过Router转发至RM,在RM端进行RMApp、RMAppAttempt状态机的初始化,并添加到调度器,为AM申请资源。
- App启动,即:调度器为AM调度资源,初始化RMContainer状态机,在NM端启动Application、Container。并进行资源本地化,启动Container。
- 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】
创建完成后,把它放进缓存。
使用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状态
- 如果已经有资源,则发送ContainersLauncherEventType.LAUNCH_CONTAINER事件。
- 没有资源,则请求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);
事件名 | 事件源 | 场景 | 描述 |
---|---|---|---|
REQUEST | ResourceLocalizationService接收INIT_CONTAINER_RESOURCES事件 | Container启动,请求本地化。 | 如果本地有这个资源,但是资源却实际不存在,则把它踢掉。然后再重新创一个LocalizedResource,放在map里。 |
LOCALIZED | PublicLocalizerLocalizerRunnner | PublicLocalizer,从queue中拿出已完成的任务,则发送该事件。LocalizerRunnner,接收ContainerLocalizer进程的心跳。如果资源状态为FETCH_SUCCESS,则说明获取成功。 | 已经完成。落地到StateStore中。 |
RELEASE | ResourceLocalizationService | ResourceLocalizationService接收CLEANUP_CONTAINER_RESOURCES事件。 | 如果需要释放的资源不存在,打日志。如果当前资源属于ResourceState.DOWNLOADING状态,且资源没人用,不为public,将其移除。 |
LOCALIZATION_FAILED | PublicLocalizerLocalizerRunnner | PublicLocalizer,addResource时出现异常,则发送本次事件。LocalizerRunner,处理心跳。状态为FETCH_FAILURE,则发送。 | 本地化失败,则移除资源。 |
RECOVERED | ContainerManager -> ResourceLocalizationService | ContainerManager的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_FINISHED, FINISHED_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结束