概览
DistributedCache 是一个提供给Map/Reduce框架的工具,用来缓存文件(text, archives, jars and so on)文件的默认访问协议为(hdfs://).
DistributedCache将拷贝缓存的文件到Slave节点在任何Job在节点上执行之前。
文件在每个Job中只会被拷贝一次,缓存的归档文件会被在Slave节点中解压缩。
符号链接
每个存储在HDFS中的文件被放到缓存中后都可以通过一个符号链接使用。
URI hdfs://namenode/test/input/file1#myfile 你可以在程序中直接使用myfile来访问 file1这个文件。 myfile是一个符号链接文件。
缓存在本地的存储目录
<property>
<name>mapred.local.dir</name>
<value>${hadoop.tmp.dir}/mapred/local</value>
<description>The local directory where MapReduce stores intermediate
data files. May be a comma-separated list of
directories on different devices in order to spread disk i/o.
Directories that do not exist are ignored.
</description>
</property>
<property>
<name>local.cache.size</name>
<value>10737418240</value> (默认大小:10GB)
<description>The limit on the size of cache you want to keep, set by default
to 10GB. This will act as a soft limit on the cache directory for out of band data.
</description>
</property>
实际在DataNode节点中的存储目录:
/netqin/hadoop/tmp{${hadoop.tmp.dir}}/mapred/local/taskTracker/archive/hadoop-server01{NameNode主机名称}
Archive文件会被解压缩
例子
package com.netqin.examples;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class CacheDemo {
public static void UseDistributedCacheBySymbolicLink() throws Exception {
FileReader reader = new FileReader("hdfs://mail.py");
BufferedReader br = new BufferedReader(reader);
String s = null;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
br.close();
reader.close();
}
public static class TokenizerMapper extends
Mapper<Object, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
protected void setup(Context context) throws IOException,
InterruptedException {
System.out.println("Now, use the distributed cache and syslink");
try {
UseDistributedCacheBySymbolicLink();
} catch (Exception e) {
e.printStackTrace();
}
}
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer extends
Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args)
.getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
DistributedCache.createSymlink(conf);
String path = "/tmp/test/mail.py";
Path filePath = new Path(path);
String uriWithLink = filePath.toUri().toString() + "#" + "mail.py";
DistributedCache.addCacheFile(new URI(uriWithLink), conf);
// Path p = new Path("/tmp/hadoop-0.20.2-capacity-scheduler.jar#hadoop-0.20.2-capacity-scheduler.jar");
// DistributedCache.addArchiveToClassPath(p, conf);
Job job = new Job(conf, "CacheDemo");
job.setJarByClass(CacheDemo.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
=========
DistributedCache
DistributedCache 可将具体应用相关的、大尺寸的、只读的文件有效地分布放置。
DistributedCache 是Map/Reduce框架提供的功能,能够缓存应用程序所需的文件 (包括文本,档案文件,jar文件等)。
应用程序在JobConf中通过url(hdfs://)指定需要被缓存的文件。 DistributedCache假定由hdfs://格式url指定的文件已经在 FileSystem上了。
Map-Redcue框架在作业所有任务执行之前会把必要的文件拷贝到slave节点上。 它运行高效是因为每个作业的文件只拷贝一次并且为那些没有文档的slave节点缓存文档。
DistributedCache 根据缓存文档修改的时间戳进行追踪。 在作业执行期间,当前应用程序或者外部程序不能修改缓存文件。
distributedCache可以分发简单的只读数据或文本文件,也可以分发复杂类型的文件例如归档文件和jar文件。归档文件(zip,tar,tgz和tar.gz文件)在slave节点上会被解档(un-archived)。 这些文件可以设置执行权限。
用户可以通过设置mapred.cache.{files|archives}来分发文件。 如果要分发多个文件,可以使用逗号分隔文件所在路径。也可以利用API来设置该属性: DistributedCache.addCacheFile(URI,conf)/ DistributedCache.addCacheArchive(URI,conf) and DistributedCache.setCacheFiles(URIs,conf)/ DistributedCache.setCacheArchives(URIs,conf) 其中URI的形式是 hdfs://host:port/absolute-path#link-name 在Streaming程序中,可以通过命令行选项 -cacheFile/-cacheArchive 分发文件。
用户可以通过 DistributedCache.createSymlink(Configuration)方法让DistributedCache 在当前工作目录下创建到缓存文件的符号链接。 或者通过设置配置文件属性mapred.create.symlink为yes。 分布式缓存会截取URI的片段作为链接的名字。 例如,URI是 hdfs://namenode:port/lib.so.1#lib.so, 则在task当前工作目录会有名为lib.so的链接, 它会链接分布式缓存中的lib.so.1。
DistributedCache可在map/reduce任务中作为 一种基础软件分发机制使用。它可以被用于分发jar包和本地库(native libraries)。 DistributedCache.addArchiveToClassPath(Path, Configuration)和 DistributedCache.addFileToClassPath(Path, Configuration) API能够被用于 缓存文件和jar包,并把它们加入子jvm的classpath。也可以通过设置配置文档里的属性 mapred.job.classpath.{files|archives}达到相同的效果。缓存文件可用于分发和装载本地库。
http://www.open-open.com/lib/view/open1337349822015.html
hadoop中的DistributedCache 2
WordCount.javaHadoop的分布式缓存机制使得一个job的所有map或reduce可以访问同一份文件。在任务提交后,hadoop将由-files和-archive选项指定的文件复制到HDFS上(JobTracker的文件系统)。在任务运行前,TaskTracker从JobTracker文件系统复制文件到本地磁盘作为缓存,这样任务就可以访问这些文件。对于job来说,它并不关心文件是从哪儿来的。在使用DistributedCache时,对于本地化文件的访问,通常使用Symbolic Link来访问,这样更方便。通过 URI hdfs://namenode/test/input/file1#myfile 指定的文件在当前工作目录中被符号链接为myfile。这样job里面可直接通过myfile来访问文件,而不用关心该文件在本地的具体路径。
示例如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.StringTokenizer;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.*;
public class WordCount
{
public static void UseDistributedCacheBySymbolicLink() throws Exception
{
FileReader reader = new FileReader("god.txt");
BufferedReader br = new BufferedReader(reader);
String s1 = null;
while ((s1 = br.readLine()) != null)
{
System.out.println(s1);
}
br.close();
reader.close();
}
public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable>
{
public void configure(JobConf job)
{
System.out.println("Now, use the distributed cache and syslink");
try {
UseDistributedCacheBySymbolicLink();
}
catch (Exception e)
{
e.printStackTrace();
}
}
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException
{
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens())
{
word.set(tokenizer.nextToken());
output.collect(word, one);
}
}
}
public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable>
{
public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException
{
int sum = 0;
while (values.hasNext())
{
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception
{
JobConf conf = new JobConf(WordCount. class);
conf.setJobName("wordcount");
conf.setOutputKeyClass(Text. class);
conf.setOutputValueClass(IntWritable. class);
conf.setMapperClass(Map. class);
conf.setCombinerClass(Reduce. class);
conf.setReducerClass(Reduce. class);
conf.setInputFormat(TextInputFormat. class);
conf.setOutputFormat(TextOutputFormat. class);
FileInputFormat.setInputPaths(conf, new Path(args[0]));
FileOutputFormat.setOutputPath(conf, new Path(args[1]));
DistributedCache.createSymlink(conf);
String path = "/xuxm_dev_test_61_pic/in/WordCount.java";
Path filePath = new Path(path);
String uriWithLink = filePath.toUri().toString() + "#" + "god.txt";
DistributedCache.addCacheFile( new URI(uriWithLink), conf);
JobClient.runJob(conf);
}
}
程序运行的结果是在jobtracker中的task的log可以看到打印后的/xuxm_dev_test_61_pic/in/WordCount.java文件的内容。
如果程序中要用到很多小文件,那么使用Symbolic Link将非常方便。
请在执行前先将WordCount.java文件放到指定位置,否则就会找不到文件
概念:
reduce-side join技术是灵活的,但是有时候它仍然会变得效率极低。由于join直到reduce()阶段才会开始,我们将会在网络中传递shuffle所有数据,而在大多数情况下,我们会在join阶段丢掉大多数传递的数据。因此我们期望能够在map阶段完成整个join操作。
主要技术难点:
什么情况下使用?
解决方案:
- DistributedCache.addCacheFile(new Path(args[0]).toUri(), conf);
- DistributedCache.getLocalCacheFiles();
新出现的问题:
华夏35度
Data Mining,NLP,Search Engine
用Hadoop1.0.3实现KMeans算法
从理论上来讲用MapReduce技术实现KMeans算法是很Natural的想法:在Mapper中逐个计算样本点离哪个中心最近,然后Emit(样本点所属的簇编号,样本点);在Reducer中属于同一个质心的样本点在一个链表中,方便我们计算新的中心,然后Emit(质心编号,质心)。但是技术上的事并没有理论层面那么简单。
Mapper和Reducer都要用到K个中心(我习惯称之为质心),Mapper要读这些质心,Reducer要写这些质心。另外Mapper还要读存储样本点的数据文件。我先后尝试以下3种方法,只有第3种是可行的,如果你不想被我误导,请直接跳过前两种。
一、用一个共享变量在存储K个质心
由于K很小,所以我们认为用一个Vector<Sample>来存储K个质心是没有问题的。以下代码是错误的:
class
MyJob extends Tool{
static
Vector<Sample> centers=
new
Vector<Sample>(K);
static
class
MyMapper extends Mapper{
//read centers
}
static
class
MyMapper extends Reducer{
//update centers
}
void
run(){
until ( convergence ){
map();
reduce();
}
}
|
发生这种错误是因为对hadoop执行流程不清楚,对数据流不清楚。简单地说Mapper和Reducer作为MyJob的内部静态类,它们应该是独立的--它们不应该与MyJob有任何交互,因为Mapper和Reducer分别在Task Tracker的不同JVM中运行,而MyJob以及MyJob的内部其他类都在客户端上运行,自然不能在不同的JVM中共享一个变量。
详细的流程是这样的:
首先在客户端上,JVM加载MyJob时先初始化静态变量,执行static块。然后提交作业到Job Tracker。
在Job Tracker上,分配Mapper和Reducer到不同的Task Tracker上。Mapper和Reducer线程获得了MyJob类静态变量的初始拷贝(这份拷贝是指MyJob执行完静态块之后静态变量的模样)。
在Task Tracker上,Mapper和Reducer分别地读写MyJob的静态变量的本地拷贝,但是并不影响原始的MyJob中的静态变量的值。
二、用分布式缓存文件存储K个质心
既然不能通过共享外部类变量的方式,那我们通过文件在map和reduce之间传递数据总可以吧,Mapper从文件中读取质心,Reducer把更新后的质心再写入这个文件。这里的问题是:如果确定要把质心放在文件中,那Mapper就需要从2个文件中读取数据--质心文件和样本数据文件。虽然有MutipleInputs可以指定map()的输入文件有多个,并可以为每个输入文件分别指定解析方式,但是MutipleInputs不能保证每条记录从不同文件中传给map()的顺序。在我们的KMeans中,我们希望质心文件全部被读入后再逐条读入样本数据。
于是乎就想到了DistributedCache,它主要用于Mapper和Reducer之间共享数据。DistributedCacheFile是缓存在本地文件,在Mapper和Reducer中都可使用本地Java I/O的方式读取它。于是我又有了一个错误的思路:
class
MyMaper{
Vector<Sample> centers=
new
Vector<Sample>(K);
void
setup(){
//读取cacheFile,给centers赋值
}
void
map(){
//计算样本离哪个质心最近
}
}
class
MyReducer{
Vector<Sample> centers=
new
Vector<Sample>(K);
void
reduce(){
//更新centers
}
void
cleanup(){
//把centers写回cacheFile
}
}
|
错因:DistributedCacheFile是只读的,在任务运行前,TaskTracker从JobTracker文件系统复制文件到本地磁盘作为缓存,这是单向的复制,是不能写回的。试想在分布式环境下,如果不同的mapper和reducer可以把缓存文件写回的话,那岂不又需要一套复杂的文件共享机制,严重地影响hadoop执行效率。
三、用分布式缓存文件存储样本数据
其实DistributedCache还有一个特点,它更适合于“大文件”(各节点内存容不下)缓存在本地。仅存储了K个质心的文件显然是小文件,与之相比样本数据文件才是大文件。
此时我们需要2个质心文件:一个存放上一次的质心prevCenterFile,一个存放reducer更新后的质心currCenterFile。Mapper从prevCenterFile中读取质心,Reducer把更新后有质心写入currCenterFile。在Driver中读入prevCenterFile和currCenterFile,比较前后两次的质心是否相同(或足够地接近),如果相同则停止迭代,否则就用currCenterFile覆盖prevCenterFile(使用fs.rename),进入下一次的迭代。
这时候Mapper就是这样的:
class
MyMaper{
Vector<Sample> centers=
new
Vector<Sample>(K);
void
map(){
//逐条读取质心,给centers赋值
}
void
cleanup(){
//逐行读取cacheFile,计算每个样本点离哪个质心最近
//然后Emit(样本点所属的簇编号,样本点)
}
}
|
源代码
试验数据是在Mahout项目中作为example提供的,600个样本点,每个样本是一个60维的浮点向量。点击下载
为样本数据建立一个类Sample.java。
KMeans.java
注意在Driver中创建Job实例时一定要把Configuration类型的参数传递进去,否则在Mapper或Reducer中调用DistributedCache.getLocalCacheFiles(context.getConfiguration());返回值就为null。因为空构造函数的Job采用的Configuration是从hadoop的配置文件中读出来的(使用new Configuration()创建的Configuration就是从hadoop的配置文件中读出来的),请注意在main()函数中有一句:DistributedCache.addCacheFile(dataFile.toUri(), conf);即此时的Configuration中多了一个DistributedCacheFile,所以你需要把这个Configuration传递给Job构造函数,如果传递默认的Configuration,那在Job中当然不知道DistributedCacheFile的存在了。
Further
方案三还是不如人意,质心文件是很小的(因为质心总共就没几个),用map()函数仅仅是来读一个质心文件根本就没有发挥并行的作用,而且在map()中也没有调用context.write(),所以Mapper中做的事情可以放在Reducer的setup()中来完成,这样就不需要Mapper了,或者说上面设计的就不是MapReduce程序,跟平常的单线程串行程序是一样的。sigh
1、DistributedCache In Hadoop
此篇文章主要是前一篇的后续,主要讲Hadoop的分布式缓存机制的原理与运用。
分布式缓存在MapReduce中称之为DistributedCache,它可以方便map task之间或者reduce task之间共享一些信息,同时也可以将第三方包添加到其classpath路径中去。Hadoop会将缓存数据分发到集群的所有准备启动的节点上,复制到在mapred.temp.dir中配置的目录。
2、DistributedCache的使用
DistributedCache的使用的本质其实是添加Configuraton中的属性:mapred.cache.{files|archives}。图方便的话,可以使用DistributedCache类的静态方法。
不省事法:
conf.set("mapred.cache.files", "/data/data"); conf.set("mapred.cache. archives", "/data/data.zip"); |
省事法:
DistributedCache. DistributedCache. |
需要注意的是,上面几行代码需要写在Job类初始化之前,否则在运行会中找不到文件(被折磨了很长时间),因为Job初始化时将传入Configuration对象克隆一份给了JobContext。
在MapReduce的0.21版本以后的org.apache.hadoop.mapreduce均移到org.apache.hadoop.mapred包下。但文档中提供的configure方法是重写的MapReduceBase中的,而新版本中map继承于mapper,reduce继承于reducer,所以configure方法一律改成了setup。要获得cache数据,就得在map/reduce task中的setup方法中取得cache数据,再进行相应操作:
|
而三方库的使用稍微简单,只需要将库上传至hdfs,再用代码添加至classpath即可:
DistributedCache.addArchiveToClassPath(new Path("/data/test.jar"), conf); |
3、symlink的使用
Symlink其实就是hdfs文件的一个快捷方式,只需要在路径名后加入#linkname,之后在task中使用linkname即使用相应文件,如下:
conf.set("mapred.cache.files", "/data/data#mData"); conf.set("mapred.cache. archives", "/data/data.zip#mDataZip"); |
|
在使用symlink之前,需要告知hadoop,如下:
conf.set("mapred.create.symlink", "yes"); // 是yes,不是true |
4、注意事项
1)缓存文件(数据、三方库)需上传至HDFS,方能使用;
2)缓存较小的情况下,建议将数据全部读入相应节点内存,提高访问速度;
3)缓存文件是read-only的,不能修改。若要修改得重新输出,将新输出文件作为新缓存进入下一次迭代。