Hadoop Day4

 


1.Writable接口与序列化机制(***必须掌握***
序列化概念
序列化(Serialization是指把结构化对象转化为字节流。
反序列化(Deserialization是序列化的逆过程。即把字节流转回结构化对象。
Java序列化(java.io.Serializable
Hadoop序列化的特点
Hadoop的序列化格式:Writable
序列化格式特点:
紧凑:高效使用存储空间。
快速:读写数据的额外开销小
可扩展:可透明地读取老格式的数据
互操作:支持多语言的交互
Hadoop序列化的作用
序列化在分布式环境的两大作用:进程间通信,永久存储。
Hadoop节点间通信。

 

Writable接口
Writable接口, 是根据 DataInput 和 DataOutput 实现的简单、效的序列化对象.
MapReduce的任意Key和Value必须实现Writable接口.

MapReduce的任意key必须实现WritableComparable接口

 

 


常用的Writable实现类
Text一般认为它等价于java.lang.String的Writable。针对UTF-8序列。

例:
Text test = new Text("test");
IntWritable one = new IntWritable(1);

 

 

 

自定义WritableKpi
电信例子

FlowBean.java
package cn.itcast.hadoop.mr.flowsum;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;


public class FlowBean implements Writable {

private String phoneNB;
private long up_flow;
private long down_flow;
private long sun_flow;


//在反序列化时,反射机制需要调用空参的构造函数,所以显示定义了一个空参构造方法。
public FlowBean() {
}
//为了调用对象数据方便,加入一个带参数的构造方法
public FlowBean(String phoneNB, long up_flow, long down_flow) {
this.phoneNB = phoneNB;
this.up_flow = up_flow;
this.down_flow = down_flow;
this.sun_flow = up_flow+down_flow;
}
public String getPhoneNB() {
return phoneNB;
}
public void setPhoneNB(String phoneNB) {
this.phoneNB = phoneNB;
}
public long getUp_flow() {
return up_flow;
}
public void setUp_flow(long up_flow) {
this.up_flow = up_flow;
}
public long getDown_flow() {
return down_flow;
}
public void setDown_flow(long down_flow) {
this.down_flow = down_flow;
}
public long getSun_flow() {
return sun_flow;
}
public void setSun_flow(long sun_flow) {
this.sun_flow = sun_flow;
}
//将对象数据序列化到输入流
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(phoneNB);
out.writeLong(up_flow);
out.writeLong(down_flow);
out.writeLong(sun_flow);
}
//将输入流中的数据序列化为输出流
@Override
public void readFields(DataInput in) throws IOException {
phoneNB = in.readUTF();
up_flow = in.readLong();
down_flow = in.readLong();
sun_flow = in.readLong();
}
@Override
public String toString() {
return ""+up_flow+"\t"+d_flow +"\t"+s_flow;
}
}

flowsumReducer.java

package cn.itcast.hadoop.mr.flowsum;
import java.io.IOException;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class flowsumReducer extends Reducer<Text, FlowBean, Text, FlowBean> {

@Override
protected void reduce(Text key, Iterable<FlowBean> valus,
Reducer<Text, FlowBean, Text, FlowBean>.Context context)
throws IOException, InterruptedException {
long up_flow_counter =0;
long d_flow_counter =0;

for(FlowBean bean :valus){
up_flow_counter +=bean.getUp_flow();
d_flow_counter +=bean.getDown_flow();
}

context.write(key, new FlowBean(key.toString(), up_flow_counter, d_flow_counter));
}
}

FlowsunRunner .java

package cn.itcast.hadoop.mr.flowsum;

import javax.imageio.stream.FileImageInputStream;

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.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

//job提交类的标准写法
public class FlowsunRunner extends Configured implements Tool{

public static void main(String[] args) throws Exception {
int reds = ToolRunner.run(new Configuration(), new FlowsunRunner(), args);
System.exit(reds);
}

@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(FlowsunRunner.class);
job.setMapperClass(FlowsumMapper.class);
job.setReducerClass(flowsumReducer.class);

job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);

job.setOutputKeyClass(FlowBean.class);
job.setOutputValueClass(FlowBean.class);

FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));

return job.waitForCompletion(true)?0:1;
}
}


FlowBean.java

public class FlowBean implements WritableComparable<FlowBean>{

private String phoneNB;
private long up_flow;
private long d_flow;
private long s_flow;
//在反序列化时,反射机制需要调用空参数构造函数,所以显示定义了一个空参构造函数。
public FlowBean(){}
//为了对象数据的初始化方便,加入一个带参的构造函数。
public FlowBean(String phoneNB,long up_flow,long d_flow){
this.phoneNB = phoneNB;
this.up_flow = up_flow;
this.d_flow = d_flow;
this.s_flow=up_flow+d_flow;
}

public String getPhoneNB() {
return phoneNB;
}

public void setPhoneNB(String phoneNB) {
this.phoneNB = phoneNB;
}

public long getUp_flow() {
return up_flow;
}

public void setUp_flow(long up_flow) {
this.up_flow = up_flow;
}

public long getD_flow() {
return d_flow;
}

public void setD_flow(long d_flow) {
this.d_flow = d_flow;
}

public long getS_flow() {
return s_flow;
}

public void setS_flow(long s_flow) {
this.s_flow = s_flow;
}
//将对象数据序列化到流中
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(phoneNB);
out.writeLong(up_flow);
out.writeLong(d_flow);
out.writeLong(s_flow);

}

//从数据流中反序列出对象的数据
//从数据流中读出对象字段时,必须跟序列化时的顺序保持一致
@Override
public void readFields(DataInput in) throws IOException {
phoneNB = in.readUTF();
up_flow = in.readLong();
d_flow = in.readLong();
s_flow = in.readLong();
}


@Override
public String toString() {
return ""+up_flow+"\t"+d_flow +"\t"+s_flow;
}
@Override
public int compareTo(FlowBean o) {
return s_flow>o.getS_flow()?-1:1;
}
}

FlowsumMapper.java

/**
* FlowBean 我们自定义的一种数据类型,必须实现序列化接口
* @author root
*
*/
public class FlowsumMapper extends Mapper<LongWritable, Text, Text, FlowBean>{

@Override
protected void map(LongWritable key, Text value,
Mapper<LongWritable, Text, Text, FlowBean>.Context context)
throws IOException, InterruptedException {
//拿到一行数据
String line = value.toString();
//分字段
String[] fields = StringUtils.split(line,"\t");
//拿到我们要的字段信息
String phoneNB= fields[1];
long up_flow = Long.parseLong(fields[7]);
long d_flow = Long.parseLong(fields[8]);
//封装数据k v 并输出
context.write(new Text(phoneNB), new FlowBean(phoneNB,up_flow,d_flow));

}

}

FlowSumRedcer.java

public class FlowSumRedcer extends Reducer<Text, FlowBean, Text, FlowBean>{

@Override
protected void reduce(Text key, Iterable<FlowBean> values,
Reducer<Text, FlowBean, Text, FlowBean>.Context context)
throws IOException, InterruptedException {
long up_flow_counter= 0;
long d_flow_counter=0;

for(FlowBean flowBean :values){
up_flow_counter += flowBean.getUp_flow();
d_flow_counter += flowBean.getD_flow();
}

context.write(key, new FlowBean(key.toString(),up_flow_counter,d_flow_counter));
}


}

FlowsumRunner.java
//job提交类的标准写法
public class FlowsunRunner extends Configured implements Tool{

public static void main(String[] args) throws Exception {
int reds = ToolRunner.run(new Configuration(), new FlowsunRunner(), args);
System.exit(reds);
}

@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(FlowsunRunner.class);
job.setMapperClass(FlowsumMapper.class);
job.setReducerClass(flowsumReducer.class);

job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);

job.setOutputKeyClass(FlowBean.class);
job.setOutputValueClass(FlowBean.class);

FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));

return job.waitForCompletion(true)?0:1;
}
}


把上面例子里的Mapper的value改写为自定义Writable类型。修改原MapReduce程序,并成功执行。结果跟原来一致。
MapReduce输入的处理类
FileInputFormat
FileInputFormat是所以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所文件,并实现了对输入文件计算splits的方法。至于获得记录的方法是不同的子类——TextInputFormat进行实现的。 
InputFormat

 


InputFormat 负责处理MapReduce的输入部分.
个作用:
验证作业的输入是否规范.
把输入文件切分成InputSplit.
提供RecordReader 的实现类,把InputSplit读到Mapper中进行处理.
InputFormat类的层次结构

 

 

 

 

 

InputSplit
在执行mapreduce之前,原始数据被分割成若干split,每个split作为一个map任务的输入,在map执行过程中split会被分解成一个个记录(key-value对,map会依次处理每一个记录。
FileInputFormat只划分比HDFS block大的文件,所以FileInputFormat划分的结果是这个文件或者是这个文件中的一部分.
如果一个文件的大小比block小,将不会被划分,这也是Hadoop处理大文件的效率要比处理很多小文件的效率高的原因。
当Hadoop处理很多小文件(文件大小小于hdfs block大小的时候,由于FileInputFormat不会对小文件进行划分,所以每一个小文件都会被当做一个split并分配一个map任务,导致效率底下。

例如:一个1G的文件,会被划分成16个64MB的split,并分配16个map任务处理,而10000个100kb的文件会被10000个map任务处理。 

TextInputFormat
TextInputformat是默认的处理类,处理普通文本文件。
文件中每一行作为一个记录,他将每一行在文件中的起始偏移量作为key,每一行的内容作为value。
默认以\n或回车键作为一行记录。
TextInputFormat继承了FileInputFormat。

其他输入类
CombineFileInputFormat
相对于大量的小文件来说,hadoop更合适处理少量的大文件。
CombineFileInputFormat可以缓解这个问题,它是针对小文件而设计的。
KeyValueTextInputFormat
当输入数据的每一行是两列,并用tab分离的形式的时候,KeyValueTextInputformat处理这种格式的文件非常适合。

NLineInputformat 
NLineInputformat可以控制在每个split中数据的行数。
SequenceFileInputformat 
当输入文件格式是sequencefile的时候,要使用SequenceFileInputformat作为输入。

自定义输入格式
继承FileInputFormat基类。
重写里面的getSplits(JobContext context)方法。
重写createRecordReader(InputSplit split,askAttemptContext context)方法。

Hadoop的输出
TextOutputformat
默认的输出格式,key和value中间值用tab隔开的。
SequenceFileOutputformat
将key和value以sequencefile格式输出。
SequenceFileAsOutputFormat
将key和value以原始二进制的格式输出。
MapFileOutputFormat
将key和value写入MapFile中。由于MapFile中的key是序的,所以写入的时候必须保证记录是key值顺序写入的。
MultipleOutputFormat
默认情况下一个reducer会产生一个输出,但是有些时候我们想一个reducer产生多个输出,MultipleOutputFormat和MultipleOutputs可以实现这个功能。


2.自定义排序编程(*****必须掌握******
public class SortMr {

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());

job.setJarByClass(SortMr.class);
job.setMapperClass(SortMapper.class);
job.setReducerClass(SortReducer.class);

job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(NullWritable.class);

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);

FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));

System.exit(job.waitForCompletion(true)?0:1);

}

public static class SortMapper extends Mapper<LongWritable, Text, FlowBean, NullWritable>{

@Override
protected void map(
LongWritable key,
Text value,
Mapper<LongWritable, Text, FlowBean, NullWritable>.Context context)
throws IOException, InterruptedException {
String line = value.toString();

String[] fields = StringUtils.split(line,"\t");

String phoneNB = fields[0];
long up_flow = Long.parseLong(fields[1]);
long d_flow = Long.parseLong(fields[2]);

context.write(new FlowBean(phoneNB, up_flow, d_flow), NullWritable.get());
}

}

public static class SortReducer extends Reducer<FlowBean, NullWritable, Text, FlowBean>{

@Override
protected void reduce(FlowBean key, Iterable<NullWritable> values,
Reducer<FlowBean, NullWritable, Text, FlowBean>.Context content)
throws IOException, InterruptedException {
String phoneNB = key.getPhoneNB();
content.write(new Text(phoneNB), key);
}

}
}
排序和分组:
在map和reduce阶段进行排序时,比较的是k2。v2是不参与排序比较的。如果要想让v2也进行排序,需要把k2和v2组装成新的类,作为k2,才能参与比较。

分组时也是照k2进行比较的。

3.Partitioner编程(*****必须掌握******
Partitioner是partitioner的基类,如果需要定制partitioner也需要继承该类。

HashPartitioner是mapreduce的默认partitioner。计算方法是
which reducer=(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks,得到当前的目的reducer。

例子:
AreaPartitioner.java
public class AreaPartitioner<KEY, VALUE> extends Partitioner<KEY, VALUE>{
private static HashMap<String,Integer> areaMap = new HashMap<>();
static{
areaMap.put("135", 0);
areaMap.put("136", 1);
areaMap.put("137", 2);
areaMap.put("138", 3);
areaMap.put("139", 4);
}
@Override
public int getPartition(KEY key, VALUE value, int numPartitions) {
//从key中拿到手机号,查询手机归属地字典,不同的省份返回不同的组号

int areaCoder = areaMap.get(key.toString().substring(0, 3))==null?5:areaMap.get(key.toString().substring(0, 3));

return areaCoder;
}

}
FlowSumArea.java
/**
* 对流量原始日志进行流量统计,将不同省份的用户统计结果输出到不同文件
* 需要自定义改造两个机制:
* 1、改造分区的逻辑,自定义一个partitioner
* 2、自定义reduer task的并发任务数
*
* @author yangbingwen@itcast.cn
*
*/
public class FlowSumArea {

public static class FlowSumAreaMapper extends Mapper<LongWritable, Text, Text, FlowBean>{

@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {

//拿一行数据
String line = value.toString();
//切分成各个字段
String[] fields = StringUtils.split(line, "\t");

//拿到我们需要的字段
String phoneNB = fields[1];
long u_flow = Long.parseLong(fields[7]);
long d_flow = Long.parseLong(fields[8]);

//封装数据为kv并输出
context.write(new Text(phoneNB), new FlowBean(phoneNB,u_flow,d_flow));

}
}
public static class FlowSumAreaReducer extends Reducer<Text, FlowBean, Text, FlowBean>{

@Override
protected void reduce(Text key, Iterable<FlowBean> values,Context context)
throws IOException, InterruptedException {
long up_flow_counter = 0;
long d_flow_counter = 0;

for(FlowBean bean: values){
up_flow_counter += bean.getUp_flow();
d_flow_counter += bean.getD_flow();
}
context.write(key, new FlowBean(key.toString(), up_flow_counter, d_flow_counter));
}
}

public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);

job.setJarByClass(FlowSumArea.class);

job.setMapperClass(FlowSumAreaMapper.class);
job.setReducerClass(FlowSumAreaReducer.class);

//设置我们自定义的分组逻辑定义
job.setPartitionerClass(AreaPartitioner.class);

job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);

//设置reduce的任务并发数,应该跟分组的数量保持一致
job.setNumReduceTasks(1);

FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));

System.exit(job.waitForCompletion(true)?0:1);
}
}
排序和分组
在map和reduce阶段进行排序时,比较的是k2。v2是不参与排序比较的。如果要想让v2也进行排序,需要把k2和v2组装成新的类,作为k2,才能参与比较。

分组时也是照k2进行比较的。

Combiner编程(*****必须掌握******
开启Combiner设置:在组装job类里设置它。
job.setReducerClass(XXReducer.class)改为job.setCombinerClass(XXReducer.class)。

每一个map可能会产生大量的输出,combiner的作用就是在map端对输出先做一次合并,以减少传输到reducer的数据量。

combiner最基本是实现本地key的归并,combiner具类似本地的reduce功能。

如果不用combiner,那么,所的结果都是reduce完成,效率会相对低下。使用combiner,先完成的map会在本地聚合,提升速度。

注意:Combiner的输出是Reducer的输入,如果Combiner是可插拔的,添加Combiner绝不能改变最终的计算结果。所以Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。
combiner使用的合适,可以在满足业务的情况下提升job的速度,如果不合适,则将导致输出的结果不正确。

5.常见的MapReduce算法(******了解*****
单词计数
数据去重
排序
Top K

投影
分组
多表连接
单表关联
6.Mapreduce原理及源码分析
Job执行流程图

 

 

 


Shuffle

 

MAP:
每个map一个环形内存缓冲区,用于存储任务的输出。默认大小100MB(io.sort.mb属性,一旦达到阀值0.8(io.sort.spill.percent,一个后台线程把内容写到(spill)磁盘的指定目录(mapred.local.dir下的新建的一个溢出写文件。
写磁盘前,要partition,sort。如果combiner,combine排序后数据。
等最后记录写完,合并全部溢出写文件为一个分区且排序的文件。


REDUCCE
Reducer通过Http方式得到输出文件的分区。
TaskTracker为分区文件运行Reduce任务。复制阶段把Map输出复制到Reducer的内存或磁盘。一个Map任务完成,Reduce就开始复制输出。
排序阶段合并map输出。然后走Reduce阶段。
hadoop的压缩codec
Codec为压缩,解压缩的算法实现。
在Hadoop中,codec由CompressionCode的实现来表示。
下面是一些实现:

 

MapReduce的输出进行压缩
输出的压缩属性:

 

posted @ 2016-11-30 21:33  十年饮冰难凉热血  阅读(103)  评论(0编辑  收藏  举报