关于Yarn源码那些事(七)

上文说到,需要指定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会正式启动起来,更具体的代码参见源码即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值