At the beginning of this section, we will analyze and explore some common behaviors in ResourceManager, and see how some specific key behaviors flow in RM. This section will go deep into the source code to explore the specific process of "starting ApplicationMaster".
1. The overall process
This section introduces the whole process from submitting the application to starting the ApplicationMaster, involving several main components such as Client, RMService, RMAppManager, RMApplmpl, RMAppAttemptImpl, RMNode, and ResourceScheduler. ApplicationClientProtocol#submitApplication
After the client invokes the RPC function , the processing on the ResourceManager side is shown in the figure below.
2. Specific process analysis
Next, following the flow chart above, we go deep into the source code and analyze how each step is executed:
at the beginning, the client initiates task submission , and sends events submitApplication()
through ClientRMService
and , and then hands them over to process.RMAppManager
RMAppEventType.START
RMAppImpl
protected void submitApplication(
ApplicationSubmissionContext submissionContext, long submitTime,
String user) throws YarnException {
ApplicationId applicationId = submissionContext.getApplicationId();
RMAppImpl application =
createAndPopulateNewRMApp(submissionContext, submitTime, user, false);
Credentials credentials = null;
try {
credentials = parseCredentials(submissionContext);
if (UserGroupInformation.isSecurityEnabled()) {
this.rmContext.getDelegationTokenRenewer()
.addApplicationAsync(applicationId, credentials,
submissionContext.getCancelTokensWhenComplete(),
application.getUser());
} else {
// Dispatcher is not yet started at this time, so these START events
// enqueued should be guaranteed to be first processed when dispatcher
// gets started.
// 这里发送 RMAppEventType.START 事件
this.rmContext.getDispatcher().getEventHandler()
.handle(new RMAppEvent(applicationId, RMAppEventType.START));
}
RMAppImpl
This thing is a state machine. After receiving an event, it will change the state and process the corresponding logic.
(Students who are not familiar with the state machine can turn to my previous article to learn "2-4 Yarn Basic Library-State Machine Library")
Intercept a part of the state transition code:
private static final StateMachineFactory<RMAppImpl,
RMAppState,
RMAppEventType,
RMAppEvent> stateMachineFactory
= new StateMachineFactory<RMAppImpl,
RMAppState,
RMAppEventType,
RMAppEvent>(RMAppState.NEW)
// Transitions from NEW state
.addTransition(RMAppState.NEW, RMAppState.NEW,
RMAppEventType.NODE_UPDATE, new RMAppNodeUpdateTransition())
// 收到 RMAppEventType.START 事件
.addTransition(RMAppState.NEW, RMAppState.NEW_SAVING,
RMAppEventType.START, new RMAppNewlySavingTransition())
.addTransition(RMAppState.NEW, EnumSet.of(RMAppState.SUBMITTED,
RMAppState.ACCEPTED, RMAppState.FINISHED, RMAppState.FAILED,
RMAppState.KILLED, RMAppState.FINAL_SAVING),
RMAppEventType.RECOVER, new RMAppRecoveredTransition())
.addTransition(RMAppState.NEW, RMAppState.KILLED, RMAppEventType.KILL,
new AppKilledTransition())
.addTransition(RMAppState.NEW, RMAppState.FINAL_SAVING,
RMAppEventType.APP_REJECTED,
new FinalSavingTransition(new AppRejectedTransition(),
RMAppState.FAILED))
a) RMAppImpl - START
RMAppEventType.START
After the event is received , it will be executed RMAppNewlySavingTransition()
.
private static final class RMAppNewlySavingTransition extends RMAppTransition {
@Override
public void transition(RMAppImpl app, RMAppEvent event) {
// If recovery is enabled then store the application information in a
// non-blocking call so make sure that RM has stored the information
// needed to restart the AM after RM restart without further client
// communication
LOG.info("Storing application with id " + app.applicationId);
app.rmContext.getStateStore().storeNewApplication(app);
}
}
If you follow along, you will find that it sends out RMStateStoreEventType.STORE_APP
events , and RMStateStore
you can find the corresponding event processing in . Discovery is also a state machine:
.addTransition(RMStateStoreState.ACTIVE,
EnumSet.of(RMStateStoreState.ACTIVE, RMStateStoreState.FENCED),
RMStateStoreEventType.STORE_APP, new StoreAppTransition())
Follow StoreAppTransition
to see what's done (send RMAppEventType.APP_NEW_SAVED
event)
private static class StoreAppTransition
implements MultipleArcTransition<RMStateStore, RMStateStoreEvent,
RMStateStoreState> {
@Override
public RMStateStoreState transition(RMStateStore store,
RMStateStoreEvent event) {
if (!(event instanceof RMStateStoreAppEvent)) {
// should never happen
LOG.error("Illegal event type: " + event.getClass());
return RMStateStoreState.ACTIVE;
}
boolean isFenced = false;
ApplicationStateData appState =
((RMStateStoreAppEvent) event).getAppState();
ApplicationId appId =
appState.getApplicationSubmissionContext().getApplicationId();
LOG.info("Storing info for app: " + appId);
try {
store.storeApplicationStateInternal(appId, appState);
// 这里发送了 RMAppEventType.APP_NEW_SAVED 事件
store.notifyApplication(new RMAppEvent(appId,
RMAppEventType.APP_NEW_SAVED));
} catch (Exception e) {
LOG.error("Error storing app: " + appId, e);
isFenced = store.notifyStoreOperationFailedInternal(e);
}
return finalState(isFenced);
};
}
2) RMAppImpl - APP_NEW_SAVED
Let's go back RMAppImpl
and find the corresponding state transition logic.
// 刚刚我们的状态是 NEW_SAVING,收到了 APP_NEW_SAVED 事件,执行 AddApplicationToSchedulerTransition() 后,转换为 SUBMITTED 状态
.addTransition(RMAppState.NEW_SAVING, RMAppState.SUBMITTED,
RMAppEventType.APP_NEW_SAVED, new AddApplicationToSchedulerTransition())
The event is sent in AddApplicationToSchedulerTransition()
the SchedulerEventType.APP_ADDED
. Then RMAppImpl
transition to RMAppState.SUBMITTED
the state.
SchedulerEventType.APP_ADDED
Will be captured and processed by multiple event handlers:
1) ResourceSchedulerWrapper
Event handler, only record
} else if (schedulerEvent.getType() == SchedulerEventType.APP_ADDED
&& schedulerEvent instanceof AppAddedSchedulerEvent) {
AppAddedSchedulerEvent appAddEvent =
(AppAddedSchedulerEvent) schedulerEvent;
String queueName = appAddEvent.getQueue();
appQueueMap.put(appAddEvent.getApplicationId(), queueName);
}
2) The implementation class AbstractYarnScheduler
of . Take CapacityScheduler
for example :
executeaddApplication()
case APP_ADDED:
{
AppAddedSchedulerEvent appAddedEvent = (AppAddedSchedulerEvent) event;
String queueName = resolveReservationQueueName(appAddedEvent.getQueue(),
appAddedEvent.getApplicationId(), appAddedEvent.getReservationID(),
appAddedEvent.getIsAppRecovering());
if (queueName != null) {
if (!appAddedEvent.getIsAppRecovering()) {
addApplication(appAddedEvent.getApplicationId(), queueName,
appAddedEvent.getUser(), appAddedEvent.getApplicatonPriority());
} else {
addApplicationOnRecovery(appAddedEvent.getApplicationId(), queueName,
appAddedEvent.getUser(), appAddedEvent.getApplicatonPriority());
}
}
}
addApplication()
In will submit the Application and send RMAppEventType.APP_ACCEPTED
the event.
queue.submitApplication(applicationId, user, queueName);
rmContext.getDispatcher().getEventHandler()
.handle(new RMAppEvent(applicationId, RMAppEventType.APP_ACCEPTED));
3) RMAppImpl - APP_ACCEPTED (emphasis)
Continue to return RMAppImpl
, execute StartAppAttemptTransition()
, create newAttempt
, and send eventsRMAppAttemptEventType.START
.addTransition(RMAppState.SUBMITTED, RMAppState.ACCEPTED,
RMAppEventType.APP_ACCEPTED, new StartAppAttemptTransition())
private static final class StartAppAttemptTransition extends RMAppTransition {
@Override
public void transition(RMAppImpl app, RMAppEvent event) {
app.createAndStartNewAttempt(false);
};
}
private void
createAndStartNewAttempt(boolean transferStateFromPreviousAttempt) {
createNewAttempt();
handler.handle(new RMAppStartAttemptEvent(currentAttempt.getAppAttemptId(),
transferStateFromPreviousAttempt));
}
RMAppAttemptImpl
This event will be captured in and executed, AttemptStartedTransition()
and SchedulerEventType.APP_ATTEMPT_ADDED
the event , which will be processed by the AbstractYarnScheduler implementation class
.addTransition(RMAppAttemptState.NEW, RMAppAttemptState.SUBMITTED,
RMAppAttemptEventType.START, new AttemptStartedTransition())
If addApplicationAttempt
handled , will submit ApplicationAttempt
, and send RMAppAttemptEventType.ATTEMPT_ADDED
the event
private synchronized void addApplicationAttempt() {
// 提交 attempt
queue.submitApplicationAttempt(attempt, application.getUser());
// 发送 RMAppAttemptEventType.ATTEMPT_ADDED 事件
rmContext.getDispatcher().getEventHandler().handle(
new RMAppAttemptEvent(applicationAttemptId,
RMAppAttemptEventType.ATTEMPT_ADDED));
}
RMAppAttemptImpl
Continue to process after receiving the event, and ScheduleTransition
then allocate am container resources.
.addTransition(RMAppAttemptState.SUBMITTED,
EnumSet.of(RMAppAttemptState.LAUNCHED_UNMANAGED_SAVING,
RMAppAttemptState.SCHEDULED),
RMAppAttemptEventType.ATTEMPT_ADDED,
new ScheduleTransition())
// AM resource has been checked when submission
Allocation amContainerAllocation =
appAttempt.scheduler.allocate(
appAttempt.applicationAttemptId,
Collections.singletonList(appAttempt.amReq),
EMPTY_CONTAINER_RELEASE_LIST,
amBlacklist.getBlacklistAdditions(),
amBlacklist.getBlacklistRemovals(), null, null);
Before the ResourceScheduler returns resources to it, it sends an RMContainerEventType.ACQUIRED
event .
On RMContainerImpl
receive RMContainerEventType.START
, send RMAppAttemptEventType.CONTAINER_ALLOCATED
event .
.addTransition(RMContainerState.NEW, RMContainerState.ALLOCATED,
RMContainerEventType.START, new ContainerStartedTransition())
private static final class ContainerStartedTransition extends
BaseTransition {
@Override
public void transition(RMContainerImpl container, RMContainerEvent event) {
container.eventHandler.handle(new RMAppAttemptEvent(
container.appAttemptId, RMAppAttemptEventType.CONTAINER_ALLOCATED));
}
}
Going back to RMAppAttemptImpl
the follow-up state machine and executing AMContainerAllocatedTransition
it, it is am allocate again, and the only parameter of allocate in the previous state is different, and I don’t understand why. Here, if it is found that the allocate container resource is still 0, it will return to the previous step, and the state is still RMAppAttemptState.SCHEDULED
waiting obtain resources again. If the resource is obtained normally, it will change to RMAppAttemptState.ALLOCATED_SAVING
the state .
.addTransition(RMAppAttemptState.SCHEDULED,
EnumSet.of(RMAppAttemptState.ALLOCATED_SAVING,
RMAppAttemptState.SCHEDULED),
RMAppAttemptEventType.CONTAINER_ALLOCATED,
new AMContainerAllocatedTransition())
Allocation amContainerAllocation =
appAttempt.scheduler.allocate(appAttempt.applicationAttemptId,
EMPTY_CONTAINER_REQUEST_LIST, EMPTY_CONTAINER_RELEASE_LIST, null,
null, null, null);
When logging is complete, sendRMStateStore
an event to . Subsequent sends the event to (actual execution is in ) and transitions the state from ALLOCATED_SAVING to ALLOCATED.RMAppAttemptImpl
RMAppAttemptEventType.ATTEMPT_NEW_SAVED
RMAppAttemptImpl
ApplicationMasterLauncher
AMLauncherEventType.LAUNCH
AMLauncher
.addTransition(RMAppAttemptState.ALLOCATED_SAVING,
RMAppAttemptState.ALLOCATED,
RMAppAttemptEventType.ATTEMPT_NEW_SAVED, new AttemptStoredTransition())
ApplicationMasterLauncher
After receiving AMLauncherEventType.LAUNCH
the event , it will put the event in the event queue and wait for AMLauncher
the thread in the thread pool to process the event. It will communicate with the corresponding NodeManager, start the ApplicationMaster, and once successfully started, will RMAppAttemptImpl
send RMAppAttemptEventType.LAUNCHED
an event to the .
public void run() {
switch (eventType) {
case LAUNCH:
try {
LOG.info("Launching master" + application.getAppAttemptId());
launch();
handler.handle(new RMAppAttemptEvent(application.getAppAttemptId(),
RMAppAttemptEventType.LAUNCHED));
RMAppAttemptImpl
After receiving RMAppAttemptEventType.LAUNCHED
the event , it will AMLivelinessMonitor
register with to monitor the running status. RMAppAttemptImpl
Status ALLOCATED
transitioned to LAUNCHED
.
Afterwards, the NodeManager reports through the heartbeat mechanism that the Container where the ApplicationMaster resides has been successfully started. After receiving the information, the ResourceScheduler will send an RMContainerEventType.LAUNCHED
event . RMContainerImpl
After receiving the event, it will be removed from the ContainerAllocationExpirer monitoring list.
The started ApplicationMaster registers ApplicationMasterProtocol#registerApplicationMaster
with , and ApplicationMasterService
the service in the ResourceManager sends RMAppAttemptEventType.REGISTERED
an event .
// ApplicationMasterService#registerApplicationMaster
LOG.info("AM registration " + applicationAttemptId);
this.rmContext
.getDispatcher()
.getEventHandler()
.handle(
// 这里发送 RMAppAttemptEventType.REGISTERED 事件
new RMAppAttemptRegistrationEvent(applicationAttemptId, request
.getHost(), request.getRpcPort(), request.getTrackingUrl()));
RMAppAttemptImpl
After receiving the event, it first saves the basic information of the ApplicationMaster (such as its host, enabled RPC port number, etc.), and then RMApplmpl
sends an RMAppEventType.ATTEMPT_REGISTERED
event to . RMAppAttemptImpl
Status LAUNCHED
transitioned to RUNNING
.
.addTransition(RMAppAttemptState.LAUNCHED, RMAppAttemptState.RUNNING,
RMAppAttemptEventType.REGISTERED, REGISTERED_TRANSITION)
// AMRegisteredTransition
appAttempt.eventHandler.handle(new RMAppEvent(appAttempt
.getAppAttemptId().getApplicationId(),
RMAppEventType.ATTEMPT_REGISTERED));
四)RMAppImpl - ATTEMPT_REGISTERED
RMAppImpl
Transitions the state from ACCEPTED to RUNNING upon receipt of RMAppEventType.ATTEMPT_REGISTERED
the event .
.addTransition(RMAppState.ACCEPTED, RMAppState.RUNNING,
RMAppEventType.ATTEMPT_REGISTERED, new RMAppStateUpdateTransition(
YarnApplicationState.RUNNING))
At this point, the analysis of the overall process of starting ApplicationMaster is completed!
3. Summary
This article analyzes the whole process from application submission to starting ApplicationMaster. It may be a bit cumbersome to analyze the specific process. But as long as you grasp the core essence, it is easy to figure it out. The focus is on event processing and state machine. If these two components are clearly understood, it is easy to understand the flow of the program.
The actual logic is nothing more than several services sending corresponding events to each other. After receiving the event, they will start the service, record the log, monitor the status, and then send a new event.
It's not difficult in itself, but you need to be patient and sort out a little bit.