Hadoop集群中JobTracker和TaskTracker启动耗时过多的原因分析

在正式环境中,我们遇到一个这样的问题:配置更改后,需要重启JobTracker和TaskTracker节点的进程。在重启过程中,JobTracker和TaskTracker进程都很快启动完成,但是查看JobTracker的50030端口的Web管理页面时,发现JobTracker一直无法探知任何一个TaskTracker节点,大概经过10分钟后,才陆陆续续地探知完所有的TaskTracker节点。

    另外,如下参数,如无说明,均为Hadoop 1.2.1版本参数。

  • 重现

    这个问题一直困扰着我们,并且曾经一度怀疑是Hadoop内部的机架探知比较缓慢所造成的。直到最近一次,由于运维需要移动某个TaskTracker机器的位置,需要单独重启这个TaskTracker,在重启了节点的JobTracker和DataNode进程后,发现JobTracker经过长时间也没办法探知到该节点的TaskTracker,此时的NameNode却早已成功探知到DataNode。出现问题后,尝试重新单独启动TaskTracker进程,这时,却发现探知了两个TaskTracker节点,后来才变回一个TaskTracker节点。

  • 分析

    为了了解究竟发生了什么问题,我们查看了JobTracker和TaskTracker的log。由于log的数量太多,这里仅仅列出关键的log。另外,关闭的TaskTracker的hostname为datanode6.dataplat.com。

    首先是JobTracker的log:

2015-01-13 20:38:11,016 INFO org.apache.hadoop.mapred.JobTracker: Lost tracker 'tracker_datanode6.dataplat.com:localhost/127.0.0.1:47379'

2015-01-13 21:21:18,826 INFO org.apache.hadoop.mapred.JobTracker: Adding tracker tracker_datanode6.dataplat.com:localhost/127.0.0.1:52849 to host datanode6.dataplat.com

2015-01-13 21:21:31,320 INFO org.apache.hadoop.mapred.JobTracker: Adding tracker tracker_datanode6.dataplat.com:localhost/127.0.0.1:39956 to host datanode6.dataplat.com
2015-01-13 21:31:31,020 INFO org.apache.hadoop.mapred.JobTracker: Lost tracker 'tracker_datanode6.dataplat.com:localhost/127.0.0.1:52849'

    然后是TaskTracker的log:

2015-01-13 20:26:21,114 INFO org.apache.hadoop.mapred.TaskTracker: SHUTDOWN_MSG: 

/************************************************************
SHUTDOWN_MSG: Shutting down TaskTracker at datanode6.dataplat.com/192.168.1.254
************************************************************/
2015-01-13 21:09:39,038 INFO org.apache.hadoop.mapred.TaskTracker: STARTUP_MSG: 
/************************************************************
......
2015-01-13 21:09:41,194 INFO org.apache.hadoop.mapred.TaskTracker: Starting tasktracker with owner as hadoop
2015-01-13 21:09:41,216 INFO org.apache.hadoop.mapred.TaskTracker: Good mapred local directories are: /disk2/hadoopdata/tmp/mapred/local,/disk3/hadoopdata/tmp/mapred/local,/disk4/hadoopdata/tmp/mapred/local
2015-01-13 21:21:17,304 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source jvm registered.
2015-01-13 21:21:17,306 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source TaskTrackerMetrics registered.
......
2015-01-13 21:21:17,348 INFO org.apache.hadoop.ipc.Server: IPC Server Responder: starting
2015-01-13 21:21:17,348 INFO org.apache.hadoop.ipc.Server: IPC Server listener on 52849: starting
.......
2015-01-13 21:21:17,357 INFO org.apache.hadoop.mapred.TaskTracker: Starting tracker tracker_datanode6.dataplat.com:localhost/127.0.0.1:52849
.......

2015-01-13 21:21:20,165 INFO org.apache.hadoop.mapred.TaskTracker: SHUTDOWN_MSG: 
/************************************************************
SHUTDOWN_MSG: Shutting down TaskTracker at datanode6.dataplat.com/192.168.1.254
************************************************************/
2015-01-13 21:21:29,452 INFO org.apache.hadoop.mapred.TaskTracker: STARTUP_MSG: 
/************************************************************
......
2015-01-13 21:21:30,425 INFO org.apache.hadoop.mapred.TaskTracker: Starting tasktracker with owner as hadoop
2015-01-13 21:21:30,426 INFO org.apache.hadoop.mapred.TaskTracker: Good mapred local directories are: 
/disk2/hadoopdata/tmp/mapred/local,/disk3/hadoopdata/tmp/mapred/local,/disk4/hadoopdata/tmp/mapred/local
2015-01-13 21:21:30,444 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source jvm registered.
2015-01-13 21:21:30,445 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source TaskTrackerMetrics registered
......
2015-01-13 21:21:30,470 INFO org.apache.hadoop.ipc.Server: IPC Server listener on 39956: starting
2015-01-13 21:21:30,470 INFO org.apache.hadoop.ipc.Server: IPC Server Responder: starting
......
2015-01-13 21:21:30,474 INFO org.apache.hadoop.mapred.TaskTracker: Starting tracker tracker_datanode6.dataplat.com:localhost/127.0.0.1:39956

    TaskTracker的log是从正式第一次关闭TaskTracker的时间,在20:26:21开始,其中还包括第二次重启的log细节。首先从第一次重启TaskTracker后,在打印出mapreduce本地存储目录后(Good mapred local directories are ...),有10分钟的时间,没有任何log输出,而在第二次关闭前,Starting tracker tracker_datanode6.dataplat.com:localhost/127.0.0.1:52849这一条log说明了TaskTracker才开始进行RPC服务器监听,注意这里绑定的端口号为52849。

    在第二次重启之后,TaskTracker并没有重现第一次重启的卡死现象,这次在39956端口号绑定RPC服务器监听。

    结合JobTracker的log来查看,我们可以看到在TaskTracker关闭后大约10分钟的时间,JobTracker才判定TaskTracker失效。这是由参数mapred.tasktracker.expiry.interval设置的,默认为10min,当TaskTracker经过10min也没有发送Heartbeat到JobTracker的时候,JobTracker就判定其失效。另外,可以看到JobTracker接收到TaskTracker的两个绑定请求,分别是端口号52849和39956,可见,JobTracker在短时间内接收到TaskTracker第二次关闭前发送的Heartbeat和第二次重启后的Heartbeat,因此突然会增加了两个JobTracker,不过很快JobTracker便把第一次无效的Heartbeat去除。

    于是,我们来集中关注这样一个问题,在第一次重启后,TaskTracker究竟为什么会卡死了10分钟?

    根据打印出的Log查询源码,可以进行对应匹配,发现了这10分钟里的Log竟然都是在TaskTracker.initialize()这个初始化函数堆栈内!其中,两个log之间的代码如下:

synchronized void initialize() throws IOException, InterruptedException {
    //...略去多余...
    final String dirs = localStorage.getDirsString();
    fConf.setStrings(JobConf.MAPRED_LOCAL_DIR_PROPERTY, dirs);
    //2015-01-13 21:09:41,216 INFO org.apache.hadoop.mapred.TaskTracker: Good mapred local directories are: ......
    LOG.info("Good mapred local directories are: " + dirs);
    taskController.setConf(fConf);
    server.setAttribute("conf", fConf);
    deleteUserDirectories(fConf);
    // NB: deleteLocalFiles uses the configured local dirs, but does not 
    // fail if a local directory has failed. 
    fConf.deleteLocalFiles(SUBDIR);
    final FsPermission ttdir = FsPermission.createImmutable((short) 0755);
    for (String s : localStorage.getDirs()) {
      localFs.mkdirs(new Path(s, SUBDIR), ttdir);
    }
    fConf.deleteLocalFiles(TT_PRIVATE_DIR);
    final FsPermission priv = FsPermission.createImmutable((short) 0700);
    for (String s : localStorage.getDirs()) {
      localFs.mkdirs(new Path(s, TT_PRIVATE_DIR), priv);
    }
    fConf.deleteLocalFiles(TT_LOG_TMP_DIR);
    final FsPermission pub = FsPermission.createImmutable((short) 0755);
    for (String s : localStorage.getDirs()) {
      localFs.mkdirs(new Path(s, TT_LOG_TMP_DIR), pub);
    }
    // Create userlogs directory under all good mapred-local-dirs
    for (String s : localStorage.getDirs()) {
      Path userLogsDir = new Path(s, TaskLog.USERLOGS_DIR_NAME);
      if (!localFs.exists(userLogsDir)) {
        localFs.mkdirs(userLogsDir, pub);
      }
    }
    // Clear out state tables
    this.tasks.clear();
    this.runningTasks = new LinkedHashMap<TaskAttemptID, TaskInProgress>();
    this.runningJobs = new TreeMap<JobID, RunningJob>();
    this.mapTotal = 0;
    this.reduceTotal = 0;
    this.acceptNewTasks = true;
    this.status = null;
    this.minSpaceStart = this.fConf.getLong("mapred.local.dir.minspacestart", 0L);
    this.minSpaceKill = this.fConf.getLong("mapred.local.dir.minspacekill", 0L);
    //tweak the probe sample size (make it a function of numCopiers)
    probe_sample_size = this.fConf.getInt("mapred.tasktracker.events.batchsize", 500);
    //2015-01-13 21:21:17,304 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source jvm registered.
    //2015-01-13 21:21:17,306 INFO org.apache.hadoop.metrics2.impl.MetricsSourceAdapter: MBean for source TaskTrackerMetrics registered.
    createInstrumentation();
    //...略去多余...
}

    很明显,在这两个Log之间的代码,有可能会卡死到10min的操作就是大量的目录创建和删除的IO操作。为了证明猜想,查看了删除子目录的文件情况。

    目录位置是由属性mapred.local.dir配置的,另外,考虑到数据损坏,该属性可以配置由多个硬盘挂载后的目录。在正式环境里,我们总共配置了3个不同位置的目录,每次写入数据的时候,都必须往这三个目录写入相同内容。查看其中一个目录,我们发现,{$mapred.local.dir}/taskTracker/{user}/distcache目录下总共有14G大小的数据!如果加上3个不同目录,则起码有42G的数据!代码:

fConf.deleteLocalFiles(SUBDIR); //public static final String SUBDIR = "taskTracker";

则是负责删除这一子目录。因此基本可以确定,在这10分钟内,TaskTracker的主线程都在删除这42G数据上了。

    另外,我们还可以通过测试验证结论,可以首先关闭TaskTracker节点,然后mv这个目录的位置,然后在重启TaskTracker,查看JobTracker是否能够快速探知到该TaskTracker(待测试)。

  • 解决

    查看源代码和相关资料,我们可以了解到这个目录是由于Hadoop的DistributedCache机制。它能够自动将指定的文件分发到各个节点上,缓存到本地,供用户程序读取使用。缓存大小由参数local.cache.size设置,当缓存大小超过参数mapreduce.tasktracker.cache.local.keep.pct和local.cache.size的乘积大小时,就会进行清理,规则是当前没有被使用的资源按照LRU算法进行清理。具体清理逻辑实现在TrackerDistributedCacheManager类中。

    因此只要我们把local.cache.size的值适当调低(默认为10G),则可以在每次重启的时候,减轻删除这些缓存文件的压力,加快启动速度。另外,也有其他相关文章表示出默认缓存大小影响启动性能(hadoop-too-much-local-cache-is-a-bad-thing)。

    另外,在Hadoop 2.x里,这部分的机制包括API也改变。最主要的变化是这个缓存机制由YARN进行管理,具体到每个NodeManager节点。NodeManager也不再在启动初始化主线程进行缓存文档删除操作,而是每次都会启动线程,按照缓存限制大小进行清理,这样就避免在启动过程中的卡死现象,具体的清理逻辑在DeletionService中。对于整个缓存机制可以参考resource-localization-in-yarn-deep-dive。

你可能感兴趣的:(hadoop,大数据)