MapReduce:是一种分布式计算编程模型,由Google提出,主要用于搜索领域,解决海量数据的计算问题。
MR由两个阶段组成:MapReduce,用户只需要实现map()和reduce()两个函数几科实现分布式计算。
这两个函数的形参是key,value对,表示函数的输入信息。
主从结构:
主节点,只有一个:JobTracker
从节点,有很多个:TaskTracker
JobTracker负责:
接收客户提交的计算任务
把计算任务分给TaskTracker执行
监控TaskTracker
TaskTracker负责:
执行JobTracker分配的计算任务
执行步骤:
MapReduce键值对格式
JobTracker负责接收用户提交的作业,负责启动弄、跟踪任务执行。
JobSubmissionProtocol是JobClient与JobTracker通信的接口。
InterTrackerProtocol是TaskTracker与JobTracker通信的接口。
TaskTracker负责执行任务。
是用户作业与JobTracker交互的主要接口。
负责提交作业的,负责启动、跟踪任务执行、访问任务状态和日志等。
Configuration configuration = new Configuration();
Job job = new Job(configuration, "HelloWorld");
job.setInputFormat(TextInputFormat.class);
job.setMapperClass(IdentityMapper.class);
job.setMapOutputKeyClass(LongWritable.class);
job.setMapOutputValueClass(Text.class);
job.setPartitionerClass(HashPartitioner.class);
job.setNumReduceTasks(1);
job.setReducerClass(IdentityReducer.class);
job.setOutputKeyClass(LongWritable.class);
job.setOutputValueClass(Text.class);
job.setOutputFormat(TextOutputFormat.class);
job.waitForCompletion(true);
2.9.1 序列化概念
序列化(Serizlization)是指把结构化对象转化为字节流。
反序列化(Deserialization)是序列化的逆过程。即把字节流转回结构化对象。
Java序列化(java.io.Serializable)
2.9.2 Hadoop序列化的特点
序列化格式特点:
Hadoop的序列化格式:Writable
2.9.3 Hadoop序列化的作用
Hadoop序列化的作用:序列化在分布式环境的两大作用:进程间通信,永久存储。
Hadoop节点间通信。
Writable接口,是根据DataInput和DataOutput实现的简单、有效的序列化对象。
MR的任意Key和Value必须实现Writable接口。
MR的任意key必须实现WritableComparable接口
2.10.1 常用的Writable实现类
Text一般认为它等价于java.lang.String的Writable。针对UTF-8序列。
例如:
Text t = new Text(“t”);
IntWritable one = new IntWritable(1);
Writable
实现WritableComparable
Java值对象的比较:一般需要重写toString(),hashCode(),equals()方法
2.10.2 实现Writable示例
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
/**
* 示例:
*
* @author caonanqing
*
*/
public class DataBean implements Writable {
private String telNo;
private long upPayLoad;
private long downPayLoad;
private long totalPayLoad;
public DataBean() {
}
public DataBean(String telNo, long upPayLoad, long downPayLoad) {
super();
this.telNo = telNo;
this.upPayLoad = upPayLoad;
this.downPayLoad = downPayLoad;
this.totalPayLoad = upPayLoad + downPayLoad;
}
@Override
public String toString() {
return this.upPayLoad + "\t" + this.downPayLoad + "\t" + this.totalPayLoad;
}
/**
* 序列化 注意:1.类型 2.顺序
*/
public void write(DataOutput out) throws IOException {
out.writeUTF(telNo);
out.writeLong(upPayLoad);
out.writeLong(downPayLoad);
out.writeLong(totalPayLoad);
}
/**
* 反序列化
*/
public void readFields(DataInput in) throws IOException {
this.telNo = in.readUTF();
this.upPayLoad = in.readLong();
this.downPayLoad = in.readLong();
this.totalPayLoad = in.readLong();
}
public String getTelNo() {
return telNo;
}
public void setTelNo(String telNo) {
this.telNo = telNo;
}
public long getUpPayLoad() {
return upPayLoad;
}
public void setUpPayLoad(long upPayLoad) {
this.upPayLoad = upPayLoad;
}
public long getDownPayLoad() {
return downPayLoad;
}
public void setDownPayLoad(long downPayLoad) {
this.downPayLoad = downPayLoad;
}
public long getTotalPayLoad() {
return totalPayLoad;
}
public void setTotalPayLoad(long totalPayLoad) {
this.totalPayLoad = totalPayLoad;
}
}
基于文件的存储结构
SequenceFile:无序存储。
MapFile会对key建立索引文件,value按key顺序存储。
基于MapFile的结构有:
ArrayFile:像数组一样,key值为序列化的数字。
SetFile:只有key,value为不可变的数据。
BloomMapFile:在MapFile的基础上增加了一个/bloom文件,包含的是二进制的过滤表,在每一次写操作完成时,会更新这个过滤表。
2.11.1 FileInputFormat
FileInputFormat:是所有以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所有文件,并实现了对输入文件计算splits的方法。至于获得记录的方法是有不同的子类--------TextInputFormat
2.11.2 InputFormat
InputFormat负责处理MR的输入部分。
有三个作用:
例如:1G的文件会被划分成16个64MB的split,并分配16个map任务处理,而10000个100kb的文件会被10000个map任务处理,所以不适合处理大量小文件的数据。
2.11.3 TextInputFormat
TextInputFormat是默认的处理类,处理普通的文本文件。
文件中每一行作为一个记录,他将每一行在文件中的起始偏移量作为key,每一行的内容作为value。
默认以\n或回车键作为一行记录。
TextInputFormat继承了FileInputFormat
2.11.4 InputFormat类的层次结构
2.11.5 其他输入类
CombineFileInputFormat
相对于大量的小文件来说,hadoop更加适合处理少量的大文件。
CombineFileInputFormat可以缓解这个问题,它是针对小文件而设计的。
KeyValueTextInputFormat
当输入数据的每一行是两列,并用tab分离的形式的时候,KeyValueTextInputFormat处理这种格式的文件非常适合。
NLineInputFormat
可以控制在每个split中数据的行数。
SequenceFileInputFormat
当输入文件格式是sequenceFile的时候,要使用SequenceFileInputFormat作为输入。
2.11.6 自定义输入格式
//案例:定义输入格式,作为二进制输入
//1.重写FileInputFormat
package com.saviation.mapreduce;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.mapred.FileSplit;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
/**
* 重写输入格式,作为二进制输入
* @author caonanqing
*
*/
public class TrackFileInputFormat extends FileInputFormat
//2.重写RecordReader
package com.saviation.mapreduce;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
/**
* 打开文件,将文件字节存到value
* @author caonanqing
*
*/
public class TrackRecordReader extends RecordReader {
private FSDataInputStream inputStream = null; // 文件操作
private long start,end,pos; //起始位置,结束位置,使用长度
private Configuration conf;
private FileSplit fileSplit;
private Text key = null;
private BytesWritable value = null;
private boolean processed = false;
/**
* 进行初始化,打开文件流,根据分块信息设置起始位置,结束位置和长度等
*/
@Override
public void initialize(InputSplit inputSplit, TaskAttemptContext context)
throws IOException, InterruptedException {
fileSplit = (FileSplit)inputSplit;
conf = context.getConfiguration();
this.start = fileSplit.getStart(); // 设置起始位置
this.end = this.start + fileSplit.getLength(); // 设置结束位置
try{
//获取分片文件对应的完整文件
Path path = fileSplit.getPath();
FileSystem fs = path.getFileSystem(conf);
this.inputStream = fs.open(path); // 打开文件
inputStream.seek(this.start); // 设置已读取长度
this.pos = this.start;
}catch(IOException e ){
e.printStackTrace();
}
}
/**
* 生成下一个键对值
*/
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if(this.pos < this.end){
this.key = new Text(fileSplit.getPath().getName()); // 获取文件名称作为key
byte[] bytes = new byte[(int) fileSplit.getLength()];
IOUtils.readFully(this.inputStream, bytes, 0, bytes.length);
this.value = new BytesWritable(bytes);// 获取字节作为value
this.pos = inputStream.getPos();
return true;
}else{
processed = true;
return false;
}
}
/**
* 获取当前key
*/
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
/**
* 获取当前value
*/
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
/**
* 获取处理进度
*/
@Override
public float getProgress() throws IOException, InterruptedException {
return ((processed == true) ? 1.0f : 0.0f);
}
/**
* 关闭文件流
*/
@Override
public void close() throws IOException {
try{
if(inputStream != null){
inputStream.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
//3.Mapper映射类
public class TrackMapper extends Mapper {
@Override
protected void map(Object key, BytesWritable value, Context context)
throws IOException, InterruptedException {
byte[] b2 = value.getBytes();
long len = b2.length; // 总的字节数
context.write(new Text(""),new Text(s));
}
}
//4.Reduce归约类
public class TrackReducer extends Reducer {
@Override
protected void reduce(Text key, Iterable values, Reducer.Context context) throws IOException, InterruptedException {
//普通解析
for(Text v : values){
System.out.println();
context.write(key, v);
}
}
}
//5.Driver驱动类
/**
* mapreduce主函数
* @author caonanqing
*
*/
public class TrackDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] newArgs = new String[]{"hdfs://namenode:9000/track/track_input/20180401.ZNDQ",
"hdfs://namenode:9000/track/track_out1"};
String[] otherArgs = new GenericOptionsParser(conf, newArgs).getRemainingArgs();
Job job = Job.getInstance(conf, "TrackDriver"); //启动job,并给job起个名称
job.setJarByClass(TrackDriver.class); //主函数
job.setInputFormatClass(TrackFileInputFormat.class); //重写输入格式
job.setMapOutputKeyClass(Text.class); //map输出key
job.setMapOutputValueClass(Text.class); //map输出value
job.setMapperClass(TrackMapper.class); //map函数
job.setCombinerClass(TrackCombiner.class); //combiner,本地合并
job.setReducerClass(TrackReducer.class); //reduce函数
job.setOutputKeyClass(Text.class); //reduce输出key
job.setOutputValueClass(Text.class); //redecu输出value
FileInputFormat.setInputPaths(job, new Path(newArgs[0])); //输入文件路径
FileOutputFormat.setOutputPath(job, new Path(newArgs[1])); //输出文件路径
if (!job.waitForCompletion(true))
return;
}
}
TextOutputFormat
默认的输出格式,key和value中间值用tab隔开的。
SequenceFileOutputFormat
将key和value以sequencefile格式输出
SequenceFileAsOutputFormat
将key和value以原始的二进制格式输出
MapFileOutFormat
将key和value写入MapFile中,由于MapFile中的key是有序的,都有写入的时候必须保证记录是按key值顺序写入的。
MultipleOutputFormat
默认情况下一个reduce会产生一个输出,但是有些时候我们想一个reduce产生多个输出,MultipleOutputFormat和MultipleOutputs可以实现这个功能。
Hadoop计数器:可以让开发人员以全局的视角来审查程序的运行情况以及各项指标,及时做出错误诊断并进行相应处理。
内置计数器(MapReduce相关、文件系统相关和作业调度相关)
可以通过Master:50030/jobdetails.jsp查看
例子:Hello you,hello me 的计数器信息
Counters: 19
File Output Format Counters
Bytes Written=19 //reduce输出到hdfs的字节数
FileSystemCounters
FILE_BYTES_READ=481
HDFS_BYTES_READ=38
FILE_BYTES_WRITTEN=81316
HDFS_BYTES_WRITTEN=19
File Input Format Counters
Bytes Read=19 //map从hdfs读取的字节数
Map-Reduce Framework
Map output materialized bytes=49
Map input records=2 //map读入的记录行数
Reduce shuffle bytes=0
Spilled Records=8
Map output bytes=35
Total committed heap usage (bytes)=266469376
SPLIT_RAW_BYTES=105
Combine input records=0
Reduce input records=4 //reduce从map端接收的记录行数
Reduce input groups=3 //reduce函数接收的key数量,即归并后的k2数量
Combine output records=0
Reduce output records=3 //reduce输出的记录行数
Map output records=4 //map输出的记录行数
2.13.1 自定义计数器与实现
Context类调用方法getCounter()
计数器声明
1.通过枚举声明
context.getCounter(Enum enum)
2.动态声明
context.getCounter(String groupName,String counterName)
计数器操作
counter.setValue(long value);//设置初始值
counter.increment(long incr);//增加计数
org.apache.hadoop.mapreduce.Counter
每一个map可以会产生大量的输出,combiner的作用就是在map端对输出先做一次合并,以减少传输到Reduce的数据量。
Combiner最基本是实现本地key的归并,Combiner具有类似本地的Reduce功能。
如果不用Combiner,那么,所有的结果都是在Reduce完成,效率会相对低下。使用Combiner,先完成的map会在本地聚合,提升速度。
注意:Combiner的输出是Reduce的输入,Combiner决不能改变最终的计算结果,所以Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如:累加,最大值。
Partitioner是partitioner的基类,如果需要定制partitioner也需要继承该类。
HashPartitioner是MapReduce的默认Partitioner。计算方法是
which reduce= (key.hashCode() & Integer.Max_VALUE) % numReduceTasks
得到当前的数目的reduce。
在Map和Reduce阶段进行排序时,比较的是k2。v2是不参与排序比较的。如果要想让v2也进行排序,需要把k2和v2组装成新的类,作为k2,才能参与比较。
分组时也是按照k2进行比较的。
Map:
Reduce:
Codec为压缩,解压缩的算法实现。
在Hadoop中,codec有CompressionCode的实现来表示。下面是一些实现:
MapReduce的输出进行压缩
输出的压缩属性: