MapReduce User Manual
来源:互联网 发布:手机伴奏消音软件 编辑:程序博客网 时间:2024/06/06 15:04
目录
[隐藏]- 1 MapReduce简介
- 2 MapReduce使用示例
- 2.1 词频统计(WordCount)--java版本
- 2.2 词频统计(WordCount)--streaming版本
- 2.3 查看Job运行状态
- 3 MapReduce编程接口
- 3.1 Java API
- 3.2 Streaming 接口
- 4 MapReduce平台使用规范
- 4.1 需要使用个人用户访问集群
- 4.2 Job的命名规范
- 4.3 平台资源限制
- 4.4 失败job的处理
- 5 MapReduce程序开发常见问题
- 5.1 hdfs操作查看
- 5.1.1 如何跨集群访问hdfs?
- 5.1.2 如何跨集群访问归档(.har)文件?
- 5.2 Job提交参数
- 5.2.1 提交任务时如何上传文件?
- 5.2.2 如何在HDFS上缓存大文件?(DistributedCache)
- 5.2.2.1 Streaming如何使用-cacheFile/-cacheArchive上传大文件或部署所需的软件环境?
- 5.2.2.2 Java如何用distributedCache缓存大文件?
- 5.2.3 提交任务时使用-D ,-files,-libjars参数不起作用或不能正确提交?
- 5.2.4 提交job时如何指定优先使用用户的jar包?
- 5.2.5 MapReduce中如何使用第三方jar包?
- 5.2.6 指定参数时使用-D和-jobconf的区别?
- 5.3 input输入相关的参数及功能
- 5.3.1 如何指定输入路径?
- 5.3.2 InputFormat
- 5.3.2.1 TextInputFormat(默认的inputFormat)
- 5.3.2.2 CombineTextInputFormat(输入是小文件时使用)
- 5.3.2.3 使用CombineTextInputFormat后,怎么处理不同类型的文件?
- 5.3.3 MapReduce怎么处理不同类型的输入文件?(MultipleInputs)
- 5.4 output输出相关的参数及功能
- 5.4.1 如何对输出进行压缩?
- 5.4.2 OutputFormat
- 5.4.2.1 TextOutputFormat
- 5.4.2.2 如何将MapReduce的一个输出写入多个文件?(TextMultiOutputFormat)
- 5.4.2.3 MapReduce如何使用多路输出?
- 5.5 Map/Reduce执行相关参数功能
- 5.5.1 Mapper
- 5.5.1.1 如何设置map的输出key/value类型?
- 5.5.1.2 如何在map或reduce中获得唯一id号?
- 5.5.2 Partitioner
- 5.5.2.1 如何使用自定义的partitioner?
- 5.5.3 Comparator
- 5.5.3.1 默认的Comparator
- 5.5.3.2 KeyFieldBasedComparator(非字节排序,如按照数字排序)
- 5.5.3.3 排序加速
- 5.5.4 Combiner
- 5.5.4.1 Map端如何使用Combiner?
- 5.5.4.2 常用的Combiner示例
- 5.5.5 Reducer
- 5.5.5.1 如何将Counters写入_SUCCESS文件?
- 5.5.5.2 如何设置MapReduce的语言环境?
- 5.5.6 MR TIPS
- 5.5.6.1 如何自定义计数器和进行状态汇报?
- 5.5.6.2 Streaming程序当map或reduce返回值非0时,整个任务会失败?
- 5.5.6.3 Streaming任务如何指定key,value的分隔符?
- 5.5.6.4 Streaming程序如何判断管道的每个进程返回值?
- 5.5.1 Mapper
- 5.6 Job调度参数功能
- 5.6.1 如何设置JobName?
- 5.6.2 如何设置Reduce的个数?
- 5.6.3 如何限制Job的task并发数?
- 5.6.4 如何设置Job的失败比例?
- 5.6.5 如何设置Job的优先级?
- 5.6.6 如何设置task的超时时间?
- 5.6.7 如何打开/关闭预测执行?
- 5.6.8 如何设置slow start?
- 5.1 hdfs操作查看
- 6 MapReducejob常见问题及解决方法
- 6.1 如何找到失败task的对应输入?
- 6.2 如何判断job的运行结果?
- 6.3 如何处理syslog输出超限的错误?
- 6.4 其他
- 7 如何自主部署hadoop不支持的运行环境
- 7.1 php的mcrypt扩展示例
- 8 附录
- 8.1 MapReduce常见参数表
- 8.2 MultiOutputFormat实例
- 8.3 MultiOutputs实例
- 8.4 hhvm实例
- 8.5 pypy实例
MapReduce简介
- MapRduce采用“分而治之”的思想,把对大规模数据集的操作,分发给一个主节点管理下的各分节点,使其协作共同完成计算任务,然后再通过整合各个分节点的中间结果,得到最终的结果。简而言之,MapReduce就是“任务的分解与计算结果的整合”。
对于这个处理过程,我们将MapReduce高度抽象为两个函数,即map和reduce:
- map负责任务分解
- reduce负责将分解后的多个任务处理的结果整合汇总在一起
MapReduce的特点
- 编程简单,基本的只需实现map和reduce函数。
- 程序员无需担心并行编程中的其他复杂问题,比如,分布式存储、工作调度、负载均衡、容错处理、网络通信等
- 采用MapReduce处理的数据集或任务,必须有以下特性:待处理的数据集或任务可以被分解成多个小的子集,并且每个子集可以完全并行的进行处理,而互不影响
- 实际应用中,有很多典型的应用,包括:URL访问频率统计、分布式grep、倒排索引构建、分布式排序等
MapReduce使用示例
- 本节提供java和streaming方式的典型应用:词频统计,其中streaming支持shell、php、python、perl、C等语言。
词频统计(WordCount)--java版本
示例代码
import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.lib.input.*; import org.apache.hadoop.mapreduce.lib.output.*; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner;
public class WordCount extends Configured implements Tool { public static class MyMap extends Mapper<LongWritable, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); // map函数key:字符串偏移量,LongWritable类型;value:一行字符串内容,Text类型 @Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); StringTokenizer tokenizer = new StringTokenizer(line); //将字符串分割成单词 while (tokenizer.hasMoreTokens()) { word.set(tokenizer.nextToken()); context.write(word, one); //迭代输出key/value对 } } } public static class Reduce extends Reducer<Text, IntWritable, Text, IntWritable> { // reduce函数key:一个单词,Text类型;value:该单词出现的次数列表 @Override public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } context.write(key, new IntWritable(sum)); } } public int run(String[] args) throws Exception { if (args.length != 2) { System.out .println("Usage:hadoop jar WordCount.jar WordCount <input> <output> "); System.exit(-1); } // 必要的配置参数 Configuration conf = getConf(); Job job = new Job(conf); job.setJarByClass(WordCount.class); // 设置主类 job.setJobName("wuyunyun_WordCount"); // 设置JobName job.setMapOutputKeyClass(Text.class); // 设置Job的Map输出key类型(如需指定map的输出Key类型,可通过此参数设置) job.setMapOutputValueClass(IntWritable.class); // 设置Job的Map输出value类型(如需指定map的输出Value类型,可通过此参数设置) job.setOutputKeyClass(Text.class); // 设置Job的输出key类型 job.setOutputValueClass(IntWritable.class); // 设置Job的输出value类型 job.setMapperClass(MyMap.class); // 设置Mapper类 job.setReducerClass(Reduce.class); // 设置Reducer类 job.setNumReduceTasks(1); // 设置Reduce task的个数 job.setInputFormatClass(TextInputFormat.class); // 设置Job输入分割格式(InputFormat) job.setOutputFormatClass(TextOutputFormat.class);// 设置Job的输出格式(OutputFormat) //从命令行参数中获取输入输出路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); boolean success = job.waitForCompletion(true); return success ? 0 : 1; }
public static void main(String[] args) throws Exception { int ret = ToolRunner.run(new WordCount(), args); System.exit(ret); } }
编译测试提交Job
- 编译并打包
javac -d classes WordCount.javajar cvf WordCount.jar -C classes/ .
- 提交Job
hadoop jar WordCount.jar WordCount <input_path> <output_path>例如:hadoop jar WordCount.jar WordCount /home/mr/wuyunyun/testfile /home/mr/wuyunyun/output1
测试数据:
[mr@w-hdp905 wordCount]$ cat testfile hello worldhello wuyunhello kittykitty wuyunyun
测试结果:
[mr@w-hdp905 wordCount]$ ~/software/hadoop/bin/hadoop fs -cat /home/mr/wuyunyun/output1/part-00000hello3kitty2world1wuyun1wuyunyun1
词频统计(WordCount)--streaming版本
示例代码
- map.php
<?php $in=fopen("php://stdin","r"); $out_array=array(); while ($line=fgets($in)){ $wordlist=str_word_count($line,1); foreach($wordlist as $word){ print "$word\t1\n"; } } fclose($in);?>
- reduce.php
<?php $in=fopen("php://stdin","r"); $res_array=array(); while ($line=fgets($in)){ list($word,$count)=split("\t",$line); if($word!="") { $key=$word; $res_array[$key] +=$count; # 对相同的key值进行累加 } } fclose($in); foreach ($res_array as $key => $value) { print "$key\t$value\n"; # 以\t分隔key和value }?>
本地测试程序
测试map程序:[mr@w-m2 ~]$ export LC_ALL=C[mr@w-m2 ~]$ cat testfile | php map.php | sort > map.out测试reduce程序:[mr@w-m2 ~]$ cat map.out | php reduce.php
提交job
- job提交脚本--run.sh
hadoop streaming \ -input /home/mr/wuyunyun/testfile \ -output /home/mr/wuyunyun/output \ -mapper "php map.php" \ -reducer "php reduce.php" \ -jobconf mapred.reduce.tasks=1 \ -file ./map.php \ -file ./reduce.php \ -jobconf mapred.job.name=wuyunyun_wordcount
注:
1. "\"是换行,换行符前务必只保留一个空格
2.可以使用-D 或者 -jobconf指定需要功能参数,但使用-D时,只能将其放置在所有参数的最前面,而-jobconf可以放置在任何位置约定:为了更好的管理mapreduce平台上的job,请遵守job命名规范: 提交者域账户_具有实际意义的job名称,中间用下划线分割。 如邮箱名为:zhangsan-sal@360.cn,请命名为:zhangsan-sal_wordcount 如果您的程序无法在本地进行调试,进行线上测试时,请在jobName后添加后缀"_test",这些测试的失败邮件将只会发给提交人,而不会发给接口人。
查看Job运行状态
- 任务提交成功后,job运行进度会显示在客户端的标准输出中:
注:提交在集群上的job,kill后请务必确认kill成功,以免浪费集群资源
确认kill job操作成功的方法:
- 查看kill命令的返回值,为0,则kill成功:echo $?
- 在JobTracker作业监控页面,Failed Job列表中看到此job,且状态为Killed
MapReduce编程接口
Java API
- 用户编写MapReduce需要实现的类或方法有:
1.Mapper接口:
- 用户需继承Mapper接口实现自己的Mapper,其中包含的方法有:
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {需要实现的方法: //每一个key/value对均会调用一次map方法,默认的map只输出key/value,不进行任何处理。用户需要自己实现map方法,对输入的key/value对进行逻辑处理。 void map(KEYIN key, VALUEIN value, Context context) throws IOException, InterruptedException { //Context为上下文环境对象,用户可通过此对象将输出的key/value写入输出流中、获取配置文件信息、progress信息等; context.write((KEYOUT) key, (VALUEOUT) value); }可选择实现的方法: void setup(Context context) throws IOException, InterruptedException{ // 用户可根据需求实现task的初始化工作(如:打开文件),默认不做任何操作,在task开始前调用一次 // Nothing } void cleanup(Context context) throws IOException, InterruptedException{ // 用户可根据需求实现 task的收尾清理工作(如:关闭文件),默认不做任何操作,在task结束后调用一次 //Nothing }}
2.Reducer接口:
- 用户需继承Reducer接口实现自己的Reducer,其中包含的方法有:
public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {需要实现的方法:// 对每一个key均会调用一次reduce方法,默认的reduce只输出key/value,不进行任何处理 void reduce(KEYIN key, VALUEIN value, Context context) throws IOException, InterruptedException { for(VALUEIN value: values) { context.write((KEYOUT) key, (VALUEOUT) value); } }可选择实现的方法:(同Mapper) void setup(Context context) throws IOException, InterruptedException { //Nothing } void cleanup(Context context) throws IOException, InterruptedException { //Nothing }}
函数中的参数<KEYOUT, VALUEOUT>类型通过setOutputKeyClass()及setOutputValueClass()设置,如示例WordCount的conf配置。
代码片段:Job.setOuputKeyClass(Text.class);Job.setOutputValueClass(IntWritable.class);
注:Reducer接口中的KEYIN,VALUEIN必须和Mapper中的KEYOUT,VALUEOUT类型一致
3.Main函数
建议用户在在main函数中实现Tool接口,通过ToolRunner来运行程序,这样就可以解析通过命令行传递的MapReduce参数。(ToolRunner内部调用GenericOptionParser可以对命令行参数进行解析)
例如:
public int run(String[] args) throws Exception { if (args.length != 2) { System.out .println("Usage:hadoop jar WordCount.jar WordCount <input> <output> "); System.exit(-1); } // 必要的配置参数 Configuration conf = getConf(); Job job = new Job(conf); job.setJarByClass(WordCount.class); // 设置主类 job.setJobName("wuyunyun_WordCount"); // 设置JobName job.setOutputKeyClass(Text.class); // 设置Job的输出key类型 job.setOutputValueClass(IntWritable.class); // 设置Job的输出value类型 job.setMapperClass(MyMap.class); // 设置Mapper类 job.setReducerClass(Reduce.class); // 设置Reducer类 job.setNumReduceTasks(1); // 设置Reduce task的个数 job.setInputFormatClass(TextInputFormat.class); // 设置Job输入分割格式(InputFormat) job.setOutputFormatClass(TextOutputFormat.class);// 设置Job的输出格式(OutputFormat) //从命令行参数中获取输入输出路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); boolean success = job.waitForCompletion(true); return success ? 0 : 1; }
public static void main(String[] args) throws Exception { int ret = ToolRunner.run(new WordCount(), args); System.exit(ret); }
Streaming 接口
- Hadoop MapReduce和HDFS采用Java实现,默认提供Java编程接口,另外提供了C++编程接口和Streaming框架。Streaming框架允许任何程序语言实现的程序在Hadoop MapReduce中使用,方便原有程序向Hadoop平台移植
1. Mapper程序
- 用户程序实现的mapper程序需要将从标准输出中获得的每一行内容转换成key/value对(“key \t value”)作为mapper的输出。
默认情况下,一行中第一个tab之前的部分作为key,之后的(不包括tab)作为value。如果没有tab,整行作为key值,value值为null。(Reducer同理)
程序返回值:默认情况,streaming task返回非零时表示失败,所以grep示例中我们需要设置exit 0,以避免grep查找不到时程序返回非零值 link pipe
注:用户可根据需求直接在脚本中获取一些常用的环境变量,进行判断和逻辑处理。
2. Reducer程序
- 用户的实现的reducer程序从标准输出获取输入的key/value对,经过用户逻辑处理后得到聚合的key/value对,作为reduer的输出即MapReduce的最终输出,写至HDFS上。
3.streaming程序返回值 对于Streaming程序,MR框架会判断map和reduce的返回值,如果返回值非0,则认为map或reduce执行中出现错误,重试数次(默认为4)后仍然失败,会导致整个任务失败
4.提交job:
1) 指定附加配置参数:
- 用户可以使用“-jobconf <name>=<value>”增加一些配置变量。
如我们指定jobName,可以使用“-jobconf mapred.job.name=xxx_wordCount”
指定reduce task个数,可以使用“-jobconf mapre.reduce.tasks=1”
注:使用-D和-jobconf指定配置参数的区别:-D是通用的,-jobconf 只适用于streaming
2)将文件打包到提交的作业中:
- 任何可执行文件都可以被指定为mapper/reducer。这些可执行文件不需要事先存放在集群上; 如果在集群上还没有,则需要用-file选项让MapReduce框架把可执行文件作为作业的一部分,一起打包提交。如streaming方式提交job
3) 上传大文件和压缩文件
- 有时我们需要上传一些大文件或压缩词典文件,请使用-cacheFile和-cacheArchive选项在集群中分发文件。
具体方法请见:使用-cacheFile/cacheArchive上传大文件至HDFS
4) 只使用Mapper的作业:
- 有时只需要map函数处理输入数据。这时只需把mapred.reduce.tasks设置为零,MapReduce框架就不会创建reducer任务,mapper任务的输出就是整个作业的最终输出。
使用-reducer NONE 或 -jobconf mapred.reduce.tasks=0 选项。
5)为作业指定其他插件:
- 和其他普通的Map/Reduce作业一样,用户可以为streaming作业指定其他插件:
-inputformat <JavaClassName> //例如:-inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormat -outputformat <JavaClassName> //例如:-outputformat org.apache.hadoop.mapred.TextMultiOutputFormat -partitioner <JavaClassName> //例如:-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner -combiner <JavaClassName/可执行程序> //例如:-combiner "sh myCombiner.sh"
如果不指定inputformat,则默认会使用TextInputFormat。
6) 集群中的软件环境版本
- 这里主要介绍当前集群MR软件版本:
- Hadoop版本:hadoop-0.20.1
- Perl版本:5.10.1
- PHP版本:
/usr/bin/php 版本为:5.1.6/usr/local/bin/php 版本为:5.2.10 (建议使用此版本,其中包括常用的php扩展包)/usr/local/php7/bin/php 版本为:7.0.0 RC 3(safe.lycc,dfs.shjt) 7.0.0 Beta 3(qss.zzbc2) (php7对原有php兼容性能肯定比hhvm要好,所以后续hadoop php性能优化会主推php7,欢迎大家试用、提出建议。)hhvm-2.3.2,此版本性能比原生php性能好,如果没有使用特殊的php扩展,强烈推荐使用这个版本提高性能,参见-cacheArchive的方式部署软件环境
- Python版本:
/usr/bin/python 版本2.4.3 (默认)/usr/bin/python2.6 版本为2.6.5 pypy 高性能的python版本(由hadoop粉丝 范晓恺 <fanxiaokai@360.cn>提供)
如不能满足应用需求,请自行上传软件环境, 参见-cacheArchive的方式部署软件环境
MapReduce平台使用规范
需要使用个人用户访问集群
- HDFS目录访问:请先发邮件到g-xitong-hadoop邮件组,标明申请开通的对应组、个人域账号以及访问目录。待接口人审批后,则可以以个人账号访问目录。
- MR用户组提交:请先发邮件到g-xitong-hadoop邮件组,申请开通个人权限。然后在提交作业时,使用-jobconf mapred.fairscheduler.pool=,指定job需要提交到的组pool。
Job的命名规范
- 为了更好的管理mapreduce平台上的job,请遵守如下job命名规范:
- mapred.job.name=提交者邮箱域账户_具有实际意义的job名称,中间请用下划线分割
- 如邮箱名为:zhangsan-sal@360.cn,请命名为:zhangsan-sal_wordcount
- 无法进行本地调试的job,请在命名后添加“_test”标注
- mapred.job.name=提交者邮箱域账户_具有实际意义的job名称,中间请用下划线分割
平台资源限制
- 单个map/reduce task内存限制为1G
- Map task总个数不能超过20万个
- Job的jar包不超过100M
- 磁盘限制:
- 每个map的输出不超过20G
- Task log不超过1G
失败job的处理
- 对于提交至集群上的Job,我们会进行失败Job监控,同时发送失败job的邮件至job提交者。请job提交者规范job命名,否则我们的监控脚本会因无法匹配job提交者,而将失败邮件推送给job提交者所在的hadoop组所有成员。
为避免其他同事受此干扰,请您按照我们的约定对Job命名进行严格规范,谢谢。
- 失败邮件页面如下:
MapReduce程序开发常见问题
hdfs操作查看
如何跨集群访问hdfs?
命令参数形式 -fs <local|namenode:port> 使用范例 ./hadoop fs -ls hdfs://w-namenode.dfs.shgt.qihoo.net:9000/home 常用集群namenode safe_lycc hdfs://namenode.safe.lycc.qihoo.net:9000 qss_zzbc2 hdfs://w-namenode.qss.zzbc2.qihoo.net:9000 dfs_shjt hdfs://w-namenode.dfs.shgt.qihoo.net:9000 TIP:报attempt错误,请到云图为相关客户机申请集群访问加白。
如何跨集群访问归档(.har)文件?
使用范例 1)查找归档文件 ./hadoop fs -ls hdfs://w-namenode.dfs.shgt.qihoo.net:9000/home/mr/example.har 2)查看归档文件里的数据 ./hadoop fs -ls har://hdfs-w-namenode.dfs.shgt.qihoo.net:9000/home/mr/example.har 3)查看归档文件里的子目录 ./hadoop fs -ls har://hdfs-w-namenode.dfs.shgt.qihoo.net:9000/home/mr/example.har/SubDir
Job提交参数
提交任务时如何上传文件?
上传本地文件: -file #上传用户的本地脚本文件(1个)java程序请使用-files -libjars #上传用户指定的jar包并加入classpath中(多个jar包以逗号分隔) -cacheFile #指定使用缓存在hdfs上的大文件 -cacheArchive #指定使用缓存在hdfs上的压缩文件注:
1)-libjars参数需放置在java 主类名之后,作为第一个指定参数
2)以上指定参数上传的文件或jar包均会打入job的jar包中,并在map task初始化时分发到各个节点上,如果job.jar包过大(>100M),加之map并发过大,则可能会导致task初始化时超时而失败,建议大的文件使用-cacheArchive/-cacheFile上传
如何在HDFS上缓存大文件?(DistributedCache)
- distributedCache可以把HDFS上的文件(数据文件、压缩文件等等)分发到各个执行task的节点。执行map或者reduce task的节点就可以在本地,直接用java的IO接口读取这些文件。
有两个需要注意的地方:被分发的文件需要事先存储在hdfs上;这些文件是只读的。 下面分别介绍Streaming和Java方式如何使用。
Streaming如何使用-cacheFile/-cacheArchive上传大文件或部署所需的软件环境?
-cacheFile: 用来上传大文件
-cacheArchive:用来上传大的压缩文件
此处我们提供一个实例: 使用-cacheArchive的方式上传data.tar.gz 具体步骤如下:
- 1. 将依赖的数据文件上传到hdfs某个目录:
例如:hadoop fs -put data.tar.gz hdfs:///user/cache-test/data.tar.gz
- 2.提交job,采用cacheArchive方式使用数据文件。
例如:提交job的命令行参数中添加 -cacheArchive hdfs:///user/cache-test/data.tar.gz#test
- 在上面的例子里,url中#后的部分是建立在任务当前工作目录下的符号链接的名字,此处为test,test指向data解压后的目录。
-cacheArchive自动将压缩文件data进行解压,用户不需再做解压操作,只需在程序中采用相对路径引用即可。
注:-cacheArchive支持的是归档压缩文件(zip、tar、tgz、tar.gz)和jar文件解压
-cacheFile使用方法类似:
-cacheFile hdfs:///user/cache-test/testfile.txt#test这里的任务的当前工作目录下有一个“test”符号链接,它指向testfile.txt文件在本地的拷贝。如果有多个文件,选项可以写成:-cacheFile hdfs:///user/cache-test/testfile1.txt#test1 -cacheFile hdfs:///user/cache-test/testfile2.txt#test2
注:与-cacheArchive不同,cacheFile不对文件解压。
-cacheArchive的方式部署软件环境
以使用python2.7.3为例,具体使用方式如下:
上传python2.7包至hdfs指定目录: hadoop fs -put ./python2.7.tgz hdfs:///home/mr/lib/ 指定cacheArchive: -cacheArchive hdfs:///home/mr/lib/python2.7.tgz#python 指定map或者reduce使用的python位置: python/bin/python 实例; hadoop streaming \ -input /home/xitong/tmp/test/data \ -output /home/xitong/tmp/test/output/out3 \ -mapper "./python/bin/python test.py" \ -reducer "cat" \ -file test.py \ -cacheArchive hdfs:///home/mr/lib/python2.7.tgz#python
注:部署的软件环境,若是使用了#设置,请在原本的压缩包结构上再添加设置的目录名,以防止运行找不到环境。
例如:使用-cacheArchive python2.7.tgz#python,请先确认压缩包的解压的目录,然后在解压目录前添加#标识的软链名,如下:
python2.7.tgz解压后的目录为./lib与./bin……,则在程序中使用的运行环境目录为./python/lib与./python/bin……
Java如何用distributedCache缓存大文件?
使用distributedCache的步骤:
- 在conf里正确配置被分发的文件的路径(hdfs上的路径)
- 在自定义的mapper或reducer中获取文件下载到本地后的路径(linux文件系统路径);一般是重写configure(老API)或者重写setup(新API)
- 在自定义的mapper或reducer类中读取这些文件的内容
distributedCache也提供创建符号链接的功能,第2步就不需要获取文件在本地的路径,直接使用约定的符号链接即可。
示例:
- 将需要依赖的文件或者归档压缩包上传至HDFS上:
hadoop fs -put data /home/mr/test/distributedcache/hadoop fs -put mydata.tgz /home/mr/test/distributedcache/
- 在Java main函数中添加DistributedCache(old API):
JobConf job = new JobConf();DistributedCache.addCacheFile(new URI("hdfs:///home/mr/test/distributedcache/data#data"), job); // "#"后的data表示文件data的软连接名DistributedCache.addCacheArchive(new URI("hdfs:///home/mr/test/distributedcache/mydata.tgz#mydata", job);
- 在Mapper或者Reducer中使用cached files:
public static class MapClass extends MapReduceBase implements Mapper<K, V, K, V> { private Path[] localArchives; private Path[] localFiles;
@Override public void configure(JobConf job) { // Get the cached archives/files localArchives = DistributedCache.getLocalCacheArchives(job); localFiles = DistributedCache.getLocalCacheFiles(job); } @Override public void map(K key, V value, OutputCollector<K, V> output, Reporter reporter) throws IOException { // Use data from the cached archives/files here // ... // ... output.collect(k, v); }}
注:如果cache文件有变动或者更新,需要重新将其上传至HDFS上
提交任务时使用-D ,-files,-libjars参数不起作用或不能正确提交?
- 正确使用的方法需要满足两个条件:
- 程序实现:
- 使用GenericOptionParser解析hadoop命令行。
- 需要实现Tool接口,通过ToolRunner来运行程序。(ToolRunner内部调用GenericOptionParser对命令行参数进行解析)
- 提交任务时:
- 提交任务时,需要将上述参数放在jar的主类后面,紧跟主类名,否则不会解析成功。
- 注:streaming支持上述参数, java上传本地文件的参数是-files,streaming同时支持-file和-files。
提交job时如何指定优先使用用户的jar包?
请设置:mapreduce.user.classpath.first为true, 默认为false
MapReduce中如何使用第三方jar包?
有以下2种常用方式使用第三方jar包:
1.Streaming程序使用-libjars参数指定本地jar包路径;
2.Java方式借助DistributedCache.addFileToClassPath机制(对于使用java security相关包,必须采用这种方式,如果要用libjars和tmpjars方式,需要在本地将文件解开,再上传到hdfs上,违反了security的特性)
示例:DistributedCache.addFileToClassPath(new Path("/home/mr/libjars/bcprov-ext-jdk15on-148.jar"),conf);
指定参数时使用-D和-jobconf的区别?
- 在提交streaming程序时 -jobconf和-D 均可以指定提交功能参数,但使用-D 时必须将次参数放置在所有参数之首;在提交java中只能使用-D
P.S. 此处可能有人会容易记混 -jobconf 和 -D, 提供一个小窍门, 命令行中直接运行 "hadoop streaming" 回车会得到如下提示: [spider@client16v ~/hbase-mr-tools/bin]$ hadoop streaming15/03/11 15:54:04 ERROR streaming.StreamJob: Missing required options: input, outputUsage: $HADOOP_HOME/bin/hadoop jar \ $HADOOP_HOME/hadoop-streaming.jar [options]Options: -input <path> DFS input file(s) for the Map step -output <path> DFS output directory for the Reduce step -mapper <cmd|JavaClassName> The streaming command to run -combiner <cmd|JavaClassName> The streaming command to run -reducer <cmd|JavaClassName> The streaming command to run -file <file> File/dir to be shipped in the Job jar file -inputformat TextInputFormat(default)|SequenceFileAsTextInputFormat|JavaClassName Optional. -outputformat TextOutputFormat(default)|JavaClassName Optional. -partitioner JavaClassName Optional. -numReduceTasks <num> Optional. -inputreader <spec> Optional. -cmdenv <n>=<v> Optional. Pass env.var to streaming commands -mapdebug <path> Optional. To run this script when a map task fails -reducedebug <path> Optional. To run this script when a reduce task fails -verboseGeneric options supported are-conf <configuration file> specify an application configuration file-D <property=value> use value for given property-fs <local|namenode:port> specify a namenode-jt <local|jobtracker:port> specify a job tracker for corona-jtold <local|jobtracker:port> specify a job tracker for common mapreduce-files <comma separated list of files> specify comma separated files to be copied to the map reduce cluster-libjars <comma separated list of jars> specify comma separated jar files to include in the classpath.-archives <comma separated list of archives> specify comma separated archives to be unarchived on the compute machines.The general command line syntax isbin/hadoop command [genericOptions] [commandOptions]
可以看到 "-D" 属于Generic options,而且"bin/hadoop command [genericOptions] [commandOptions]" 表明genericOptions 在 commandOptions之前,例如 还有一个参数设置 "-input", "-input" 不在"Generic options" 列表中,故属于 commandOptions, 所以 "-D" 一定在 "-input" 之前。
input输入相关的参数及功能
如何指定输入路径?
Java方式,程序通过命令行获取输入路径:
FileInputFormat.setInputPaths(job, new Path(args[0]));
Streaming方式,提交job时指定-input,如:
-input /home/mr/wuyunyun/input-dir
Tips:
1)输入路径可以为一个或多个目录/文件
- 用户可以使用“,”(逗号)分割多个目录/文件,如: -input /home/mr/wuyunyun/input-dir1,/home/mr/wuyunyun/input-dir2
2)输入路径支持通配
- 如:-input /home/mr/wuyunyun/input-dir/part-0000[0-9] //匹配目录input-dir下的文件part-00000~part-00009
- -input /home/mr/wuyunyun/input-dir/*-test-* //匹配目录input-dir下包含“-test-”的所有文件做输入*
3)使用har归档文件做输入时,需指定input为har://
- 注:使用har归档文件支持通配符
正确用法:-input har:///home/xxxx/archive/data/20131314*.har/*/
错误用法:-input har:///home/xxxx/archive/data/2013*/*/
[请注意!] 若har文件下有多级目录,如har:///../**.har/A/B/C.gz,input的匹配行请设置到匹配到文件的所在目录,如har:///../**.har/*/*/ [请注意!] 若需要跨集群读取har文件,如har://hdfs-namenode.safe.lycc.qihoo:9000/../*.har/*/*,请设置fs.default.name=hdfs://namenode.safe.lycc.qihoo:9000。并将提交中使用到的路径相关配置,设置为包含对应集群名的全路径。
4)如何忽略压缩损坏文件?
- 需要MapReduce处理过程中忽略压缩损坏的文件,需要设置参数mapred.ignore.badcompress为true
5)输入路径中带有多级目录
- 设置参数mapred.input.dir.recursive为true,可以支持在输入路径中递归匹配各级目录中的文件。
如:输入路径为:/home/mr/wuyunyun/input-dir1/sub-dir1/file1,/home/mr/wuyunyun/input-dir1/sub-dir2/file2,则可设置此参数为true,同时指定-input /home/mr/wuyunyun/input-dir1/ 即可匹配input-dir1中子目录中的文件
InputFormat
TextInputFormat(默认的inputFormat)
- MapReduce默认的inputFormat是TextInputFormat,用于处理文本格式的输入。它的key为一行的偏移量,value为此行的内容。
如果某行的value过大,会导致OOM,建议设置一行处理最大字节数来避免job失败:mapred.linerecordreader.maxlength(单位:字节),超过设定值部分将被丢弃
CombineTextInputFormat(输入是小文件时使用)
- 当处理大量的小数据文件时,如果文件不能被inputformat切分(如gz压缩文件),则会启动很多的map,比如几十万个map,这样会带来几个问题:
- 对jobtracker内存造成压力。当一个job完成初始化后,jobtracker会在内存中维护该job的所有map/reduce task的相关信息。含有大量task的job会占用jobtracker的大量内存。
- 每个map处理的数据很小,执行时间很短,比如几秒,则task调度和启动的时间开销将会非常可观,大量的时间花费在task启动时间上。
- 因此需要使用CombineTextInputFormat对输入数据进行合并。
使用方法:
Streaming程序提交任务时指定: -inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormatJava new API程序 import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat; job.setInputFormatClass(CombineTextInputFormat.class); //设置inputformat类Java old API程序 import org.apache.hadoop.mapred.lib.CombineTextInputFormat; conf.setInputFormat(CombineTextInputFormat.class); //设置inputformat类注:默认的合并大小是256M,即每个map处理256M数据,用户亦可根据数据量进行自定义设置,通过参数mapred.max.split.size(单位:字节)
如java程序设置
conf.setLong(“mapred.max.split.size”, 1073741824);//设置每个map处理的数据为1G。
Streaming程序设置:-jobconf mapred.max.split.size=1073741824
一般情况无需设置mapred.max.split.size
mapred.max.split.size设置多少合适?mapred.max.split.size用于指定每个map最多处理的数据量。单位为Bytes。一般建议每个map输出的数据量在300M以内,这样中间结果数据能保存在内存中,减少磁盘IO。可以根据map的输入/输出比决定每个mapred.max.split.size的设置。
如何严格分割为同样大小的split?根据combineInputFormat的split切分规则,要完全按照要求分割,一共要设置四个值:mapred.max.split.size = mapred.min.split.size.per.node = mapred.min.split.size.per.rack = {splitSize}mapred.max.num.blocks.per.split = 100(默认,可尽量大)
使用CombineTextInputFormat后,怎么处理不同类型的文件?
- 采用CombineTextInputFormat后,一个map可能处理多个文件的不同块。
对于java,仍然采用MultipleInputs对不同类型的文件采用不同的mapper进行处理(参见MultiInputs)
对于streaming,首先需要对输入文件进行分组,确保每个map处理的文件都是同一种类型的,然后再根据环境变量map_input_file环境变量判断该map处理的是何种类型文件,再做差异化处理。 使用方法如下:
hadoop streaming \-inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormat \-jobconf mapred.max.split.size=1073741824 \-mapper "sh map.sh" \-reducer NONE \-input /home/xitong/data/wangzhiqiang/ \-output /home/xitong/tmp/wangzhiqiang/out1 \-file map.sh \-jobconf combineinput.pool.filters="*example0*,*example1*,*example2*" \ #注:请注意文件名中不包含“example11*,example22*”,否则匹配会异常-jobconf mapred.job.name="wangzhiqiang-test-combine-filter"
combineinput.pool.filters:指定输入文件的分组配置,注意,需要为正则式表达式,否则匹配不到该文件将不进行处理。
该例中有三类文件,分别为文件名中包含example0, example1, example2的文件。
在map中就可以通过map_input_file环境变量判断该map处理的是哪种类型的文件。 注意map_input_file对应的值是该map处理的第一个文件的HDFS存储全路径名(例如:hdfs://w-namenode.safe.zzbc.qihoo.net:9000/home/mr/data/test_multiinputs/data1)在map执行过程中该变量不会改变,也就是说,对于该map中后续处理的文件的文件名并没有反映到map_input_file环境变量里。
MapReduce怎么处理不同类型的输入文件?(MultipleInputs)
- 在mapreduce程序可能同时处理不同类型的文件,一种方法是使用MultipleInputs对不同输入类型文件使用不同的Mapper类分别进行处理(java),另一种方法是根据处理文件名进行不同的处理(streaming)。
- Java方式(使用MultipleInputs示例)
//对于/home/mr/data/test_multiinputs/data1目录,采用Map1进行处理MultipleInputs.addInputPath(conf,new Path("/home/mr/data/test_multiinputs/data1"),TextInputFormat.class,Map1.class); //对于/home/mr/data/test_multiinputs/data2目录,采用Map2进行处理MultipleInputs.addInputPath(conf,new Path("/home/mr/data/test_multiinputs/data2"),TextInputFormat.class,Map2.class);
- Streaming方式
Streaming方式只能根据处理的文件名进行判断,文件名可以从环境变量里获取map_input_file。streaming方式如果采用CombineTextInputFormat,则可以借助对输入路径进行分组来实现(参见上节CombineTextInputFormat)
output输出相关的参数及功能
如何对输出进行压缩?
当前默认对MapReduce的输出不进行压缩,如果数据量比较大,建议采用输出压缩以节省HDFS存储空间。
用户可采用在java代码中设置压缩参数,或者在job提交时指定压缩参数
- java代码中设置:
conf.setBoolean("mapred.output.compress", true); conf.set("mapred.output.compression.codec", "org.apache.hadoop.io.compress.GzipCodec");
- 提交job时指定如下参数:
-D mapred.output.compress=true \ -D mapred.output.compression.codec=<codec> \
常用的压缩算法指定参数:
gz压缩:-D mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodeclzo压缩:-D mapred.output.compression.codec=com.hadoop.compression.lzo.LzoCodecBzip2压缩:-D mapred.output.compression.codec=org.apache.hadoop.io.compress.BZip2CodecOutputFormat
TextOutputFormat
输出时如何忽略多余的分隔符
对于streaming Job,当只需要输出key时,默认的TextOutputFormat会输出多余的分割符(默认为’\t’),此时可以通过设置如下参数不输出多余的分隔符。
JAVA程序:
conf.setBoolean("mapred.textoutputformat.ignore.separator",true);
Streaming程序:
-jobconf mapred.textoutputformat.ignore.separator =true
如何将MapReduce的一个输出写入多个文件?(TextMultiOutputFormat)
如果一个reduce输出过大(或者无reduce的map输出过大),会对下游的job产生较大的处理压力。为了避免此情况,可以采用TextMultiOutputFormat对reduce的输出进行切分,让一个reduce的输出可以至多个文件,
切分后的文件命名为:part-r/m-p-00x(其中p为partition号,part-r-00000-000),不会影响reduce的分区。
使用方式如下:
1.此功能要求输出需要必须采用输出压缩,请先确认输出压缩功能设置为true,再指定压缩方式,下面采用了gz压缩:-D mapred.output.compress=true \-D mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec
2.指定outputformat为TextMultiOutputFormat:streaming程序请使用老API: -outputformat org.apache.hadoop.mapred.TextMultiOutputFormatJAVA程序的新API: 可在程序中设置:job.setOutputFormatClass(TextMultiOutputFormat.class);
注:- 当前默认的输出切分大小是512M(压缩前),可以通过参数:mapred.reduce.max.size.per.file设置切分大小,单位是字节- YARN 中此类包路径修改,使用class请用:org.apache.hadoop.mapred.lib.TextMultiOutputFormat 或者 org.apache.hadoop.mapreduce.lib.output.TextMultiOutputFormat
MapReduce如何使用多路输出?
Streaming支持多路输出(SuffixMultipleTextOutputFormat)
如下示例:
hadoop streaming \-input /home/mr/data/test_tab/ \-output /home/mr/output/tab_test/out19 \-outputformat org.apache.hadoop.mapred.lib.SuffixMultipleTextOutputFormat \ # 指定outputformat为org.apache.hadoop.mapred.lib.SuffixMultipleTextOutputFormat-jobconf suffix.multiple.outputformat.filesuffix=a,c,f,abc,cde \ # 指定输出文件名的前缀,所有需要输出的文件名必须通过该参数配置,否则job会失败-jobconf suffix.multiple.outputformat.separator="#" \ # 设置value与文件名的分割符,默认为“#”,如果value本身含有“#”,框架会自动匹配至最后一个分隔符,用户亦可通过该参数重新设置其他的分隔符。-mapper "cat" \-reducer "sh reduce.sh" \-file reduce.sh
注:1、标记为红色的参数必须设置,参数说明请见注释
注:2、当value为空时要在key值与"suffix.multiple.outputformat.separator"之间补充一个"\t"分隔符
注:3、输出不能有空行
注:4、key和value值中不能有换行符
Map或者reduce里需要在每个记录的reduce追加“#+文件名”
#!/bin/bash
while read linedo key=$(echo $line | awk -F' ' '{print $1}') value=$(echo $line | awk -F' ' '{print $2}') if [ "$key" == "a" ] then echo "$key$value#a" fi
if [ "$key" == "c" ] then echo "$key$value#c" fi
if [ "$key" == "f" ] then echo "$key$value#f" fi
if [ "$key" == "abc" ] then echo "$key$value#abc" fi
if [ "$key" == "cde" ] then echo "$key$value#cde" fidone
Java如何实现多路输出?(MultiOutputs/MultiOutputFormat)
- 下面用MultiOutputFormat实现了一个小需求:把原始的日志文件用hadoop做清洗后,按业务线输出到不同的目录下去。
核心即实现继承自MultipleOutputFormat的输出文件分类的方法,核心代码如下:
// MultipleTextOutputFormat 继承自MultipleOutputFormat,实现输出文件的分类 public static class PartitionByCountryMTOF extends MultipleTextOutputFormat<NullWritable, Text> { // key is // NullWritable, // value is Text protected String generateFileNameForKeyValue(NullWritable key, Text value, String filename) { String[] arr = value.toString().split(",", -1); String country = arr[4].substring(1, 3); // 获取country的名称 return country + "/" + filename; }
完整代码及示例请移步 #MultiOutputFormat实例
- 新API如何使用MultiOutputs?
1.在main函数中指定多输出路径的名字,指定OutputFormat类及输出Key/Value类型。
MultipleOutputs.addNamedOutput(job,"MOSInt",TextOutputFormat.class,Text.class,IntWritable.class);MultipleOutputs.addNamedOutput(job,"MOSText",TextOutputFormat.class,Text.class,Text.class);
2.在Mapper中处理:
@Overrideprotected void setup(Context context) throws IOException,InterruptedException { mos = new MultipleOutputs<Text,IntWritable>(context);}
@Overridepublic void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException{ String line = value.toString(); String[] tokens = line.split("-"); mos.write("MOSInt",new Text(tokens[0]), new IntWritable(Integer.parseInt(tokens[1]))); // 直接输出至:/home/mr/wuyunyun/output/MOSInt-m-00000 mos.write("MOSText", new Text(tokens[0]),tokens[2]); // 直接输出至:/home/mr/wuyunyun/output/MOSText-m-00000 mos.write("MOSText", new Text(tokens[0]),line,tokens[0]+"/part"); // 输出至指定的目录下:/home/mr/wuyunyun/output/xxx/part-m-00000}
@Overrideprotected void cleanup(Context context) throws IOException,InterruptedException { mos.close();}
完整代码及示例请移步 #MultiOutputs实例
Map/Reduce执行相关参数功能
Mapper
如何设置map的输出key/value类型?
用户可以按需设置map的输出key/value类型。
Streaming方式:
- 默认的map输出key/value类型为io.apache.hadoop.Text,可通过如下参数设置:
-jobconf mapred.mapoutput.key.class=<类全名> -jobconf mapred.output.value.class=<类全名>如:-jobconf mapred.mapoutput.key.class=org.apache.hadoop.io.Text-jobconf mapred.output.value.class=org.apache.hadoop.io.Text
Java方式:
- Java中需要显式在Mapper声明中指定map输出key/value的类型:
public static class MyMap extends Mapper<Text, Text, Text, Text> 同时设置map输出key/value类型为声明的类型: job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class);
如何在map或reduce中获得唯一id号?
可以通过在老API中的configure()或者新API中的setup()中通过如下接口获得: 以setup()为例:
public void setup(Context context) throws IOException, InterruptedException { //获得attempt id,如:attempt_201207171155_0009_m_000001_0 context.getConfiguration().get("mapred.task.id"); //获得task id, 如:task_201207171155_0009_m_000001 context.getConfiguration().get("mapred.tip.id"); //获得该map或reduce的索引号,即是第几个map/reduce task,去task id后面的整数 context.getConfiguration().getInt("mapred.task.partition"); //该task是map还是reduce context.getConfiguration().getBoolean("mapred.task.is.map"); //获得job id,如:job_201207171155_0009 context.getConfiguration().get("mapred.job.id");}
Streming程序中可以通过环境变量获取,比如bash脚本
#!/bin/bashecho ${mapred_task_id} >&2 #bash中不允许变量中存在.,所以用_代替echo ${mapred_tip_id} >&2
另外,通过-D或者-jobconf指定的自定义的参数也可以通过环境变量取得。
Streaming常用的环境变量:
Partitioner
如何使用自定义的partitioner?
Streaming任务默认的计算partition的方式为key.hashCode()%numReduces。
用户也可按需自己实现指定partitioner。当前MapReduce框架除了默认的hashCode
- 写程序实现自定义的Partitioner
- 用户可以采用自定义的Partitioner,以决定key被送到哪个reduce中。
用户编写自定义的Partitioner类,需实现org.apache.hadoop.mapred.Partitioner接口,并实现configure(JobConf conf)和getPartition(K,V,numReduces)。
例如:
package com.qihoo.xitong.mapred;
import org.apache.hadoop.mapred.*;import org.apache.hadoop.io.Text;public class NumberialPartition implements Partitioner<Text,Text>{ public void configure(JobConf conf){} public int getPartition(Text key,Text value,int numReduces){ return Integer.parseInt(key.toString())%numReduces; }}
提交时需要将该段代码编译打包并通过-libjars指定,同时设置-partitioner参数。例如:
~/software/hadoop/bin/hadoop ~/software/hadoop/contribe/streaming/hadoop-xxxx-streaming.jar \-libjars MyPartitioner.jar \-partitioner com.qihoo.xitong.mapred.NumberialPartition
- 按key中的某些字段计算partition(KeyFieldBasedPartitioner)(常用来做二次排序)
- 若需要按key中的某些字段的hash计算partiton,则可以采用KeyFieldBasedPartitioner,并设置相关的参数即可实现。
Map/Reduce框架提供了KeyFieldBasedPartitioner类,便于用户设置按key中的某些字段计算partition。
例如: 对如下输入,期望将同一个公司的记录由同一个reduce进行处理,即按照第二个字段计算partiton即可:com.qihoo.safecom.qihoo.desktopcom.baidu.zhidaocom.baidu.baike
提交命令如下:
~/software/hadoop/bin/hadoop jar ~/software/hadoop/contrib/streaming/hadoop-0.20.1.16-streaming.jar \-Dmap.output.key.field.separator="." \-Dmapred.text.key.partitioner.options=-k2,2 \-input /test/keyfield_par*.txt \-output /test/wzq/out$(date +%s) \-mapper "cat" \-reducer "cat" \-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner \-jobconf mapred.reduce.tasks=10 \-jobconf mapred.job.name="wuyunyun_KeyFieldBasedPartition_test"参数说明:map.output.key.field.separator指定map输出的key中各字段的分隔符。注意:该参数不支持转义。比如指定’\t’分割必须做如下设置:-jobconf map.output.key.field.separator=" "mapred.text.key.partitioner.options指定按第几个字段计算partition。可以指定多个字段。例如-k2,3表示按第2-3个字段计算partition。
Comparator
默认的Comparator
KeyFieldBasedComparator(非字节排序,如按照数字排序)
Streaming默认情况下对key按照字典序进行排序。如果需要按照key的某个字段进行排序需要采用KeyFieldBasedComparator,并设置相关的参数。
例如:
对于如下的map输出,期望按照key(前两列)中第一列按字典序,第二列按数字进行排序。com.sina100value_sina1com.sina101value_sina2com.sina99value_sina3com.baidu28value_baidu1com.baidu26value_baidu2com.baidu122value_baidu3
若只设置一个reduce,则预期reduce处理的顺序如下:com.baidu26value_baidu2com.baidu28value_baidu1com.baidu122value_baidu3com.sina99value_sina3com.sina100value_sina1com.sina101value_sina2
提交命令如下:/home/work/software/hadoop/bin/hadoop jar /home/software/hadoop/contrib/streaming/hadoop-xxx-streaming.jar \ -Dmapred.output.key.comparator.class=org.apache.hadoop.mapred.lib.KeyFieldBasedComparator \ -Dstream.map.output.field.separator="" \ -Dstream.num.map.output.key.fields=2 \ -Dmapred.text.key.comparator.options="-k1,1 -k2,2n" \ -Dmapred.job.name="wuyunyun_keyFieldBasedComparator_test" \ -mapper "cat" \ -reducer "cat" \ -input /test/keyfield_numsort.txt \ -output /test/wzq/out$(date +%s)
参数说明:
mapred.output.key.comparator.class 参数指定对key进行比较所用的comparator类,此处指定为org.apache.hadoop.mapred.lib.KeyFieldBasedComparator,用户可以自己实现comparator,通过该参数指定,使用自定义的comparator。stream.num.map.output.key.fields 参数指定用哪几个列作为key。mapred.text.key.comparator.options 参数指定用于排序的列和排序方法。例如:“-k2,2n”指定按第2列以numerical方式由小到大排序,“-k2,2nr” 指定按第2列以numerical方式由大到小排序。
排序加速
Map输出排序系统默认在一个大buffer内先对partition进行排序,再对parition内的key进行排序。经过优化的map排序是将一个大buffer变为多个小buffer,每个小buffer即对应一个partition,排序时各个小buffer进行内部排序。对于瓶颈在MapReduce Write的job,此优化可以提升10%左右的性能,用户只需要在job提交时设置:mapred.map.output.blockcollector=true, 即可生效。
- 但不是所有情况都适合使用此优化功能:
- 对于瓶颈在于“读”或者用户处理逻辑中时,map排序优化的效果可以忽略不计;
- 此优化,暂时只支持key/value类型同时为“org.apache.hadoop.Text/BytesWritable”类型,map的K/V类型不符合的系统会自动采用默认排序方式;
- 由于此优化采用字节序排序方式,所以不支持用户指定的keyCompartor;
- 此优化不适合reduce过多(>=10000个)且reduce数据倾斜的job,会因为map输出的index文件过多导致OOM。
- 对于瓶颈在于“读”或者用户处理逻辑中时,map排序优化的效果可以忽略不计;
Combiner
Map端如何使用Combiner?
Mapreduce中的Combiner就是为了避免map任务和reduce任务之间的数据传输而设置的,Hadoop允许用户针对map task的输出指定一个合并函数,即减少传输到Reduce中的数据量。它主要是为了削减Mapper的输出从而减少网络带宽和Reducer之上的负载。
Combiner可以看做是Map阶段的reduce,系统中没有默认的combiner,需要用户自己实现,并显示设置。
例如:
Streaming方式,提交job时指定: -combiner <Combiner可执行脚本 OR Combiner的Java类名>
Java方式,程序中设置: conf.setCombinerClass(Combiner.class);
如果Reducer只运行简单的分布式方法,例如最大值、最小值、或者计数,那么我们可以让Reducer自己作为Combiner。
注:combine的输入和reduce的完全一致,输出和map的完全一致
常用的Combiner示例
Reducer
如何将Counters写入_SUCCESS文件?
将counters写入_SUCCESS文件,设置参数mapred.success.file.status为true
Streaming方式,提交Job时指定:-D/-jobconf mapred.success.file.status=true
Java方式,在程序中设置:conf.setBoolean();Java方式同样可以向Streaming方式在提交job时指定参数,请参见5.1.3节job提交时指定参数不生效
如何设置MapReduce的语言环境?
为了避免mapreduce集群中语言环境不一致可能造成的结果异常,建议在提交任务是指定语言环境。以设置en_US.UTF-8为例:
JAVA程序:
conf.set("mapred.child.env","LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8");
Streaming程序:
-jobconf mapred.child.env="LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8"
MR TIPS
如何自定义计数器和进行状态汇报?
Java方式:
用户可自定义counter的GroupName和CounterName,并通过Context获取counter进行操作,例如:
public class Reducer<Text, IntWritable, Text, IntWritable> { public String groupName="My_Counters"; public String counterName="KEY_COUNT"; void reduce(Textkey, Iterable<IntWritable> value, Context context) throws IOException, InterruptedException { int sum = 0; for(IntWritable value: values) { sum += val.get(); } context.write(key, new IntWritable(sum)); context.getCounter("My_Counters","KEY_COUNT").increment(1); // 示例自定义counter统计KEY的个数 }}
Streaming方式:
Streaming程序通过向标准错误流写入特殊格式的字符串进行计数和状态汇报。
计数的格式如下:
- reporter:counter:<group>,<counter>,<amount>
状态汇报的格式如下:
- reporter:status:<message>
如下是一个shell程序进行计数和状态汇报的例子:
#!/bin/bashwhile read linedo echo "$line" echo "reporter:counter:MapCounter,ReadCounter,1" >&2 echo "reporter:status:read one line" >&2done
Streaming程序当map或reduce返回值非0时,整个任务会失败?
对于Streaming程序,MR框架会判断map和reduce的返回值,如果返回值非0,则认为map或reduce执行中出现错误,重试数次(默认为4)后仍然失败,会导致整个任务失败。
如果返回值非0属于正常情况,则有两种方法规避任务失败:
- 可以通过设置参数:stream.non.zero.exit.is.failure=false,即使map/reduce失败,整个任务也不会失败。
- 修改或封装参数,使可预期正常结束的情况的返回值设置为0。
请注意,streaming程序中若直接使用命令,可能会因为命令失败,导致返回值非0,从而导致Job失败。 请尽量封装命令到脚本中执行,并在脚本中添加exit 0,保证返回值为0 [EXAMPLE] 常见失败命令: grep ***
Streaming任务如何指定key,value的分隔符?
Streaming任务默认以‘\t’对输入和输出的key/value进行分割。即:第一个’\t’之前的字符串作为key,余下的字符串作为value。若整行都没有’\t’分隔符,则整行作为key,value为空。
可以通过设置参数stream.map.output.field.separator和参数stream.num.map.output.key.fields自定义分隔符和key。 例如设置:
-D stream.map.output.field.separator="_" \-D stream.num.map.output.key.fields=3 \
则对于”aa_bb_cc_dd”,key为”aa_bb_cc”,value为”dd”
Streaming程序如何判断管道的每个进程返回值?
rets="${PIPESTATUS[*]}"
ret=$?if [ $ret -ne 0 ]then exit $retfi
for ret in $retsdo if [ "$ret" != "0" ] thenecho "PIPESTATUS $rets" 1>&2 exit $ret fidone
Job调度参数功能
如何设置JobName?
我们期望用户可以主动指定有意义JobName,这样不仅方便我们沟通联系,而且谨防job失败后推送邮件轰炸其他同事。 请指定如下参数:
mapred.job.name="xxx_test" 其中xxx为您的邮箱域账户
如何设置Reduce的个数?
如果用户不主动设置Reduce个数,系统默认启动一个Reduce。如果用户处理数据量过大(>10G),则会影响Reduce的处理速度。 请您根据map的输出量合理设置reduce的个数,请确保每个reduce处理的数据不超过10G 通过如下参数指定reduce个数,单位(个):
mapred.reduce.tasks
如何限制Job的task并发数?
通过如下参数控制,单位(个):
mapred.job.max.map.runningmapred.job.max.reduce.running
如果Job已经提交,可以在运行时通过hadoop命令,修改map/reduce task的并发数:
hadoop job -set-max-task-running <job-id> <task-type> <max-task-running>
例如: hadoop job -set-max-task-running job_201404080925_130068 map 500
如何设置Job的失败比例?
默认情况下,一个task重试4次失败,就会导致这个job失败。 如果用户对job的结果不要求严格无误,且不想个别task的失败而导致整个job的失败,可设置map/reduce的容错率:
mapred.max.map.failures.percentmapred.max.reduce.failures.percent参数单位为整型,例如设置mapred.max.map.failures.percent=3 表示此job允许3%的task失败
如何设置Job的优先级?
Job的优先级设置值有:LOW、VERY_LOW、NORMAL、HIGH、VERY_HIGH 五种类型,默认情况下,一般普通的job优先级默认是NORMAL。系统会根据map的数据个数自动设置优先级,map个数较小的job,优先级会较高。 通过如下参数,用户可以根据自己业务的紧急性,设置Job的优先级:
mapred.job.priority
如何设置task的超时时间?
默认情况下,如果一个task 10min没有进行MapReduce的读/写,就会失败。 为满足特殊业务需求,例如处理逻辑本身时间>10min的,可以通过如下参数调整task的超时时间,请您根据处理输出量合理预估时间:
mapred.task.timeout(单位:毫秒)
注意:非特殊长逻辑处理,我们不建议您修改此参数的默认值。否则会带来未知的Job问题,比如job hang住不结束。
如何打开/关闭预测执行?
预测执行是MapReduce框架中一个重要的优化,其主要是为了解决慢节点导致的task处理慢问题。 它的原理是:如果一个task运行比较慢,系统会在另一个计算节点启动一个与此task完全相同的task,哪个task先完成,则kill掉另一个,并将完成的task的结果移至最终的输出目录中。这也就是为什么您会在正常的job执行页面看到一些kill的task。 默认情况下,系统的预测执行功能是打开状态,对于写数据库或者hbase这样的存储,需要关闭预测执行,不能同时写。 控制预测执行的参数:
mapred.map.tasks.speculative.execution // 默认打开为true,关闭请显式设置为falsemapred.reduce.tasks.speculative.execution // 默认打开为true,关闭请显式设置为false
如何设置slow start?
Slow start也是MapReduce框架的一个优化,它代表reduce在何时开始调度启动。当前我们的集群配置为85%,即map task完成85%的时候,开始启动reduce,让其进行shuffle操作,从已完成的map拖数据。这样的好处是尽量使reduce和map可以并行起来。
但是如果map有个别长尾(完成率>85%),则reduce会全部起来,并且一直处于shuffle阶段,等待map的完成,这样会造成reduce的槽位被无辜占用,其他的job会因为得不到reduce槽位资源而被堵住。
所以您可以通过如下参数,设置适合job运行的slow start比例(不得低于85%)
mapred.reduce.slowstart.completed.maps
比如设置mapred.reduce.slowstart.completed.maps=1,则表明关闭slow start,即等所有map运行完成后在启动reduce; mapred.reduce.slowstart.completed.maps=0.9,则表明map运行完成90%的时候再启动reduce。
MapReducejob常见问题及解决方法
如何找到失败task的对应输入?
在task的all task页面,从task log输出中可以查到。通过定位syslog logs,查询关键字“org.apache.hadoop.mapred.MapTask: split:”,即为task的输入文件。
当task失败时,可以定位到导致失败的单个task,将其split作为输入,在集群进行测试,以检查是否数据问题。
TIP:task的输入partId与taskId并不匹配
如何判断job的运行结果?
有两种方法:1、通过脚本提交的job,通过$?返回值判断是否非0,为0则为成功,非0为失败。2、通过check输出目录,是否存在_SUCCESS标记,以判断是否成功运行完成。
TIP:第一种方法可能存在提交进程被杀死的情况,这时的返回值可能也非0,但job可能已被正常提交。
如何处理syslog输出超限的错误?
使用自定义的log4j文件,将输出较多的class设置为ERROR LEVEL。
1、log4j设置示例:常见客户端log4j文件位置:/usr/bin/hadoop/software/hadoop/conf/log4j.properties调整两个package log输出为ERROR级别:log4j.logger.org.apache.hadoop.hbase=ERRORlog4j.logger.org.apache.hadoop.hdfs=ERROR注:在所需设置类前加log4j.logger.
2、作业提交示例:~/software/hadoop/bin/hadoop streaming-Dmapreduce.user.classpath.first=true-input /home/work/bashrc -output /home/work/zhongliang-xy/log4j-test2 -mapper "cat" -reducer NONE-file log4j.properties
注:java方式请用-libjars 上传log4j.properties
其他
请移步 MapReduce失败Job常见问题及解决方法
如何自主部署hadoop不支持的运行环境
php的mcrypt扩展示例
通过命令行将依赖的扩展文件上传至hdfs:
-file ./mcrypt.so-file ./libmcrypt.so.4
设置依赖的lib环境:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.;
设置php.ini中变量值:
php -d extension_dir=. -d enable_dl=on
mapper中load扩展包:
dl('mcrypt.so')
附录
MapReduce常见参数表
MultiOutputFormat实例
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.conf.Configured;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.NullWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapred.FileInputFormat;import org.apache.hadoop.mapred.FileOutputFormat;import org.apache.hadoop.mapred.JobClient;import org.apache.hadoop.mapred.JobConf;import org.apache.hadoop.mapred.MapReduceBase;import org.apache.hadoop.mapred.Mapper;import org.apache.hadoop.mapred.OutputCollector;import org.apache.hadoop.mapred.Reporter;import org.apache.hadoop.mapred.TextInputFormat;import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat;import org.apache.hadoop.util.Tool;import org.apache.hadoop.util.ToolRunner;
public class MultiFile extends Configured implements Tool { public static class MapClass extends MapReduceBase implements Mapper<LongWritable, Text, NullWritable, Text> { @Override public void map(LongWritable key, Text value, OutputCollector<NullWritable, Text> output, Reporter reporter) throws IOException { output.collect(NullWritable.get(), value); } }
// MultipleTextOutputFormat 继承自MultipleOutputFormat,实现输出文件的分类 public static class PartitionByCountryMTOF extends MultipleTextOutputFormat<NullWritable, Text> { // key is // NullWritable, // value is Text protected String generateFileNameForKeyValue(NullWritable key, Text value, String filename) { String[] arr = value.toString().split(",", -1); String country = arr[4].substring(1, 3); // 获取country的名称 return country + "/" + filename; } }
// 此处不使用reducer /* * public static class Reducer extends MapReduceBase implements * org.apache.hadoop.mapred.Reducer<LongWritable, Text, NullWritable, Text> * { * @Override public void reduce(LongWritable key, Iterator<Text> values, * OutputCollector<NullWritable, Text> output, Reporter reporter) throws * IOException { // TODO Auto-generated method stub * } * * } */
@Override public int run(String[] args) throws Exception { Configuration conf = getConf(); JobConf job = new JobConf(conf, MultiFile.class);
Path in = new Path(args[0]); Path out = new Path(args[1]);
FileInputFormat.setInputPaths(job, in); FileOutputFormat.setOutputPath(job, out); job.setJobName("wuyunyun_MultiFile"); job.setMapperClass(MapClass.class); job.setInputFormat(TextInputFormat.class); job.setOutputFormat(PartitionByCountryMTOF.class); job.setOutputKeyClass(NullWritable.class); job.setOutputValueClass(Text.class); job.setNumReduceTasks(0); JobClient.runJob(job); return 0; }
public static void main(String[] args) throws Exception { int ret = ToolRunner.run(new Configuration(), new MultiFile(), args); System.exit(ret); }}
测试数据:
$~/software/hadoop/bin/hadoop fs -cat /home/mr/wuyunyun/test-input/multiTest5765303,1998,14046,1996,"AD","",,1,12,42,5,59,11,1,0.4545,0,0,1,67.3636,,,,5785566,1998,14088,1996,"AD","",,1,9,441,6,69,3,0,1,,0.6667,,4.3333,,,,5894770,1999,14354,1997,"AD","",,1,,82,5,51,4,0,1,,0.625,,7.5,,,,5765303,1998,14046,1996,"CN","",,1,12,42,5,59,11,1,0.4545,0,0,1,67.3636,,,,5785566,1998,14088,1996,"CN","",,1,9,441,6,69,3,0,1,,0.6667,,4.3333,,,,5894770,1999,14354,1997,"CN","",,1,,82,5,51,4,0,1,,0.625,,7.5,,,,
测试结果:
$ ~/software/hadoop/bin/hadoop fs -ls /home/mr/wuyunyun/out1/*/*-rw-r--r-- 2 mr mr 214 2014-04-14 15:56 /home/mr/wuyunyun/out1/AD/part-00000-rw-r--r-- 2 mr mr 214 2014-04-14 15:56 /home/mr/wuyunyun/out1/CN/part-00000-rw-r--r-- 2 mr mr 0 2014-04-14 15:56 /home/mr/wuyunyun/out1/_SUCCESS
MultiOutputs实例
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.conf.Configured;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.Text;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.mapreduce.*;import org.apache.hadoop.mapreduce.lib.output.*;import org.apache.hadoop.util.Tool;import org.apache.hadoop.util.ToolRunner;import org.apache.hadoop.mapreduce.lib.input.*;
public class TestwithMultipleOutputs extends Configured implements Tool { public static class MapClass extends Mapper<LongWritable,Text,Text,IntWritable> { private MultipleOutputs<Text,IntWritable> mos; protected void setup(Context context) throws IOException,InterruptedException { mos = new MultipleOutputs<Text,IntWritable>(context); }
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException{ String line = value.toString(); String[] tokens = line.split("-"); mos.write("MOSInt",new Text(tokens[0]), new IntWritable(Integer.parseInt(tokens[1]))); // 直接输出至:/home/mr/wuyunyun/output/MOSInt-m-00000 mos.write("MOSText", new Text(tokens[0]),tokens[2]); // 直接输出至:/home/mr/wuyunyun/output/MOSText-m-00000 mos.write("MOSText", new Text(tokens[0]),line,tokens[0]+"/part"); // 输出至指定的目录下:/home/mr/wuyunyun/output/xxx/part-m-00000,其中xxx为tokens[0]即key值 }
protected void cleanup(Context context) throws IOException,InterruptedException { mos.close(); } }
public int run(String[] args) throws Exception { Configuration conf = getConf(); Job job = new Job(conf); job.setJarByClass(TestwithMultipleOutputs.class); Path in = new Path(args[0]); Path out = new Path(args[1]);
FileInputFormat.setInputPaths(job, in); FileOutputFormat.setOutputPath(job, out); job.setMapperClass(MapClass.class); job.setNumReduceTasks(0); job.setJobName("wuyunyun_TestwithMultipleOutputs");
MultipleOutputs.addNamedOutput(job,"MOSInt",TextOutputFormat.class,Text.class,IntWritable.class); MultipleOutputs.addNamedOutput(job,"MOSText",TextOutputFormat.class,Text.class,Text.class);
System.exit(job.waitForCompletion(true)?0:1); return 0; }
public static void main(String[] args) throws Exception { int ret = ToolRunner.run(new Configuration(), new TestwithMultipleOutputs(), args); System.exit(ret); }}
测试数据:
abc-1232-hdfabc-123-rtdioj-234-grjthntg-653-sdgfvdkju-876-btyunbhm-530-bhythfter-45642-bhgfbgrfg-8956-fmghjnhdf-8734-adfbgfntg-68763-nfhsdfntg-98634-dehuyhfter-84567-drhuk
测试结果:
$ ~/software/hadoop/bin/hadoop fs -ls /home/mr/wuyunyun/output/*/*-rw-r--r-- 2 mr mr 115 2014-04-14 21:20 /home/mr/wuyunyun/output/MOSInt-m-00000-rw-r--r-- 2 mr mr 124 2014-04-14 21:20 /home/mr/wuyunyun/output/MOSText-m-00000-rw-r--r-- 2 mr mr 0 2014-04-14 21:20 /home/mr/wuyunyun/output/_SUCCESS-rw-r--r-- 2 mr mr 33 2014-04-14 21:20 /home/mr/wuyunyun/output/abc/part-m-00000-rw-r--r-- 2 mr mr 22 2014-04-14 21:20 /home/mr/wuyunyun/output/bgrfg/part-m-00000-rw-r--r-- 2 mr mr 17 2014-04-14 21:20 /home/mr/wuyunyun/output/bhm/part-m-00000-rw-r--r-- 2 mr mr 47 2014-04-14 21:20 /home/mr/wuyunyun/output/hfter/part-m-00000-rw-r--r-- 2 mr mr 18 2014-04-14 21:20 /home/mr/wuyunyun/output/ioj/part-m-00000-rw-r--r-- 2 mr mr 24 2014-04-14 21:20 /home/mr/wuyunyun/output/jnhdf/part-m-00000-rw-r--r-- 2 mr mr 18 2014-04-14 21:20 /home/mr/wuyunyun/output/kju/part-m-00000-rw-r--r-- 2 mr mr 60 2014-04-14 21:20 /home/mr/wuyunyun/output/ntg/part-m-00000-rw-r--r-- 2 mr mr 0 2014-04-14 21:20 /home/mr/wuyunyun/output/part-m-00000
hhvm实例
高性能php版本hhvm例子:
hadoop streaming \ -input /home/xitong/tmp/test/data \ -output /home/xitong/tmp/test/output/out3 \ -mapper "./hhvm/bin/hhvm test.php" \ -reducer "cat" \ -file test.php \ -cacheArchive /home/mr/share/hhvm-2.3.2.tar.gz#hhvm
为了发挥hhvm的性能优势,将代码封装成一个函数效果更好,例如test.php中把核心逻辑封装成fun()函数,然后调用fun()函数 cat test_hhvm.php <?php function fun() { $n = 0; while(!feof(STDIN)){ $line = fgets(STDIN); $n++; } echo "$n\n"; } fun(); ?>
pypy实例
高性能python版本pypy例子(由hadoop粉丝 范晓恺 <fanxiaokai@360.cn> 提供):
[PyPy][1]作为CPython的高性能替代方案,目前已经十分成熟,得益于JIT等技术的应用,多数场景下PyPy都具有更好的性能。
以通过正则对Nginx日志进行解析为例,对300G数据进行处理,PyPy对比原生Python能得到2.5倍以上的性能提升:
在MapReduce任务中,使用官方支持的[Portable PyPy][2]能够比较完美地解决对运算性能以及运行环境的灵活配置的要求,该版本在CentOS 5上编译完成,对运行环境系统/链接库的要求很低,一般现在的RHEL/Centos/Ubuntu/Debian系统都能直接运行:
% ldd pypy linux-vdso.so.1 => (0x00007fff959ff000) libdl.so.2 => /lib64/libdl.so.2 (0x000000392e200000) libm.so.6 => /lib64/libm.so.6 (0x000000302fe00000) libz.so.1 => /lib64/libz.so.1 (0x00000039fe600000) librt.so.1 => /lib64/librt.so.1 (0x000000381a600000) libbz2.so.1 => /lib64/libbz2.so.1 (0x0000003031e00000) libcrypt.so.1 => /lib64/libcrypt.so.1 (0x000000392e600000) libutil.so.1 => /lib64/libutil.so.1 (0x0000003032a00000) libncurses.so.5 => /lib64/libncurses.so.5 (0x0000003270600000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003031200000) libpthread.so.0 => /lib64/libpthread.so.0 (0x000000302fa00000) libc.so.6 => /lib64/libc.so.6 (0x000000302f600000) /lib64/ld-linux-x86-64.so.2 (0x000000302ee00000) libfreebl3.so => /lib64/libfreebl3.so (0x000000392ee00000) libtinfo.so.5 => /lib64/libtinfo.so.5 (0x0000003030e00000)
pypy使用示例:
hadoop streaming \ -D mapred.job.name=pypy-test -cacheArchive /home/mr/share/pypy.tar.gz#pypy -input /path/to/input -output /path/to/output -file map.py -mapper "./pypy/bin/pypy map.py"
- MapReduce User Manual
- User Manual
- CUP User's Manual
- Apache Ant User Manual
- SLF4J user manual
- ffmpeg user manual
- VideoEdit+ User Manual
- Percona XtraBackup User Manual
- Android Studio User Manual
- mxGraph User Manual - JavaScript
- Vim user manual notes
- TELPAD MT7 User Manual
- Mushroom User's Manual
- Valgrind User Manual
- notes virtualbox User Manual
- OsiriX User Manual
- ST7 Peripheral User`s Manual
- The tcprstat User's Manual
- LNMP +discuz论坛 详细架设过程
- 安装ubantu后配置
- Bootstrap按钮和折叠插件
- 微信小程序 | 来自小程序开发者的开发教程③
- 637. Average of Levels in Binary Tree
- MapReduce User Manual
- jfreechart柱形图详细属性设置
- 快速使用CSS 弹性盒子
- spark--transform算子--flatMap
- log4j将日志存储到数据库
- SSD掉电保护也是一门艺术
- LeetCode总结
- fork函数
- 使用javassist生成实体对象