In a huge distributed system, how do the various components coordinate their work? How are components decoupled? How can threads run more efficiently and reduce inefficiencies caused by blocking? This section will introduce Yarn's service library and event library, and see how Yarn solves these problems.
1. Service library
1) Introduction
For objects with a long life cycle, Yarn adopts a service-based model to manage them, which has the following characteristics:
- State-based management: divided into 4 states:
NOTINITED
(created),INITED
(initialized),STARTED
(started),STOPPED
(stopped). - Changes in the state of the service trigger other actions.
- Services can be composed in a compositional manner.
2) Brief analysis of source code
The source code address is hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service
in Service
the interface of .
It defines the four states of the service, as well as the state transition, information acquisition, registration and other methods that need to be implemented.
public interface Service extends Closeable {
public enum STATE {
NOTINITED(0, "NOTINITED"),
INITED(1, "INITED"),
STARTED(2, "STARTED"),
STOPPED(3, "STOPPED");
}
void init(Configuration config);
void start();
void stop();
void close() throws IOException;
void registerServiceListener(ServiceStateChangeListener listener);
// ......
The abstract class AbstractService
implements Service
the interface and provides the basic Service
implementation . Non-composition services can directly inherit this abstract class and then develop it.
public abstract class AbstractService implements Service {
// 以 start 实现为例,执行后会触发其他的操作
public void start() {
if (isInState(STATE.STARTED)) {
return;
}
//enter the started state
synchronized (stateChangeLock) {
if (stateModel.enterState(STATE.STARTED) != STATE.STARTED) {
try {
startTime = System.currentTimeMillis();
serviceStart();
if (isInState(STATE.STARTED)) {
//if the service started (and isn't now in a later state), notify
if (LOG.isDebugEnabled()) {
LOG.debug("Service " + getName() + " is started");
}
notifyListeners();
}
} catch (Exception e) {
noteFailure(e);
ServiceOperations.stopQuietly(LOG, this);
throw ServiceStateException.convert(e);
}
}
}
}
// ......
For combined services such as ResourceManager, NodeManager, etc., inheritance is required CompositeService
. There will be logic processing for composite services.
public List<Service> getServices() {
synchronized (serviceList) {
return new ArrayList<Service>(serviceList);
}
}
protected void addService(Service service) {
if (LOG.isDebugEnabled()) {
LOG.debug("Adding service " + service.getName());
}
synchronized (serviceList) {
serviceList.add(service);
}
}
2. Event library
The problem with traditional function calls:
the entire execution process is serial and synchronous. When calling another function, you need to wait for the function to complete before continuing. The schematic diagram is as follows:
In order to solve the problem of functional calls, the programming model of **"event-driven"** can be used.
- All objects are abstracted into event handlers
- Event handlers are associated with each other through events
- Each event handler handles an event
- Another event is fired as needed
- The processing of each type of event can be divided into multiple steps, represented by a finite state machine
- The important thing is that there is a **"Central Asynchronous Dispatcher (AsyncDispatcher)", ** responsible for collecting and distributing events to be processed
The schematic diagram is as follows:
Through the above method, the program can have the characteristics of low coupling and high cohesion. Each module only needs to complete its own function, and at the same time improve the execution efficiency. The split operation can be sent out through events.
3. Use cases of service library and event library
This section will implement a simplified version MapReduce ApplicationMaster
to help understand how to use service and event.
Similar to MR, a job will be divided into multiple tasks for execution. So events involving both job and task objects. And have a AsyncDispatcher
handle dispatch.
The case has been uploaded to github, please click ⭐️
https://github.com/Simon-Ace/hadoop-yarn-study-demo/tree/master/service-event-demo
a) Events section
Refer to the implementation of Task and Job Event in hadoop source code to make some simplifications.
1. task
public enum TaskEventType {
//Producer:Client, Job
T_KILL,
//Producer:Job
T_SCHEDULE
}
public class TaskEvent extends AbstractEvent<TaskEventType> {
private String taskID;
public TaskEvent(String taskID, TaskEventType type) {
super(type);
this.taskID = taskID;
}
public String getTaskID() {
return taskID;
}
}
2、job
public enum JobEventType {
//Producer:Client
JOB_KILL,
//Producer:MRAppMaster
JOB_INIT
}
public class JobEvent extends AbstractEvent<JobEventType> {
private String jobID;
public JobEvent(String jobID, JobEventType type) {
super(type);
this.jobID = jobID;
}
public String getJobId() {
return jobID;
}
}
2) Event Scheduler
- Define and register EventDispatcher
- service initialization and start method
import com.shuofxz.event.JobEvent;
import com.shuofxz.event.JobEventType;
import com.shuofxz.event.TaskEvent;
import com.shuofxz.event.TaskEventType;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.service.CompositeService;
import org.apache.hadoop.service.Service;
import org.apache.hadoop.yarn.event.AsyncDispatcher;
import org.apache.hadoop.yarn.event.Dispatcher;
import org.apache.hadoop.yarn.event.EventHandler;
@SuppressWarnings("unchecked")
public class MyMRAppMaster extends CompositeService {
private Dispatcher dispatcher; // AsyncDispatcher
private String jobID;
private int taskNumber; // 一个 job 包含的 task 数
private String[] taskIDs;
public MyMRAppMaster(String name, String jobID, int taskNumber) {
super(name);
this.jobID = jobID;
this.taskNumber = taskNumber;
taskIDs = new String[taskNumber];
for (int i = 0; i < taskNumber; i++) {
taskIDs[i] = this.jobID + "_task_" + i;
}
}
public void serviceInit(Configuration conf) throws Exception {
dispatcher = new AsyncDispatcher();
dispatcher.register(JobEventType.class, new JobEventDispatcher()); // register a job
dispatcher.register(TaskEventType.class, new TaskEventDispatcher()); // register a task
addService((Service) dispatcher);
super.serviceInit(conf);
}
public void serviceStart() throws Exception {
super.serviceStart();
}
public Dispatcher getDispatcher() {
return dispatcher;
}
private class JobEventDispatcher implements EventHandler<JobEvent> {
public void handle(JobEvent event) {
if (event.getType() == JobEventType.JOB_KILL) {
System.out.println("Receive JOB_KILL event, killing all the tasks");
for (int i = 0; i < taskNumber; i++) {
dispatcher.getEventHandler().handle(new TaskEvent(taskIDs[i], TaskEventType.T_KILL));
}
} else if (event.getType() == JobEventType.JOB_INIT) {
System.out.println("Receive JOB_INIT event, scheduling tasks");
for (int i = 0; i < taskNumber; i++) {
dispatcher.getEventHandler().handle(new TaskEvent(taskIDs[i], TaskEventType.T_SCHEDULE));
}
}
}
}
private class TaskEventDispatcher implements EventHandler<TaskEvent> {
public void handle(TaskEvent event) {
if (event.getType() == TaskEventType.T_KILL) {
System.out.println("Receive T_KILL event of task id " + event.getTaskID());
} else if (event.getType() == TaskEventType.T_SCHEDULE) {
System.out.println("Receive T_SCHEDULE event of task id " + event.getTaskID());
}
}
}
}
3) Test procedure
- generate a new job
- trigger event
JOB_KILL
andJOB_INIT
public class MyMRAppMasterTest {
public static void main(String[] args) {
String jobID = "job_20221011_99";
MyMRAppMaster appMaster = new MyMRAppMaster("My MRAppMaster Test", jobID, 10);
YarnConfiguration conf = new YarnConfiguration(new Configuration());
try {
appMaster.serviceInit(conf);
appMaster.serviceStart();
} catch (Exception e) {
e.printStackTrace();
}
appMaster.getDispatcher().getEventHandler().handle(new JobEvent(jobID, JobEventType.JOB_KILL));
appMaster.getDispatcher().getEventHandler().handle(new JobEvent(jobID, JobEventType.JOB_INIT));
}
}
Output result:
Receive JOB_KILL event, killing all the tasks
Receive JOB_INIT event, scheduling tasks
Receive T_KILL event of task id job_20150723_11_task_0
Receive T_KILL event of task id job_20150723_11_task_1
Receive T_KILL event of task id job_20150723_11_task_2
Receive T_KILL event of task id job_20150723_11_task_3
Receive T_KILL event of task id job_20150723_11_task_4
Receive T_KILL event of task id job_20150723_11_task_5
Receive T_KILL event of task id job_20150723_11_task_6
Receive T_KILL event of task id job_20150723_11_task_7
Receive T_KILL event of task id job_20150723_11_task_8
Receive T_KILL event of task id job_20150723_11_task_9
Receive T_SCHEDULE event of task id job_20150723_11_task_0
Receive T_SCHEDULE event of task id job_20150723_11_task_1
Receive T_SCHEDULE event of task id job_20150723_11_task_2
Receive T_SCHEDULE event of task id job_20150723_11_task_3
Receive T_SCHEDULE event of task id job_20150723_11_task_4
Receive T_SCHEDULE event of task id job_20150723_11_task_5
Receive T_SCHEDULE event of task id job_20150723_11_task_6
Receive T_SCHEDULE event of task id job_20150723_11_task_7
Receive T_SCHEDULE event of task id job_20150723_11_task_8
Receive T_SCHEDULE event of task id job_20150723_11_task_9
Four. Summary
This section introduces Yarn's service and event libraries.
The service library standardizes service-type objects with a long life cycle, defines the four states of the service, the methods to be realized such as start-stop registration, and gives the basic implementation of single-type and combined-type services.
The use of the event library solves the problems of high coupling and blocking inefficiency of the original function call. A large task can be split into multiple small tasks, and small tasks become different events to trigger processing. Each event handler handles one type of event, and a central asynchronous scheduler manages event collection and distribution.
Finally, a simplified MR ApplicationMaster is used to combine the event library and the service library, so as to gain a deeper understanding of how to use them together in the project.
During the learning process, writing a demo can better help you understand the knowledge.
Reference article:
"Hadoop Technology Insider - In-depth Analysis of Yarn Structure Design and Implementation Principles" Section 3.4