FairScheduler(公平调度器)的源码阅读

FairScheduler hadoop 中的作业公平调度器,主要是解决当 TT 发送心跳告诉 JT 当前的空闲 slots 时,希望 JT 分配给 TT 相应多个 task ,让 TT 去执行这些 task 。所以 JT 就需要一个调度器来对作业进行调度,选择出作业,然后将作业的 task 分配 TT 去执行。

hadoop 中的 task 可以分为 map,reduce,jobsetup,jobcleanup,taskcleanup 这五种 task

关于 setup,cleanup task 的信息在

https://issues.apache.org/jira/browse/MAPREDUCE-463 hadoop-5785

分别是为 job 做准备工作,为 job 做清理工作,清理 task 工作。

一个 job 初始化时,依次产生 map task,reduce task,steup and cleanup task

Setup,cleanup 分别有二个,对应于 map reduce 的。

比如 jobcleanup 工作主要是删除输出的临时文件夹,因为 mapreduce 为了保证正确性,数据会先写到输出文件夹下面的 _temporary 文件下 , 如果正确执行的话,最终会将临时文件夹下的数据重命名为真正的输出文件夹下,而这个重命名在 hadoop 中的代价比较小。

Jobstepup 的原理也是一样,即先创建起相应的输出的临时文件夹,用于数据存储。

那么到底分配哪个 job 中的 map,reduce task TT 去执行呢?这就是调度器要做的事情。

首先将用户分成多个组,每个组中的用户是相互独立的,每个组都有一定的容量大小,并且有实际的 slots 数目,根据这些来决定当前哪个组可以分配 task ,然后再到该组中的选择一个用户的 job 来分配。

FairScheduler

继承了 TaskScheduler 类。主有要变量 Configuration, TaskTrackerManager 二个变量,另外有方法:start(),terminate(),assignTasks(TaskTracker)

Start : 是由Jobtracker 启动的调用它,用来初始化调度器。

terminate() :停止工作。

assignTasks(TaskTracker) :为某个TT 分配task 队列。

Start

首先,创建日志监听器 eventLog = new FairSchedulerEventLog(); 将调度器的动作写到日志中,用于为后面决策作分析。

添加二个job 监听对象。

  taskTrackerManager .addJobInProgressListener( jobListener );

      if (! mockMode ) {

        eagerInitListener = new EagerTaskInitializationListener(conf);

        eagerInitListener .setTaskTrackerManager( taskTrackerManager );

        eagerInitListener .start();

        taskTrackerManager .addJobInProgressListener( eagerInitListener );

      }

JobListener 对象是从调度器中添加或删除job ,继续JobInProgressListener 接口的方法。

   public void jobAdded(JobInProgress job) {

      synchronized (FairScheduler. this ) {

        eventLog .log( "JOB_ADDED" , job.getJobID());

        JobInfo info = new JobInfo( new JobSchedulable(FairScheduler. this , job, TaskType. MAP ),

            new JobSchedulable(FairScheduler. this , job, TaskType. REDUCE ));

        infos .put(job, info);

        poolMgr .addJob(job); // Also adds job into the right PoolScheduable

        update();

      }

    }

EagerTaskInitializationListener 对象启动了一个线程池来对job 队列进行实始化工作。

接下来创建组管理和对TT 的负载管理,最后初始化一些环境参数。

poolMgr = new PoolManager( this );

      poolMgr .initialize();

      loadMgr = (LoadManager) ReflectionUtils.newInstance (

          conf.getClass( "mapred.fairscheduler.loadmanager" ,

              CapBasedLoadManager. class , LoadManager. class ), conf);

      loadMgr .setTaskTrackerManager( taskTrackerManager );

      loadMgr .setEventLog( eventLog );

      loadMgr .start();

最后是启动一个 UpdateThread 的守护线程,另外创建好 servlet ,提供给给用户实时查看相关的调度信息。

   if (! mockMode ) {

        new UpdateThread ().start();

      }

    // Register servlet with JobTracker's Jetty server

      if ( taskTrackerManager instanceof JobTracker) {

        JobTracker jobTracker = (JobTracker) taskTrackerManager ;

        HttpServer infoServer = jobTracker. infoServer ;

        infoServer.setAttribute( "scheduler" , this );

        infoServer.addServlet( "scheduler" , "/scheduler" ,

            FairSchedulerServlet. class );

      }

UpdateThread 线程每隔 mapred.fairscheduler.update.interval 时间去计算调度的相关参数,如每个job 权重,公平性,当前运行的数目等。

然后就是将当前的job 运行参数dumplog 中,用于分析和决策。

计算需要被抢占的task 数目。

    public void run() {

      while ( running ) {

        try {

          Thread.sleep ( updateInterval );

          update();

           dumpIfNecessary();

          preemptTasksIfNecessary();

        } catch (Exception e) {

          LOG .error( "Exception in fair scheduler UpdateThread" , e);

        }

      }

    }

  }

 

JobListener jobAdded()

jobtrackerjob 提交后,该方法会被调用。

job 信息存到map 中,同时调用组管理器将job 加入到组中,

最后调用update() 方法更新调度器中的相关参数。

public void jobAdded(JobInProgress job) {

      synchronized (FairScheduler. this ) {

        eventLog .log( "JOB_ADDED" , job.getJobID());

        JobInfo info = new JobInfo( new JobSchedulable(FairScheduler. this , job, TaskType. MAP ),

            new JobSchedulable(FairScheduler. this , job, TaskType. REDUCE ));

        infos .put(job, info);

        poolMgr .addJob(job); // Also adds job into the right PoolScheduable

        update() ;

      }

    }

 

assignTasks()

根据每个组的情况,计算出当前集群的map,reduce 运行数目。

    for (Pool pool: poolMgr .getPools()) {

      runnableMaps += pool.getMapSchedulable().getDemand();

      runningMaps += pool.getMapSchedulable().getRunningTasks();

      runnableReduces += pool.getReduceSchedulable().getDemand();

       runningReduces += pool.getReduceSchedulable().getRunningTasks();

    }

得到集群中所有 TT 加起来总的 map,reduce slots 数目。

    int totalMapSlots = getTotalSlots(TaskType. MAP , clusterStatus);

    int totalReduceSlots = getTotalSlots(TaskType. REDUCE , clusterStatus);

利用排序算法对各个组进行排序。

  // Get the map or reduce schedulables and sort them by fair sharing

      List<PoolSchedulable> scheds = getPoolSchedulables(taskType);

      Collections.sort (scheds, new SchedulingAlgorithms.FairShareComparator());

对于排好序的组遍历,获得相应的 map,reduce task 。而在调用组的 assignTask() 方法时,会先对组内的 job 队列应用调度算法进行排序,然后再选择一个 job ,调用该 job assignTask() 方法,这样就可以得到某个 job task 任务了。

for (Schedulable sched: scheds) { // This loop will assign only one task

        eventLog .log( "INFO" , "Checking for " + taskType +

            " task in " + sched.getName());

        Task task = taskType == TaskType. MAP ?

                    sched.assignTask(tts, currentTime, visitedForMap) :

                    sched.assignTask(tts, currentTime, visitedForReduce);

        if (task != null ) {

           foundTask = true ;

          JobInProgress job = taskTrackerManager .getJob(task.getJobID());

          eventLog .log( "ASSIGN" , trackerName, taskType,

              job.getJobID(), task.getTaskID());

          // Update running task counts, and the job's locality level

          if (taskType == TaskType. MAP ) {

            launchedMap.add(job);

            mapsAssigned++;

            runningMaps++;

            updateLastMapLocalityLevel(job, task, tts);

          } else {

            reducesAssigned++;

             runningReduces++;

          }

          // Add task to the list of assignments

          tasks.add(task);

          break ; // This break makes this loop assign only one task

        } // end if(task != null)

      } // end for(Schedulable sched: scheds)

 

update()

这个方法是一个很重要的方法,就是用来 更新公平调度器的相关参数。

如:读取最新的配置参数,删除已经完成或者被kill 掉的job

更新job,pool 的最新需求。

计算每个组的map,reduce 的公平性。

更新每个组的时间优先级顺序。

当要将 job 加入到调度器时,以及调度器每隔一段时间会调用这个方法,去更新公平调度器的相关信息。分别在 JobListener 类中的jobAdded() 方法和UpdateThread 守护线程中调用。

 

PoolManager

管理调度中的所有组,即 Pool

记录调度中的每个组,一种映射关系,即组名与组对象。

private Map<String, Pool> pools = new HashMap<String, Pool>();

 

addJob(JobInProgress job)

当有job 来了,就将其加入到相应的组中。

public synchronized void addJob(JobInProgress job) {

    getPool(getPoolName(job)).addJob(job);

  }

如果该组存在,则直接得到 Pool 对象,然后将 job 加入到组中。

如果组不存在,则创建一个 Pool 对象,将 job 加入到组后,再将组加入到组管理对象的队列中。

  public synchronized Pool getPool(String name) {

    Pool pool = pools .get(name);

    if (pool == null ) {

      pool = new Pool( scheduler , name);

      pool.setSchedulingMode( defaultSchedulingMode );

      pools .put(name, pool);

    }

    return pool;

  }

reloadAllocs()

 

reloadAllocsIfNecessary()

 

Pool

组名,该组中的job, 以及组内的map,reduce task 的调度算法。

保存该组的job 队列。

private Collection<JobInProgress> jobs = new ArrayList<JobInProgress>();

该组的调度算法。

  /** Scheduling mode for jobs inside the pool (fair or FIFO) */

  private SchedulingMode schedulingMode ;

组的map,reduce 调度。

  private PoolSchedulable mapSchedulable ;

  private PoolSchedulable reduceSchedulable ;

 

addJob()

就是将job 加入到组队列,以及组的调度中。

public void addJob(JobInProgress job) {

    jobs .add(job);

    mapSchedulable .addJob(job);

    reduceSchedulable .addJob(job);

  }

 

PoolSchedulable

负责管理组内 job map,reduce 调度问题。

实现了Schedulable 接口。

主要负责三件事情:

实现了assignTask() 接口,从多个组中分配taskTT

提供了job/pool 信息给调度器,包括当前需求数,当前运行数,每个组的最小共享数,job/pool 的权重以及起始时间,优先级等。

同时对于每个用户来适用于公平调度算法。

addJob(JobInProgress)

PoolSchedulable 分为mapreduce 二种,在Pool 构造时建立起来。

  public Pool(FairScheduler scheduler, String name) {

    this . name = name;

    mapSchedulable = new PoolSchedulable(scheduler, this , TaskType. MAP );

    reduceSchedulable = new PoolSchedulable(scheduler, this , TaskType. REDUCE );

  }

 

assignTask()

当公平调度选择了该组后,具体选择组内的哪个 job ,就由 PoolSchedulable 的该方法来完成。

仔细研读一下,步骤如下:

首先得到该组当前运行的 task 数目,如果当前运行数目大于公平调度给的最大 slots 数,那么返回 null

否则可以从该组中分配任务,得到该组的调度方式,有二种: FIFO,FAIR 。这个方式是针对 job 来说的。

然后将调度方式应用于当前的组内的所有 JobSchedulable 队列中的job

最后遍历所有 JobSchedulable 队列,调用jobJobSchedulable 对象中的assignTask 方法,从job 中获得task

  public Task assignTask(TaskTrackerStatus tts, long currentTime,

      Collection<JobInProgress> visited) throws IOException {

     int runningTasks = getRunningTasks();

    if (runningTasks >= poolMgr .getMaxSlots( pool .getName(), taskType )) {

      return null ;

    }

    SchedulingMode mode = pool .getSchedulingMode();

    Comparator<Schedulable> comparator;

    if (mode == SchedulingMode. FIFO ) {

      comparator = new SchedulingAlgorithms .FifoComparator();

    } else if (mode == SchedulingMode. FAIR ) {

      comparator = new SchedulingAlgorithms.FairShareComparator();

    } else {

      throw new RuntimeException( "Unsupported pool scheduling mode " + mode);

    }

    Collections.sort ( jobScheds , comparator);

    for (JobSchedulable sched: jobScheds ) {

      Task task = sched.assignTask (tts, currentTime, visited);

      if (task != null )

        return task;

    }

    return null ;

  }

 

 

JobSchedulable

Job 的调度管理。

主要变量有:

  公平调度对象,job 对象,task 类型,有map,reduce 二种。

private FairScheduler scheduler ;

  private JobInProgress job ;

  private TaskType taskType ;

  private int demand = 0;

assignTask ()

就是从该job 中,获得相应的map,reduce task

public Task assignTask(TaskTrackerStatus tts, long currentTime,

      Collection<JobInProgress> visited) throws IOException {

    if (isRunnable()) {

      visited.add( job );

      TaskTrackerManager ttm = scheduler . taskTrackerManager ;

      ClusterStatus clusterStatus = ttm.getClusterStatus();

      int numTaskTrackers = clusterStatus.getTaskTrackers();

 

      // check with the load manager whether it is safe to

      // launch this task on this taskTracker.

      LoadManager loadMgr = scheduler .getLoadManager();

      if (!loadMgr.canLaunchTask(tts, job , taskType )) {

        return null ;

      }

      if ( taskType == TaskType. MAP ) {

        LocalityLevel localityLevel = scheduler .getAllowedLocalityLevel(

            job , currentTime);

        scheduler .getEventLog().log(

             "ALLOWED_LOC_LEVEL" , job .getJobID(), localityLevel);

        // obtainNewMapTask needs to be passed 1 + the desired locality level

        return job .obtainNewMapTask(tts, numTaskTrackers,

            ttm.getNumberOfUniqueHosts(), localityLevel.toCacheLevelCap());

      } else {

        return job .obtainNewReduceTask(tts, numTaskTrackers,

            ttm.getNumberOfUniqueHosts());

      }

    } else {

      return null ;

    }

  }

 

SchedulingAlgorithms

该类中有二个类:FairShareComparorFifoComparator 。分别实现了公平调度和先来服务算法。

二个类主要实现了一个接口。

public int compare(Schedulable s1, Schedulable s2) {}

这样可以对一个组内的job 进行排序,或者对各个组进行排序。

下面看一下公平调度的比较类 FairShareComparator ,即公平调度器是如何去选择哪个组来分配 task 任务。

判断当前运行的任务数目小于最小共享数目。

如果二个组都小于,则比较最小共享占比。即当前运行的数 / 该组的最小共享数。

如果二个组都大于,则比较权重比。即运行任务数 / 权重系数。

如果二者还是相等,则根据起始时间来比较。

 

public int compare(Schedulable s1, Schedulable s2) {

      double minShareRatio1, minShareRatio2;

      double tasksToWeightRatio1, tasksToWeightRatio2;

      int minShare1 = Math.min (s1.getMinShare(), s1.getDemand());

      int minShare2 = Math.min (s2.getMinShare(), s2.getDemand());

      boolean s1Needy = s1.getRunningTasks() < minShare1;

      boolean s2Needy = s2.getRunningTasks() < minShare2;

      minShareRatio1 = s1.getRunningTasks() / Math.max (minShare1, 1.0);

      minShareRatio2 = s2.getRunningTasks() / Math.max (minShare2, 1.0);

      tasksToWeightRatio1 = s1.getRunningTasks() / s1.getWeight();

      tasksToWeightRatio2 = s2.getRunningTasks() / s2.getWeight();

      int res = 0;

      if (s1Needy && !s2Needy)

        res = -1;

      else if (s2Needy && !s1Needy)

        res = 1;

      else if (s1Needy && s2Needy)

        res = ( int ) Math.signum (minShareRatio1 - minShareRatio2);

      else // Neither schedulable is needy

        res = ( int ) Math.signum (tasksToWeightRatio1 - tasksToWeightRatio2);

      if (res == 0) {

        // Jobs are tied in fairness ratio. Break the tie by submit time and job

        // name to get a deterministic ordering, which is useful for unit tests.

        res = ( int ) Math.signum (s1.getStartTime() - s2.getStartTime());

        if (res == 0)

          res = s1.getName().compareTo(s2.getName());

      }

      return res;

    }

 

 

到此,把公平调度的基本框架了解了一下,具体的实现还是要看里面的具体源代码,才能真正掌握具体。另外就是官方的文档, http://hadoop.apache.org/common/docs/r0.20.2/fair_scheduler.html,

以及 Hadoop Fair Scheduler Design Document

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值