一:简介
在运行核心业务 Mapreduce 程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。
清理的过程往往只需要运行 mapper 程序,不需要运行 reduce 程序。
二:日志清洗案例之简单解析版
- 需求:去除日志中字段长度小于等于11的日志(每一行按照空格切割,切割后数组长度小于11的日志不要)
- 数据如下:
- 代码实现如下:
⑴创建mapper类:
package com.kgf.mapreduce.weblog; import java.io.IOException; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class LogMapper extends Mapper<LongWritable, Text, Text, NullWritable> { Text k = new Text(); @Override protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException { //1:获取一行 String line = value.toString(); //2:解析一行数据 boolean result = parseLog(line); if(!result) { return; } k.set(line); context.write(k, NullWritable.get()); } private boolean parseLog(String line) { String[] fields = line.split(" "); if(fields.length>11) { return true; } return false; } }
⑵创建Driver:
package com.kgf.mapreduce.weblog; import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class LogDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //1:获取Job对象 Configuration conf = new Configuration(); Job job = Job.getInstance(conf); //2:设置jar对象 job.setJarByClass(LogDriver.class); //3:设置关联Mapper job.setMapperClass(LogMapper.class); //4:设置mapper输出类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(NullWritable.class); //5:设置reduce task为0 job.setNumReduceTasks(0); //6:设置最终输出参数 job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); //7:设置文件输入输出路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //8:提交 boolean result = job.waitForCompletion(true); System.exit(result?0:1); } }
- 效果:
⑴日志清理前数据有如下数量:
⑵清理后:
⑶程序运行日志如下:
- 计数器应用
⑴简介:
Hadoop 为每个作业维护若干内置计数器,以描述多项指标。例如,某些计数器记录已
处理的字节数和记录数,使用户可监控已处理的输入数据量和已产生的输出数据量。
⑵计数器方式如下:
⑶案例,我们可以在上面的日志清洗案例中加上计数器组应用:代码如下:
效果如下(可以发现控制台出现了下面的日志内容,可以帮助我们定位问题):
三:倒排索引案例
- 现在有三个文件a.txt,b.txt,c.txt,现在我们需要将文件里面的单词汇总
⑴文件如下:
⑵最后要求的结果(汇总每个单词所在的文件中个数):
- 代码实现如下:
⑴建立bean对象
⑵建立mapper对象package com.kgf.mapreduce.index; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.Writable; public class IndexVo implements Writable{ private String word; private String file; private int num; public IndexVo(String word, String file, int num) { super(); this.word = word; this.file = file; this.num = num; } public IndexVo() { super(); } @Override public void readFields(DataInput in) throws IOException { this.word = in.readUTF(); this.file = in.readUTF(); this.num = in.readInt(); } @Override public void write(DataOutput out) throws IOException { out.writeUTF(word); out.writeUTF(file); out.writeInt(num); } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } @Override public String toString() { return word + "\t" + file + "\t" + num; } }
⑶建立reducer对象package com.kgf.mapreduce.index; import java.io.IOException; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.lib.input.FileSplit; public class IndexMapper extends Mapper<LongWritable, Text, Text, IndexVo>{ private String fileName = null; Text k = new Text(); IndexVo v = new IndexVo(); @Override protected void setup(Context context) throws IOException, InterruptedException { //1:获取文件切片信息 FileSplit splitFile = (FileSplit) context.getInputSplit(); fileName = splitFile.getPath().getName(); } @Override protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException { //1:获取一行数据 String line = value.toString(); //2:切割数据 String[] fields = line.split("\t"); for (String field : fields) { k.set(field); v.setWord(field); v.setFile(fileName); v.setNum(1); context.write(k, v); } } }
⑷建立Driver对象package com.kgf.mapreduce.index; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class IndexReducer extends Reducer<Text, IndexVo, Text, NullWritable> { Text k = new Text(); @Override protected void reduce(Text key, Iterable<IndexVo> values,Context context) throws IOException, InterruptedException { Map<String,Integer> map = new HashMap<String,Integer>(); for (IndexVo indexVo : values) { String name = indexVo.getFile(); if(map.containsKey(name)) { map.put(name, map.get(name)+indexVo.getNum()); }else { map.put(name, indexVo.getNum()); } } String result = key.toString()+"\t"; for (String fileName : map.keySet()) { result+=(fileName+"\t"+map.get(fileName))+"\t"; } k.set(result); context.write(k, NullWritable.get()); } }
package com.kgf.mapreduce.index; import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class IndexDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //1:获取job对象 Configuration conf = new Configuration(); Job job = Job.getInstance(conf); //2:设置jar job.setJarByClass(IndexDriver.class); //3:关联mapper和reducer job.setMapperClass(IndexMapper.class); job.setReducerClass(IndexReducer.class); //4:设置mapper输出参数 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IndexVo.class); //5:设置最终输出参数 job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); //6:设置数据输入输出路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //7:提交 boolean rsult = job.waitForCompletion(true); System.exit(rsult?0:1); } }
四: ReduceTask 工作机制
- ReduceTask有如下特点:
- 流程图如下:
⑴Copy阶段:
ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,
则写到磁盘上,否则直接放到内存中。
⑵Merge 阶段:
在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,
以防止内存使用过多或磁盘上文件过多。
⑶Sort 阶段:
按照 MapReduce 语义,用户编写 reduce()函数输入数据是按 key 进行聚集的一组数据。
为了将 key 相同的数据聚在一起,Hadoop 采用了基于排序的策略。由于各个 MapTask 已经
实现对自己的处理结果进行了局部排序,因此,ReduceTask 只需对所有数据进行一次归并排序即可。
⑷Reduce 阶段:
reduce()函数将计算结果写到 HDFS 上。
五: Hadoop 数据压缩
- 简介
压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。
在 Hadoop 下,尤其是数据规模很大和工作负载密集的情况下,使用数据压缩显得非常重要。在这种情况下,
I/O 操作和网络数据传输要花大量的时间。还有,Shuffle与 Merge 过程同样也面临着巨大的 I/O 压力。
鉴于磁盘 I/O 和网络带宽是 Hadoop 的宝贵资源,数据压缩对于节省资源、最小化磁盘
I/O 和网络传输非常有帮助。不过,尽管压缩与解压操作的 CPU 开销不高,其性能的提升和
资源的节省并非没有代价。
如果磁盘 I/O 和网络带宽影响了 MapReduce 作业性能,在任意 MapReduce 阶段启用压
缩都可以改善端到端处理时间并减少 I/O 和网络流量。
压缩 Mapreduce 的一种优化策略:通过压缩编码对 Mapper 或者 Reducer 的输出进行
压缩,以减少磁盘 IO,提高 MR 程序运行速度(但相应增加了 cpu 运算负担)。
- MR支持的压缩编码
- 压缩性能的比较
- 压缩方式选择
⑴Gzip 压缩
⑵ Bzip2 压缩
⑶Lzo 压缩
⑷Snappy 压缩
- 压缩位置选择
压缩可以在 MapReduce 作用的任意阶段启用。
- 压缩配置参数