Dans un immense système distribué, comment les différents composants coordonnent-ils leur travail ? Comment les composants sont-ils découplés ? Comment les threads peuvent-ils fonctionner plus efficacement et réduire les inefficacités causées par le blocage ? Cette section présentera la bibliothèque de services et la bibliothèque d'événements de Yarn, et verra comment Yarn résout ces problèmes.
1. Bibliothèque de services
1. Introduction
Pour les objets à long cycle de vie, Yarn adopte un modèle basé sur les services pour les gérer, qui présente les caractéristiques suivantes :
- Gestion par état : divisée en 4 états :
NOTINITED
(créé),INITED
(initialisé),STARTED
(démarré),STOPPED
(arrêté). - Les modifications de l'état du service déclenchent d'autres actions.
- Les services peuvent être composés de manière compositionnelle.
2) Brève analyse du code source
L'adresse du code source se trouve hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service
dans Service
l'interface de .
Il définit les quatre états du service, ainsi que la transition d'état, l'acquisition d'informations, l'enregistrement et d'autres méthodes qui doivent être mises en œuvre.
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);
// ......
La classe abstraite AbstractService
implémente Service
l'interface et fournit l' Service
implémentation Les services non-composition peuvent directement hériter de cette classe abstraite et ensuite la développer.
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);
}
}
}
}
// ......
Pour les services combinés tels que ResourceManager, NodeManager, etc., l'héritage est requis CompositeService
. Il y aura un traitement logique pour les services composites.
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. Bibliothèque d'événements
Le problème avec les appels de fonction traditionnels :
l'ensemble du processus d'exécution est en série et synchrone. Lorsque vous appelez une autre fonction, vous devez attendre que la fonction se termine avant de continuer. Le schéma de principe est le suivant :
Afin de résoudre le problème des appels fonctionnels, le modèle de programmation **"event-driven"** peut être utilisé.
- Tous les objets sont résumés dans des gestionnaires d'événements
- Les gestionnaires d'événements sont associés les uns aux autres via des événements
- Chaque gestionnaire d'événements gère un événement
- Un autre événement est déclenché si nécessaire
- Le traitement de chaque type d'événement peut être divisé en plusieurs étapes, représentées par une machine à états finis
- L'important est qu'il y ait un **"Central Asynchronous Dispatcher (AsyncDispatcher)", ** responsable de la collecte et de la distribution des événements à traiter
Le schéma de principe est le suivant :
Grâce à la méthode ci-dessus, le programme peut avoir les caractéristiques d'un couplage faible et d'une cohésion élevée. Chaque module n'a besoin que de remplir sa propre fonction et, en même temps, d'améliorer l'efficacité de l'exécution. L'opération de fractionnement peut être envoyée via des événements.
3. Cas d'utilisation de la bibliothèque de services et de la bibliothèque d'événements
Cette section mettra en œuvre une version simplifiée MapReduce ApplicationMaster
pour aider à comprendre comment utiliser le service et l'événement.
Semblable à MR, un travail sera divisé en plusieurs tâches pour l'exécution. Ainsi, les événements impliquant à la fois des objets de travail et de tâche. Et avoir une AsyncDispatcher
poignée d'expédition.
Le cas a été téléchargé sur github, veuillez cliquer sur ⭐️
https://github.com/Simon-Ace/hadoop-yarn-study-demo/tree/master/service-event-demo
a) Rubrique événements
Reportez-vous à l'implémentation de Task et Job Event dans le code source hadoop pour apporter quelques simplifications.
1. tâche
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, travail
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) Planificateur d'événements
- Définir et enregistrer EventDispatcher
- méthode d'initialisation et de démarrage du service
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) Procédure d'essai
- générer un nouveau travail
- événement déclencheur
JOB_KILL
etJOB_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));
}
}
Résultat de sortie :
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
4. Résumé
Cette section présente les bibliothèques de services et d'événements de Yarn.
La bibliothèque de services standardise les objets de type service avec un long cycle de vie, définit les quatre états du service, les méthodes à réaliser telles que l'enregistrement start-stop, et donne l'implémentation de base des services de type unique et de type combiné.
L'utilisation de la bibliothèque d'événements résout les problèmes de couplage élevé et d'inefficacité bloquante de l'appel de fonction d'origine. Une grande tâche peut être divisée en plusieurs petites tâches, et les petites tâches deviennent des événements différents pour déclencher le traitement. Chaque gestionnaire d'événements gère un type d'événement et un planificateur asynchrone central gère la collecte et la distribution des événements.
Enfin, un MR ApplicationMaster simplifié est utilisé pour combiner la bibliothèque d'événements et la bibliothèque de services, afin de mieux comprendre comment les utiliser ensemble dans le projet.
Pendant le processus d'apprentissage, écrire une démo peut mieux vous aider à comprendre les connaissances.
Article de référence :
"Hadoop Technology Insider - Analyse approfondie des principes de conception et de mise en œuvre de la structure du fil" Section 3.4