上文说到,需要指定Container所在的NM启动其上的Container,我们看看这个方法的内容:
/**
* Start a list of containers on this NodeManager.
*/
@Override
public StartContainersResponse startContainers(
StartContainersRequest requests) throws YarnException, IOException {
if (blockNewContainerRequests.get()) {
throw new NMNotYetReadyException(
"Rejecting new containers as NodeManager has not"
+ " yet connected with ResourceManager");
}
UserGroupInformation remoteUgi = getRemoteUgi();
NMTokenIdentifier nmTokenIdentifier = selectNMTokenIdentifier(remoteUgi);
authorizeUser(remoteUgi, nmTokenIdentifier);
List<ContainerId> succeededContainers = new ArrayList<ContainerId>();
Map<ContainerId, SerializedException> failedContainers = new HashMap<ContainerId, SerializedException>();
for (StartContainerRequest request : requests
.getStartContainerRequests()) {
ContainerId containerId = null;
try {
ContainerTokenIdentifier containerTokenIdentifier = BuilderUtils
.newContainerTokenIdentifier(request
.getContainerToken());
verifyAndGetContainerTokenIdentifier(
request.getContainerToken(), containerTokenIdentifier);
containerId = containerTokenIdentifier.getContainerID();
startContainerInternal(nmTokenIdentifier,
containerTokenIdentifier, request);
succeededContainers.add(containerId);
} catch (YarnException e) {
failedContainers.put(containerId,
SerializedException.newInstance(e));
} catch (InvalidToken ie) {
failedContainers.put(containerId,
SerializedException.newInstance(ie));
throw ie;
} catch (IOException e) {
throw RPCUtil.getRemoteException(e);
}
}
return StartContainersResponse.newInstance(getAuxServiceMetaData(),
succeededContainers, failedContainers);
}
先有个初步印象,我们一点点拆开看,前面的验证模块和权限检查不看了,我们直接从for循环开始看,因为默认情况下启动的Container只有一个,所以这里的循环,对于2.6.5版本的Yarn来说,没有什么意义,直接看循环内容:
startContainerInternal(nmTokenIdentifier,
containerTokenIdentifier, request);
毫无疑问,这是重中之重的方法,点进去看下,我这里只粘贴该方法中部分方法(ContainerManagerImpl内部的startContainerInternal方法)
Container container = new ContainerImpl(getConfig(), this.dispatcher,
launchContext, credentials, metrics, containerTokenIdentifier,
context);
首先,新建一个ContainerImpl,其内部也维护着着一个状态机,初始状态为因为其本身也是个服务,同NM启动时候一起启动,所以内部的serviceInit和ServiceStart方法都已经调用过了,注意,这里传入的dispatcher是全局调度器:
下面看该方法中try的代码,即主要运行代码:
Application application = new ApplicationImpl(dispatcher, user,
applicationID, credentials, context);
构造了一个新的ApplicationImpl,传入的dispatcher和contex也是全局的,需要先判断本次提交的Application是否已经在指定的ContainerId所在的NM上启动了:
if (null == context.getApplications().putIfAbsent(
applicationID, application))
如果没有,接着执行:
LOG.info("Creating a new application reference for app "
+ applicationID);
// 存储
LogAggregationContext logAggregationContext = containerTokenIdentifier
.getLogAggregationContext();
// 权限管理
Map<ApplicationAccessType, String> appAcls = container
.getLaunchContext().getApplicationACLs();
// 存储
context.getNMStateStore().storeApplication(
applicationID,
buildAppProto(applicationID, user, credentials,
appAcls, logAggregationContext));
// 事件调度
dispatcher.getEventHandler().handle(
new ApplicationInitEvent(applicationID, appAcls,
logAggregationContext));
这里,NMStateStore负责NM节点的存储,这个不说了,接着,其自身调度器提交了一个ApplicationInitEvent的事件,我们可以在中找到这个事件的处理类:
dispatcher.register(ApplicationEventType.class,
new ApplicationEventDispatcher());
这里的dispatcher是ContainerManagerImpl内部的调度器,我们看下ApplicationEventDispatcher这个调度器:
class ApplicationEventDispatcher implements EventHandler<ApplicationEvent> {
@Override
public void handle(ApplicationEvent event) {
Application app = ContainerManagerImpl.this.context
.getApplications().get(event.getApplicationID());
if (app != null) {
app.handle(event);
} else {
LOG.warn("Event " + event + " sent to absent application "
+ event.getApplicationID());
}
}
}
我们看下这里的handl的处理逻辑,其实现在ApplicationImpl内:
@Override
public void handle(ApplicationEvent event) {
this.writeLock.lock();
try {
ApplicationId applicationID = event.getApplicationID();
LOG.debug("Processing " + applicationID + " of type "
+ event.getType());
ApplicationState oldState = stateMachine.getCurrentState();
ApplicationState newState = null;
try {
// queue event requesting init of the same app
newState = stateMachine.doTransition(event.getType(), event);
} catch (InvalidStateTransitonException e) {
LOG.warn("Can't handle this event at current state", e);
}
if (oldState != newState) {
LOG.info("Application " + applicationID + " transitioned from "
+ oldState + " to " + newState);
}
} finally {
this.writeLock.unlock();
}
}
在ApplicationImpl内部,状态机的初始状态为:ApplicationState.NEW,而我们传入的事件类型为:ApplicationEventType.INIT_APPLICATION,然后我们就能找到doTransition究竟执行的是哪个方法了,这同时也让我们看到了状态机到底是怎么调用的:
@SuppressWarnings("unchecked")
static class AppInitTransition implements
SingleArcTransition<ApplicationImpl, ApplicationEvent> {
@Override
public void transition(ApplicationImpl app, ApplicationEvent event) {
ApplicationInitEvent initEvent = (ApplicationInitEvent) event;
app.applicationACLs = initEvent.getApplicationACLs();
app.aclsManager.addApplication(app.getAppId(), app.applicationACLs);
// Inform the logAggregator
app.logAggregationContext = initEvent.getLogAggregationContext();
app.dispatcher.getEventHandler().handle(
new LogHandlerAppStartedEvent(app.appId, app.user,
app.credentials,
ContainerLogsRetentionPolicy.ALL_CONTAINERS,
app.applicationACLs, app.logAggregationContext));
}
}
这里,我们必须看下app.dispatcher,其实是ContainerManagerImpl的dispatcher:
dispatcher.register(LogHandlerEventType.class, logHandler);
我们看看这个logHandler:
LogHandler logHandler = createLogHandler(conf, this.context,
this.deletionService);
protected LogHandler createLogHandler(Configuration conf, Context context,
DeletionService deletionService) {
if (conf.getBoolean(YarnConfiguration.LOG_AGGREGATION_ENABLED,
YarnConfiguration.DEFAULT_LOG_AGGREGATION_ENABLED)) {
return new LogAggregationService(this.dispatcher, context,
deletionService, dirsHandler);
} else {
return new NonAggregatingLogHandler(this.dispatcher,
deletionService, dirsHandler);
}
}
我们默认逻辑下,返回的是NonAggregatingLogHandler,研究下其中的处理方法:
case APPLICATION_STARTED:
LogHandlerAppStartedEvent appStartedEvent = (LogHandlerAppStartedEvent) event;
this.appOwners.put(appStartedEvent.getApplicationId(),
appStartedEvent.getUser());
this.dispatcher
.getEventHandler()
.handle(new ApplicationEvent(
appStartedEvent.getApplicationId(),
ApplicationEventType.APPLICATION_LOG_HANDLING_INITED));
break;
基于我们的事件类型,找到相应的处理:
我们发现其提交了一个ApplicationEventType.APPLICATION_LOG_HANDLING_INITED类型的事件,而对应的dispatcher,实质上是ApplicationImpl的dispatcher:
由于异步调度,这时候ApplicationImpl的状态已经变成了ApplicationState.INITING,这里,已经第二次出现这样的方法来应用异步调度了:
addTransition(ApplicationState.INITING, ApplicationState.INITING,
ApplicationEventType.APPLICATION_LOG_HANDLING_INITED,
new AppLogInitDoneTransition())
我们找到对应的转换方法:
@SuppressWarnings("unchecked")
static class AppLogInitDoneTransition implements
SingleArcTransition<ApplicationImpl, ApplicationEvent> {
@Override
public void transition(ApplicationImpl app, ApplicationEvent event) {
app.dispatcher.getEventHandler().handle(
new ApplicationLocalizationEvent(
LocalizationEventType.INIT_APPLICATION_RESOURCES,
app));
}
}
这是个异步调度,所以调用之后,ApplicationImpl内部的状态机状态就变成了ApplicationState.INITING,相当于没变,然后调度器提交了新的事件,这个调度器是ContainerManagerImpl的调度器,所以事件交给了ResourceLocalizationService来处理:
case INIT_APPLICATION_RESOURCES:
handleInitApplicationResources(((ApplicationLocalizationEvent) event)
.getApplication());
break;
我们看下handleInitApplicationResource方法:
@SuppressWarnings("unchecked")
private void handleInitApplicationResources(Application app) {
// 0) Create application tracking structs
String userName = app.getUser();
privateRsrc.putIfAbsent(userName,
new LocalResourcesTrackerImpl(userName, null, dispatcher, true,
super.getConfig(), stateStore));
String appIdStr = ConverterUtils.toString(app.getAppId());
appRsrc.putIfAbsent(appIdStr,
new LocalResourcesTrackerImpl(app.getUser(), app.getAppId(),
dispatcher, false, super.getConfig(), stateStore));
// 1) Signal container init
//
// This is handled by the ApplicationImpl state machine and allows
// containers to proceed with launching.
dispatcher.getEventHandler().handle(
new ApplicationInitedEvent(app.getAppId()));
}
操作完成之后,提交了新的事件,而这个事件,交给ApplicationEventDispatcher来调度,最终交由ApplicationImpl来handle,前面我们看过了,这个方法内的内容就是状态机转换:
而ApplicationImpl的prestate是ApplicationState.INITING,此次提交的触发事件是:ApplicationEventType.ApplicationEventType.APPLICATION_INITED,我们看代码:
.addTransition(ApplicationState.INITING, ApplicationState.RUNNING,
ApplicationEventType.APPLICATION_INITED,
new AppInitDoneTransition())
接着看AppInitDoneTransition:
@SuppressWarnings("unchecked")
static class AppInitDoneTransition implements
SingleArcTransition<ApplicationImpl, ApplicationEvent> {
@Override
public void transition(ApplicationImpl app, ApplicationEvent event) {
// Start all the containers waiting for ApplicationInit
for (Container container : app.containers.values()) {
app.dispatcher.getEventHandler().handle(
new ContainerInitEvent(container.getContainerId()));
}
}
}
注意到,这里也是异步调度,在转换完成之前,ApplicationImpl的状态机已经更新为Application.RUNNING,我们看看ContainerEventInitEvent:
public ContainerInitEvent(ContainerId c) {
super(c, ContainerEventType.INIT_CONTAINER);
}
接着,我们找下一步转化代码:
addTransition(ApplicationState.RUNNING, ApplicationState.RUNNING,
ApplicationEventType.INIT_CONTAINER,
new InitContainerTransition())
继续看InitContainerTransition,注意,就running来说,其内部依旧存在异步调度方式:
@Override
public void transition(ApplicationImpl app, ApplicationEvent event) {
ApplicationContainerInitEvent initEvent = (ApplicationContainerInitEvent) event;
Container container = initEvent.getContainer();
app.containers.put(container.getContainerId(), container);
LOG.info("Adding " + container.getContainerId()
+ " to application " + app.toString());
switch (app.getApplicationState()) {
case RUNNING:
app.dispatcher.getEventHandler().handle(
new ContainerInitEvent(container.getContainerId()));
break;
case INITING:
case NEW:
// these get queued up and sent out in AppInitDoneTransition
break;
default:
assert false : "Invalid state for InitContainerTransition: "
+ app.getApplicationState();
}
}
状态机状态更新为ApplicationState.RUNNING,等于没改变,我们看下ContainerInitEvent:
dispatcher.register(ContainerEventType.class,
new ContainerEventDispatcher());
找到ContainerEventDispatcher的handle方法,一直到ContainerImple的handle方法,然后对ContainerImpl的状态机做出更新:
当前ContainerImpl的状态为默认的ContainerState.NEW:
addTransition(
ContainerState.NEW,
EnumSet.of(ContainerState.LOCALIZING,
ContainerState.LOCALIZED,
ContainerState.LOCALIZATION_FAILED,
ContainerState.DONE),
ContainerEventType.INIT_CONTAINER,
new RequestResourcesTransition())
这是一个多变转换,意思是在当前的触发条件下,状态机可能转换到多个状态,其中代码比较复杂,不分析了,直接给出最后几句逻辑相关性强的代码:
container.dispatcher.getEventHandler().handle(
new ContainerLocalizationRequestEvent(container, req));
return ContainerState.LOCALIZING;
} else {
container.sendLaunchEvent();
container.metrics.endInitingContainer();
return ContainerState.LOCALIZED;
}
这次的调度,主要是与Container本地化有关,因为启动过程,需要很多的资源,最后返回的则是两种状态值,Container有没有本地初始化完毕,我们选择后面的一种来分析,假设不需要额外的资源,可以直接LOCALIZED完毕。
因为异步调度的关系,在sendLaunchEvent方法执行完毕前,ContainerImpl的状态已经更新为ContainerState.LOCALIZED,我们看看sendLaunchEvent方法:
private void sendLaunchEvent() {
ContainersLauncherEventType launcherEvent = ContainersLauncherEventType.LAUNCH_CONTAINER;
if (recoveredStatus == RecoveredContainerStatus.LAUNCHED) {
// try to recover a container that was previously launched
launcherEvent = ContainersLauncherEventType.RECOVER_CONTAINER;
}
dispatcher.getEventHandler().handle(
new ContainersLauncherEvent(this, launcherEvent));
}
提交了一个新的事件,而这个事件实际处理者,则是ContainerManagerImpl内的调度器,传给ContainersLauncher之后,看看其handle方法:
case LAUNCH_CONTAINER:
Application app = context.getApplications().get(
containerId.getApplicationAttemptId().getApplicationId());
ContainerLaunch launch = new ContainerLaunch(context, getConfig(),
dispatcher, exec, app, event.getContainer(), dirsHandler,
containerManager);
containerLauncher.submit(launch);
running.put(containerId, launch);
break;
这里,重点在于:
ContainerLaunch launch = new ContainerLaunch(context, getConfig(),
dispatcher, exec, app, event.getContainer(), dirsHandler,
containerManager);
containerLauncher.submit(launch);
我们必须看看ContainerLaunch内部的run或者call方法:这个方法很长,我们只挑选其中部分代码看下:
ret = exec.launchContainer(container,
nmPrivateContainerScriptPath, nmPrivateTokensPath,
user, appIdStr, containerWorkDir, localDirs, logDirs);
这就是重点,终于走到了这里,该方法的调用,则Container会正式启动起来,更具体的代码参见源码即可。