YARN 调度器负载模拟器 (SLS)

概述

概述

YARN调度器是一个备受关注的领域,拥有多种实现方案,例如FIFO、容量调度器和公平调度器。同时,针对不同场景和工作负载也进行了多项优化以提升调度器性能。每种调度算法都具备独特的特性集,并通过公平性、容量保证、资源可用性等多重因素驱动调度决策。在生产集群部署前充分评估调度算法至关重要。然而目前评估调度算法并非易事——在真实集群中进行评估既耗时又昂贵,且很难找到足够大规模的集群。因此,能够预测特定工作负载下调度算法表现的模拟器将极具实用价值。

YARN调度器负载模拟器(SLS)就是这样一款工具,它能够在单台机器上模拟大规模YARN集群和应用负载。该模拟器通过为研究人员和开发者提供一个工具来原型化新的调度器功能,并以合理可信度预测其行为和性能,从而促进YARN的发展,这对推动快速创新具有不可估量的价值。该模拟器将通过处理和调度来自同一JVM内的NodeManagersApplicationMasters心跳事件来运行真实的YARNResourceManager,从而消除网络因素。为了持续跟踪调度器行为和性能,将使用一个调度器包装器来封装真实调度器。

集群规模和应用程序负载可以从配置文件中加载,这些配置文件是通过采用Apache Rumen直接从作业历史文件生成的。

模拟器在执行过程中会实时生成指标,包括:

  • 整个集群及各队列的资源使用情况,可用于配置集群和队列的容量。

  • 详细的应用程序执行跟踪(基于模拟时间记录),可用于分析以理解/验证调度器行为(单个作业周转时间、吞吐量、公平性、容量保证等)。

  • 调度器算法的几个关键指标,例如每个调度操作(分配、处理等)的时间成本,Hadoop开发者可以利用这些指标来定位代码热点和可扩展性限制。

目标

  • 使用真实的作业跟踪数据,在没有真实集群的情况下大规模测试调度器。

  • 能够模拟真实的工作负载。

架构

下图展示了模拟器的实现架构。

The architecture of the simulator

模拟器接收工作负载跟踪或合成负载分布作为输入,并生成集群和应用程序信息。对于每个节点管理器(NM)和应用管理器(AM),模拟器会构建一个对应的模拟器来模拟它们的运行。所有NM/AM模拟器都在一个线程池中运行。该模拟器复用YARN资源管理器,并在调度器外部构建了一个封装层。这个调度器封装层能够跟踪调度器的行为并生成若干日志,这些日志是模拟器的输出结果,可供进一步分析使用。

应用场景

  • 工程

    • 验证调度算法在负载下的正确性
    • 查找代码热点/关键路径的经济实用方法。
    • 验证变更和新功能的影响。
    • 确定影响调度器可扩展性限制的因素。
  • 问答

    • 验证“大型”集群和多种工作负载配置下的调度器行为。
  • 解决方案/销售。

    • 针对预定义/典型工作负载的规模调整模型。
    • 使用真实客户数据(作业跟踪)的集群规模调整工具。
    • 确定特定工作负载下的最低服务等级协议(SLA)。

使用说明

This section will show how to use the simulator. Here let $HADOOP_ROOT represent the Hadoop install directory. If you build Hadoop yourself, $HADOOP_ROOT is hadoop-dist/target/hadoop-$VERSION. The simulator is located at $HADOOP_ROOT/share/hadoop/tools/sls. The fold sls containers four directories: bin, html, sample-conf, and sample-data

  • bin: 包含模拟器的运行脚本。

  • html: Users can also reproduce those real-time tracking charts in offline mode. Just upload the realtimetrack.json to $HADOOP_ROOT/share/hadoop/tools/sls/html/showSimulationTrace.html. For browser security problem, need to put files realtimetrack.json and showSimulationTrace.html in the same directory.

  • sample-conf: 指定模拟器的配置。

  • sample-data: 提供一个示例的rumen跟踪数据,可用于生成模拟器的输入。

以下部分将逐步描述如何使用模拟器。开始前,请确保您的$PATH环境参数中包含hadoop命令。

步骤1:配置Hadoop和模拟器

在开始之前,请确保Hadoop和模拟器已正确配置。Hadoop和模拟器的所有配置文件都应放置在目录$HADOOP_ROOT/etc/hadoop中,ResourceManager和YARN调度器将从该目录加载配置。目录$HADOOP_ROOT/share/hadoop/tools/sls/sample-conf/提供了几个示例配置,可用于启动演示。

关于Hadoop和YARN调度器的配置,用户可以参阅Yarn官网(http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/)。

对于模拟器,它会从文件$HADOOP_ROOT/etc/hadoop/sls-runner.xml加载配置信息。

这里我们逐一说明sls-runner.xml中的每个配置参数。请注意$HADOOP_ROOT/share/hadoop/tools/sls/sample-conf/sls-runner.xml文件包含了这些配置参数的所有默认值。

  • yarn.sls.runner.pool.size

    模拟器使用线程池来模拟NMAM的运行,此参数指定池中的线程数。

  • yarn.sls.nm.memory.mb

    每个NMSimulator的总内存。

  • yarn.sls.nm.vcores

    每个NMSimulator的总虚拟CPU核心数。

  • yarn.sls.nm.heartbeat.interval.ms

    每个NMSimulator的心跳间隔时间。

  • yarn.sls.am.heartbeat.interval.ms

    每个AMSimulator的心跳间隔时间。

  • yarn.sls.am.type.mapreduce

    用于类似MapReduce应用的AMSimulator实现。用户可以指定其他类型应用的实现。

  • yarn.sls.container.memory.mb

    每个容器模拟器所需的内存。

  • yarn.sls.container.vcores

    每个容器模拟器所需的虚拟CPU核心数。

  • yarn.sls.runner.metrics.switch

    模拟器引入了Metrics来衡量关键组件和操作的行为。该字段指定我们是开启(ON)还是关闭(OFF)Metrics运行。

  • yarn.sls.metrics.web.address.port

    模拟器用于提供实时跟踪的端口。默认值为10001。

  • org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler

    Fifo调度器的调度指标实现。

  • org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler

    公平调度器的调度器指标实现。

  • org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler

    Capacity Scheduler调度器指标的实现。

步骤2:运行模拟器

模拟器支持两种类型的输入文件:Rumen跟踪记录及其自身的输入跟踪记录。启动模拟器的脚本是slsrun.sh

$ cd $HADOOP_ROOT/share/hadoop/tools/sls
$ bin/slsrun.sh
  Usage: slsrun.sh <OPTIONS>
             --tracetype=<SYNTH | SLS | RUMEN>
             --tracelocation=<FILE1,FILE2,...>
             (deprecated --input-rumen=<FILE1,FILE2,...>  | --input-sls=<FILE1,FILE2,...>)
             --output-dir=<SLS_SIMULATION_OUTPUT_DIRECTORY>
             [--nodes=<SLS_NODES_FILE>]
             [--track-jobs=<JOBID1,JOBID2,...>]
             [--print-simulation]
  • --input-rumen: 输入rumen跟踪文件。用户可以输入多个文件,用逗号分隔。示例跟踪文件位于$HADOOP_ROOT/share/hadoop/tools/sls/sample-data/2jobs2min-rumen-jh.json。这等同于--tracetype=RUMEN --tracelocation=

  • --input-sls: 模拟器自身的文件格式。模拟器还提供了一个工具将rumen跟踪转换为sls跟踪(rumen2sls.sh)。关于sls输入json文件的示例请参阅附录。这等同于--tracetype=SLS --tracelocation=

  • --tracetype: 这是配置跟踪生成的新方式,可取值RUMEN、SLS或SYNTH,用于触发三种类型的负载生成

  • --tracelocation: 输入文件的路径,需与上述跟踪类型匹配。

  • --output-dir: 生成运行日志和指标的输出目录。

  • --nodes: 集群拓扑结构。默认情况下,模拟器会使用从输入json文件中获取的拓扑结构。用户可以通过设置此参数来指定新的拓扑结构。拓扑结构文件格式请参阅附录。

  • --track-jobs: 在模拟器运行期间将被追踪的特定作业,以逗号分隔。

  • --print-simulation: 是否在模拟器运行前打印模拟信息,包括节点数量、应用程序数量、任务数量以及每个应用程序的信息。

    与rumen格式相比,这里的sls格式更加简单,用户可以轻松生成各种工作负载。模拟器还提供了一个工具将rumen跟踪记录转换为sls跟踪记录。

    $ bin/rumen2sls.sh
      --rumen-file=
      --output-dir=
        [--output-prefix=]
    
  • --rumen-file: Rumen格式文件。在sample-data目录中提供了一个示例跟踪文件。

  • --output-dir: 生成模拟跟踪数据的输出目录。该输出目录下将生成两个文件:一个包含所有作业和任务信息的跟踪文件,另一个显示拓扑信息的文件。

  • --output-prefix: 生成文件的前缀。默认值为"sls",生成的两个文件分别是sls-jobs.jsonsls-nodes.json

指标

YARN调度器负载模拟器集成了Metrics来测量关键组件和操作的行为,包括运行中的应用程序和容器、集群可用资源、调度器操作耗时等。如果将开关yarn.sls.runner.metrics.switch设置为ONMetrics将会运行并在用户指定的--output-dir目录中输出日志。用户可以在模拟器运行期间跟踪这些信息,也可以在运行后分析这些日志以评估调度器性能。

实时追踪

模拟器提供了一个实时跟踪其运行的界面。用户可以访问http://host:port/simulate来跟踪整个运行过程,或者访问http://host:port/track来跟踪特定作业或队列。这里的host是运行模拟器的主机地址,port是由yarn.sls.metrics.web.address.port配置的端口值(默认值为10001)。

这里我们将展示网页中显示的每个图表。

第一张图描述了正在运行的应用程序和容器的数量。

Number of running applications/containers

第二张图描述了集群中已分配和可用的资源(内存)。

Cluster Resource (Memory)

第三张图描述了每个队列分配的资源情况。这里我们有三个队列:sls_queue_1、sls_queue_2和sls_queue_3。前两个队列配置了25%的份额,而最后一个队列拥有50%的份额。

Queue Allocated Resource (Memory)

第四张图描述了每个调度器操作的时间成本。

Scheduler Opertion Timecost

最后,我们测量模拟器使用的内存。

JVM Memory

模拟器还提供了一个接口用于追踪某些特定的作业和队列。访问http://:/track即可获取这些信息。

第一张图展示了队列SLS_Queue_1的资源使用情况。

Tracking Queue sls_queue_3

第二张图展示了作业job_1369942127770_0653的资源使用情况。

Tracking Job job_1369942127770_0653

离线分析

模拟器运行完成后,所有日志将保存在由--output-dir参数指定的输出目录中,该参数位于$HADOOP_ROOT/share/hadoop/tools/sls/bin/slsrun.sh文件内。

  • 文件 realtimetrack.json: 每1秒记录所有实时追踪日志。

  • 文件 jobruntime.csv: 记录模拟器中所有作业的开始和结束时间。

  • 文件夹 metrics: 由Metrics生成的日志。

Users can also reproduce those real-time tracking charts in offline mode. Just upload the realtimetrack.json to $HADOOP_ROOT/share/hadoop/tools/sls/html/showSimulationTrace.html. For browser security problem, need to put files realtimetrack.json and showSimulationTrace.html in the same directory.

合成负载生成器

合成负载生成器通过提供基于分布驱动的负载生成,补充了SLS原生和RUMEN跟踪的广泛特性。该负载生成器被组织为JobStoryProducer(与rumen兼容,因此可与gridmix进行后续集成)。我们为随机数生成器设定种子,使结果随机但具有确定性——因此可重现。我们将生成的作业围绕/workloads/job_class层次结构进行组织,这样可以轻松将具有相似行为的作业分组并分类(例如,具有长时间运行容器的作业,或仅含map的计算等)。用户可以控制许多重要参数的平均值和标准差,例如mapper/reducer的数量、mapper/reducer的持续时间、容器的大小(内存/CPU)、预留机会等。当从少量选项中选择时,我们使用加权随机抽样;当从大范围值中选择时,我们使用对数正态分布(以避免负值)——详见对数正态分布附录。

SLS的SYNTH模式非常方便,无需大量输入文件即可生成非常大的负载。这允许以高效紧凑的方式轻松探索广泛的使用场景(例如,想象模拟10万个作业,并在不同运行中简单地调整平均映射器数量或平均任务持续时间)。

SLS中的资源类型

本节介绍如何在SLS中使用资源类型。

配置资源管理器

这与如何为真实集群配置资源类型相同。在yarn-site.xml中配置项yarn.resource-types,如下例所示。

 <property>
   <name>yarn.resource-types</name>
   <value>resource-type1, resource-type2</value>
 </property>

配置节点管理器

通过在sls-runner.xml中添加相关项来指定每个节点中的资源大小,如下例所示。这些值适用于SLS中的每个节点。除内存和vcores外,其他资源的默认值为0。

 <property>
   <name>yarn.sls.nm.resource-type1</name>
   <value>10</value>
 </property>
 <property>
   <name>yarn.sls.nm.resource-type2</name>
   <value>10</value>
 </property>

在SLS JSON输入中指定资源

资源类型在SLS JSON输入格式中受支持,但在其他两种格式(SYNTH和RUMEN)中不受支持。要在SLS JSON输入格式中使用此功能,您可以为任务容器和AM容器指定资源大小。以下是一个示例。

{
  "job.start.ms" : 0,
  "am.memory-mb": 2048,
  "am.vcores": 2,
  "am.resource-type1": 2,
  "am.resource-type2": 2,
  "job.tasks" : [ {
    "container.duration.ms":  5000
    "container.memory-mb": 1024,
    "container.vcores": 1,
    "container.resource-type1": 1,
    "container.resource-type2": 1
  }
}

附录

资源

YARN-1021 是引入YARN调度器负载模拟器到Hadoop YARN项目的主要JIRA。YARN-6363 是向SLS引入合成负载生成器的主要JIRA。

SLS JSON输入文件格式

这里我们提供一个sls json文件的示例格式,其中包含2个作业。第一个作业有3个map任务,第二个作业有2个map任务。

{
  "num.nodes": 3,  // total number of nodes in the cluster
  "num.racks": 1   // total number of racks in the cluster, it divides num.nodes into the racks evenly, optional, the default value is 1
}
{
  "am.type" : "mapreduce", // type of AM, optional, the default value is "mapreduce"
  "job.start.ms" : 0,      // job start time
  "job.end.ms" : 95375,    // job finish time, optional, the default value is 0
  "job.queue.name" : "sls_queue_1", // the queue job will be submitted to
  "job.id" : "job_1",      // the job id used to track the job, optional. The default value, an zero-based integer increasing with number of jobs, is used if this is not specified or job.count > 1
  "job.user" : "default",  // user, optional, the default value is "default"
  "job.count" : 1,         // number of jobs, optional, the default value is 1
  "job.tasks" : [ {
    "count": 1,    // number of tasks, optional, the default value is 1
    "container.host" : "/default-rack/node1",  // host the container asks for
    "container.start.ms" : 6664,  // container start time, optional
    "container.end.ms" : 23707,   // container finish time, optional
    "container.duration.ms":  50000, // duration of the container, optional if start and end time is specified
    "container.priority" : 20,    // priority of the container, optional, the default value is 20
    "container.type" : "map"      // type of the container, could be "map" or "reduce", optional, the default value is "map"
  }, {
    "container.host" : "/default-rack/node3",
    "container.start.ms" : 6665,
    "container.end.ms" : 21593,
    "container.priority" : 20,
    "container.type" : "map"
  }, {
    "container.host" : "/default-rack/node2",
    "container.start.ms" : 68770,
    "container.end.ms" : 86613,
    "container.priority" : 20,
    "container.type" : "map"
  } ]
}
{
  "am.type" : "mapreduce",
  "job.start.ms" : 105204,
  "job.end.ms" : 197256,
  "job.queue.name" : "sls_queue_2",
  "job.id" : "job_2",
  "job.user" : "default",
  "job.tasks" : [ {
    "container.host" : "/default-rack/node1",
    "container.start.ms" : 111822,
    "container.end.ms" : 133985,
    "container.priority" : 20,
    "container.type" : "map"
  }, {
    "container.host" : "/default-rack/node2",
    "container.start.ms" : 111788,
    "container.end.ms" : 131377,
    "container.priority" : 20,
    "container.type" : "map"
  } ]
}

SYNTH JSON 输入文件格式

这里我们提供一个合成生成器json文件的示例格式。我们使用(不符合json规范的)内联注释来解释每个参数的用途。

{
  "description" : "tiny jobs workload",    //description of the meaning of this collection of workloads
  "num_nodes" : 10,  //total nodes in the simulated cluster
  "nodes_per_rack" : 4, //number of nodes in each simulated rack
  "num_jobs" : 10, // total number of jobs being simulated
  "rand_seed" : 2, //the random seed used for deterministic randomized runs

  // a list of “workloads”, each of which has job classes, and temporal properties
  "workloads" : [
    {
      "workload_name" : "tiny-test", // name of the workload
      "workload_weight": 0.5,  // used for weighted random selection of which workload to sample from
      "queue_name" : "sls_queue_1", //queue the job will be submitted to

    //different classes of jobs for this workload
       "job_classes" : [
        {
          "class_name" : "class_1", //name of the class
          "class_weight" : 1.0, //used for weighted random selection of class within workload

          //nextr group controls average and standard deviation of a LogNormal distribution that
          //determines the number of mappers and reducers for thejob.
          "mtasks_avg" : 5,
          "mtasks_stddev" : 1,
          "rtasks_avg" : 5,
          "rtasks_stddev" : 1,

          //averge and stdev input param of LogNormal distribution controlling job duration
          "dur_avg" : 60,
          "dur_stddev" : 5,

          //averge and stdev input param of LogNormal distribution controlling mappers and reducers durations
          "mtime_avg" : 10,
          "mtime_stddev" : 2,
          "rtime_avg" : 20,
          "rtime_stddev" : 4,

          //averge and stdev input param of LogNormal distribution controlling memory and cores for map and reduce
          "map_max_memory_avg" : 1024,
          "map_max_memory_stddev" : 0.001,
          "reduce_max_memory_avg" : 2048,
          "reduce_max_memory_stddev" : 0.001,
          "map_max_vcores_avg" : 1,
          "map_max_vcores_stddev" : 0.001,
          "reduce_max_vcores_avg" : 2,
          "reduce_max_vcores_stddev" : 0.001,

          //probability of running this job with a reservation
          "chance_of_reservation" : 0.5,
          //input parameters of LogNormal distribution that determines the deadline slack (as a multiplier of job duration)
          "deadline_factor_avg" : 10.0,
          "deadline_factor_stddev" : 0.001,
        }
       ],
    // for each workload determines with what probability each time bucket is picked to choose the job starttime.
    // In the example below the jobs have twice as much chance to start in the first minute than in the second minute
    // of simulation, and then zero chance thereafter.
      "time_distribution" : [
        { "time" : 1, "weight" : 66 },
        { "time" : 60, "weight" : 33 },
        { "time" : 120, "jobs" : 0 }
     ]
    }
 ]
}

模拟器输入拓扑文件格式

这是一个示例输入拓扑文件,其中包含3个节点,组织在1个机架中。

{
  "rack" : "default-rack",
  "nodes" : [ {
    "node" : "node1"
  }, {
    "node" : "node2"
  }, {
    "node" : "node3"
  }]
}

关于对数正态分布的说明:

对数正态分布能很好地代表我们在实践中看到的许多参数(例如,大多数作业有少量映射器,但少数可能非常大,少数非常小,但大于零)。然而值得注意的是,使用它可能有些棘手,因为平均值通常位于分布峰值(最常见值)的右侧,因为该分布具有单侧尾部。