相信不少初学者在刚接触Hadoop的时候刚曾遇到过“No valid local directories in property:mapred.local.dir”的问题,该问题发生在使用hadoop jar命令向集群提交一个MapReduce任务时,通常报错如下:

 

2014-05-2709:53:54,450 ERROR org.apache.hadoop.security.UserGroupInformation:PriviledgedActionException as:root (auth:SIMPLE) cause:java.io.IOException:java.io.IOException: No valid local directories in property: mapred.local.dir

2014-05-27 09:53:54,452 INFO org.apache.hadoop.ipc.Server:IPC Server handler 3 on 8021, call submitJob(job_201405270918_0003,hdfs://hct-mon/tmp/hadoop-mapred/mapred/staging/root/.staging/job_201405270918_0003,org.apache.hadoop.security.Credentials@4e617b49), rpc version=2, clientversion=28, methodsFingerPrint=1830206421 from 10.212.52.95:57268: error:java.io.IOException: java.io.IOException: No valid local directories inproperty: mapred.local.dir

java.io.IOException:java.io.IOException: No valid local directories in property: mapred.local.dir

atorg.apache.hadoop.mapred.JobTracker.submitJob(JobTracker.java:3519)

atorg.apache.hadoop.mapred.JobTracker.submitJob(JobTracker.java:3485)

atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

atjava.lang.reflect.Method.invoke(Method.java:606)

atorg.apache.hadoop.ipc.WritableRpcEngine$Server$WritableRpcInvoker.call(WritableRpcEngine.java:474)

atorg.apache.hadoop.ipc.RPC$Server.call(RPC.java:1002)

atorg.apache.hadoop.ipc.Server$Handler$1.run(Server.java:1752)

atorg.apache.hadoop.ipc.Server$Handler$1.run(Server.java:1748)

atjava.security.AccessController.doPrivileged(Native Method)

atjavax.security.auth.Subject.doAs(Subject.java:415)

atorg.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1408)

atorg.apache.hadoop.ipc.Server$Handler.run(Server.java:1746)

Caused by: java.io.IOException: No valid local directoriesin property: mapred.local.dir

atorg.apache.hadoop.conf.Configuration.getLocalPath(Configuration.java:1881)

atorg.apache.hadoop.mapred.JobConf.getLocalPath(JobConf.java:500)

atorg.apache.hadoop.mapred.JobInProgress.<init>(JobInProgress.java:417)

atorg.apache.hadoop.mapred.JobTracker.submitJob(JobTracker.java:3517)

... 13 more

 

网上对这个问题的讨论不多(特别是中文材料),其中有一个说法是去掉mapred-site.xml配置文件中的mapred.local.dir配置项。应该说,这个做法确实可以解决问题,但由于这样会引发巨大的性能问题,因此在现网实施时强烈不推荐这么做。具体的原因,我们下面来做一个深入的分析。

我们就从mapred.local.dir的用途说起吧。在Hadoop中,mapred.local.dir这个配置项是MRv1(也就是YARN之前的MapReduce版本,YARN中的MapReduce则成为MRv2)中是用来实现其分布式缓存的(DistributedCache)。如果你碰巧是Hadoop的菜鸟,一下子来了这么多新的名字是不是感到很晕?那好吧,如果你碰巧不了解MRv1中的分布式缓存机制,我这儿就先给个简单的解释,对于分布式缓存的进一步讨论,有兴趣的读者参考董大大的博客[1]

分布式缓存是Hadoop提供的文件缓存工具,它能够自动将指定的文件分发到各个节点上,缓存到本地,供用户程序读取使用。它主要有以下几种典型的应用场景:1)分发程序jar包、MapReduce的作业配置文件job.xml以及所依赖的第三方jar包;2)分发一些情况下Mapper或者Reducer需要用到一些外部字典,比如黑白名单、词表等;3)当多表连接时,如果一个表很大,而另一个则小到足以加载到内存中,这时可以使用DistributedCache直接将小表分发到各个节点上,以供Mapper加载使用(map-side join)。在MapReduce中,主要有2个分布式缓存的配置项,其中一个就是mapred.local.dir,它主要用来控制被缓存文件的在本地硬盘的保存路径,默认为${hadoop.tmp.dir}/mapred/local;而另一个则是local.cache.size,用来限制允许缓存的文件数量,默认为10G

当用户向Hadoop集群提交任务时,JobClient首先会将执行该Job所需的各种文件(如上文中提到的各种jarjob.xml文件等)拷贝到HDFS中,随后当一个Task被调度到某个TaskTracker节点后,DistributedCache会从HDFS中自动下载jar包等一系列文件。下载后保存到哪儿呢?对了,就是mapred.local.dir参数所指定的目录。因此,如果TaskTracker节点出现“No valid local directories in property:mapred.local.dir”问题,一般主要有2个原因:要么是mapred.local.dir指定的目录不存在,要么是TaskTracker进程对没有目录的访问权限——注意,在CDH等大多数Hadoop发行版中,TaskTracker通常以mapred的用户运行,因此通常都会将mapred.local.dirowner设置为mapred。总之,只要是在TaskTracker端出现这个问题,肯定是TaskTracker无法将文件成功的写入到mapred.local.dir所致。

接下来问题来了。当我仔细检查了每个TaskTracker节点惊奇的发现,所有mapred.local.dir目录全部正常,目录全部存在,TaskTracker的访问权限也都没有问题——Hadoop这东西真让人揪心啊。Google了一把,有用的信息很少,除了stackoverflow中有一个帖子建议将mapred.local.dir配置项从mapred-site.xml中***的。照做,再次提交Job时一切正常。

报错虽然没有了,但对引起这个问题的原因还是一头雾水。仔细查看Java的异常信息,发现这次的问题实际上并不是来自TaskTracker,而是JobTracker。于是,尝试着在JobTracker中建立相应的mapred.local.dir目录,并恢复mapred.local.dir配置项,再次一切正常。至此,问题已经彻底解决了。

但疑惑之处还有:因为JobTracker只是一个任务的调度节点,它并不需要各种jar包来真正执行任务,为什么也需要用到Hadoop的分布式缓存呢?这是不是多此一举呢?要解释这个问题,就只能看MapReduce的源码了。通过Java异常中的报错来跟踪MapReduce的源代码后发现,在JobTracker.submitJob()方法中,JobTracker需要创见一个JobInProgress对象来保存一个Job的执行进度信息,而JIP对象在初始化时则需要根据job.xml文件来创建其JobConf对象。在JobConf对象创建之前,它需要首先将job.xml文件从HDFS中拷贝到mapred.local.dir中,而之前由于没有在JobTracker节点中创建相应的目录,因而导致了这个问题的产生。这一过程的详细内容可以查阅Hadoop源代码中的JobInProgress构建方法,和JobSubmissionFiles.getJobConfPath()方法。

最后,我们再来解释一下:为什么说在配置文件中去掉mapred.local.dir配置项能解决问题,但同时又会引发巨大的性能问题?在默认情况下,mapred.local.dir指向${hadoop.tmp.dir}/mapred/local(即在CDH4/5的配置环境下为/tmp/hadoop-mapred/mapred/local),由于/tmp目录对任何用户都是可读写的,因此自然不存在访问权限问题。

同时,在Hadoop集群比较繁忙的情况下,mapred.local.dir的写入压力一般都是很大的,这就需要将其分散在各块硬盘上以提高性能(如我自己一般每个节都点是10块数据盘,因而就配置了10mapred.local.dir访问路径),而默认情况下的/tmp路径也就只有12块硬盘,因而在存在性能也就不足为奇了。

 

  1. HadoopDistributedCache详解。http://dongxicheng.org/mapreduce-nextgen/hadoop-distributedcache-details/

  2. Hadoop: java.io.IOException: No valid local directories in property: mapred.local.dir.http://stackoverflow.com/questions/7679943/hadoop-java-io-ioexception-no-valid-local-directories-in-property-mapred-loc