从这一节开始,我会讲MR任务编写的方方面面,但是仅限在任务编写这块,不会过度涉及HDFS和Yarn调度的深入分析,计算时提也只是为了更好的理解MR任务。对于Mapreduce计算模型会深入介绍,只有了解Mapreduce计算模型计算模型才能真正掌握整个MR任务的编写,对于相关任务的调优和过程划分才会有清洗的认识。这一部分是适合所有人的,先不去管整个hadoop实现的复杂性,适应MR的思维用好hadoop这个工具才是最重要的,对于数据分析员,有必要深入理解MR过程才能优化自己的程序,对于数据开发员,先了解怎么用才能更好的阅读和调整源代码。见过一些数据分析人员,纯写业务不考虑MR的优化,毫无疑问这是可悲的,很多时候一个简单的优化需要数倍成本的硬件才能达到同样的效果;也见过好多hadoop都没用熟的大数据开发,指望他们能改好代码反正我是不信的。
写MR其实很简单,主要考虑的是如何划分map和reduce操作,MapReduce模型固然简单,但是也因为其抽象能力有限,对于复杂任务只能通过多个MR过程组合, 还好有Hive在上层已经帮我们处理了这一问题,hive其实就是把SQL翻译成多个MR过程来执行。最新的如Spark的计算引擎是基于DAG的,因此支持更多类型的操作,组合起来更加多样,这个后续来讲。
本节从最简单的MR任务组成和编写开始,整个MR的过程如下:
map通过继承实现Mapper接口,完成map功能,如下。map的模板参数前两个指定输入的Key、Value类型,后两个指定输出的Key、Value类型。
这里输入Key=LongWritable为输入文件每行偏移,Text为对应文本内容,注意这里必须用hadoop提供的这种实现了Writable接口的类型,这是为了数据序列化
的考虑,后面会详述。
输出为以每个单词作为Key,次数1作为值的键值对,这样在Reduce中就可以按照Key聚合起来计算每个单词出现的次数。这里提前定义了one和word,主要是为了性能优化
的考虑,现在只要记住尽量重用这些结构即可。
public class WordMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 文本按空格分割成<单词,1>组
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
map通过继承实现Reducer接口,完成reduce功能,如下。reduce的模板参数前两个指定输入的Key、Value类型,后两个指定输出的Key、Value类型。
public class WordReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// <单词,1>组按照单词聚合起来,次数累加
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
main函数中提交MR任务如下,具体每个步骤代码已经注释很清楚了。
public static void main( String[] args ) throws Exception {
if (args.length!=2){
System.out.println("输入参数个数不正确!");
System.exit(-1);
}
//1.读入配置,可以在此动态修改
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "jimwen_wordcount");
//2.分别设置Jar包路径和对应的map/reduce类
job.setJarByClass(App.class);
job.setMapperClass(WordMapper.class);
job.setReducerClass(WordReducer.class);
//3.设置map/reduce的输入键Key和值Value的类型
// map和reduce输出相同时设置setOutput***即可
// map和reduce不同时,需要用setMapOutput***设置map输出类型
//job.setMapOutputKeyClass(Text.class);
//job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//4.设置输入和输出类型和路径
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//5.等待任务执行完成
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
如上程序,编译打包后执行如下指令
hadoop jar WordCount-1.0-SNAPSHOT.jar org.jimwen.App /data /output
可以看到console中输出
8/12/23 04:09:00 INFO mapreduce.JobSubmitter: number of splits:1
18/12/23 04:09:00 INFO Configuration.deprecation: yarn.resourcemanager.system-metrics-publisher.enabled is deprecated. Instead, use yarn.system-metrics-publisher.enabled
18/12/23 04:09:00 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1545538254601_0002
18/12/23 04:09:01 INFO impl.YarnClientImpl: Submitted application application_1545538254601_0002
18/12/23 04:09:02 INFO mapreduce.Job: The url to track the job:http://192.168.139.140:8088/proxy/application_1545538254601_0002/
18/12/23 04:09:02 INFO mapreduce.Job: Running job: job_1545538254601_0002
18/12/23 04:09:22 INFO mapreduce.Job: Job job_1545538254601_0002 running in uber mode : false
18/12/23 04:09:22 INFO mapreduce.Job: map 0% reduce 0%
18/12/23 04:10:14 INFO mapreduce.Job: map 100% reduce 0%
18/12/23 04:10:46 INFO mapreduce.Job: map 100% reduce 100%
18/12/23 04:10:51 INFO mapreduce.Job: Job job_1545538254601_0002 completed successfully
18/12/23 04:10:54 INFO mapreduce.Job: Counters: 49
...
可以看到整个的执行过程,
number of splits:1
JobID=job_1545538254601_0002
application_1545538254601_0002
,可以url跟踪和查看执行进度和状态查看hdfs输出目录,结果如下:
wenzhou@ubuntu:~$ hadoop fs -ls /output
Found 2 items
-rw-r--r-- 1 wenzhou supergroup 0 2018-12-27 23:01 /output/_SUCCESS
-rw-r--r-- 1 wenzhou supergroup 28 2018-12-27 23:01 /output/part-r-00000
wenzhou@ubuntu:~$ hadoop fs -cat /output/part-r-00000
hello 5
jim 4
wen 3
world 2
运行成功,输出目录会写入_SUCCESS标记,实际应用中后续任务可通过检测此标记判断任务是否运行完成。实际输出数据在part-r-00000中,这里part的序号数和reduce数保持一致。
如上,程序运行时会输出track url,即
http://192.168.139.140:8088/proxy/application_1545538254601_0002/
然而,实际访问我们会发现,跳转后的运行历史地址不存在
http://192.168.139.140:19888/jobhistory/job/job_1545980170732_0001
实际需要开启日志服务
才行,进入脚本sbin目录,执行如下命令:
mr-jobhistory-daemon.sh start historyserver
此时显示任务运行历史,如下:
可以看到,任务运行的各个阶段耗时、map/reduce任务执行状况、各节点日志等。任务运行历史对任务调优非常重要,后面会详细讲解。
实际任务运行日志保存在本地,也可以通过开启聚合功能将日志保存在HDFS中,减少NodeManager的压力。常用的日志相关配置
如下:
配置 mapred-site.xml
1.mapreduce.jobhistory.address
参数说明:JobHistory服务器IPC 主机:端口
默认值:0.0.0.0:10020
2.mapreduce.jobhistory.webapp.address
参数说明:History服务器Web UI地址,用户可根据该地址查看Hadoop历史作业情况
默认值:0.0.0.0:19888
配置 yarn-site.xml
1.yarn.nodemanager.log-dirs
参数说明:容器还在执行中时, 容器日志在节点上的存储位置(可配置多个目录)
默认值:${yarn.log.dir}/userlogs
2.yarn.log-aggregation-enable
参数说明:是否启用日志聚合功能,日志聚合开启后保存到HDFS上
默认值:false
1.yarn.log-aggregation.retain-seconds
参数说明:聚合后的日志在HDFS上保存多长时间,单位为s。
默认值:-1(不启用日志聚合),例如设置为86400,24小时
2.yarn.log-aggregation.retain-check-interval-seconds
参数说明:删除任务在HDFS上执行的间隔,执行时候将满足条件的日志删除(超过参数2设置的时间的日志),如果是0或者负数,则为参数2设置值的1/10,上例值在此处为8640s。
默认值:-1
3.yarn.nodemanager.remote-app-log-dir
参数说明:当应用程序运行结束后,日志被转移到的HDFS目录(启用日志聚集功能时有效),修改为保存的日志文件夹。
默认值:/tmp/logs
4.yarn.nodemanager.remote-app-log-dir-suffix
参数说明:远程日志目录子目录名称(启用日志聚集功能时有效)。
默认值:logs 日志将被转移到目录${yarn.nodemanager.remote-app-log-dir}/${user}/${thisParam}下
5.yarn.log.server.url
参数说明:一旦一个应用结束, NMs 会将网页访问自动跳转到聚合日志的地址, 执行过程中它指向的是 MapReduce 的 JobHistory
1.yarn.nodemanager.log.retain-seconds
参数说明:当不启用日志聚合此参数生效,日志文件保存在本地的时间,单位为s
默认值:10800
源码下载地址
原创,转载请注明来自
日志配置参考