Bootstrap

Hadoop之MapReduce

MapReduce编程规范

  1. Mapper阶段
    (1)用户自定义的Mapper要继承自己的父类.
    (2) Mapper的输入数据是KV对的形式(KV的类型可自定义)
    (3) Mapper中的业务逻辑写在map()方法中
    (4) Mapper的输出数据是KV对的形式(KV的类型可自定义)
    (5) map()方法(MapTask进程) 对每一个K,V>调用一次
  2. Reducer阶 段
    (1)用户自定义的Reducer要继承自己的父类
    (2) Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
    (3) Reducer的业 务逻辑写在reduce()方法中。
    (4) ReduceTask进 程对每- -组相同k的<k,v>组调用一次reduce()方法
  3. Driver阶 段
    相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是
    封装了MapReduce程序相关运行参数的job对象

自定义bean对象实现序列化接口(Writable)

在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架内部传递一个bean对象,那么该对象就需要实现序列化接口。
具体实现bean对象序列化步骤如下7步。
(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

public FlowBean() {
	super();
}

(3)重写序列化方法

@Override
public void write(DataOutput out) throws IOException {
	out.writeLong(upFlow);
	out.writeLong(downFlow);
	out.writeLong(sumFlow);
}

4)重写反序列化方法

@Override
public void readFields(DataInput in) throws IOException {
	upFlow = in.readLong();
	downFlow = in.readLong();
	sumFlow = in.readLong();
}

(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用。
(7)如果需要将自定义的bean放在key中传输,则还需要实现Comparable接口,因为MapReduce框中的Shuffle过程要求对key必须能排序。

@Override
public int compareTo(FlowBean o) {
	// 倒序排列,从大到小
	return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

设置切片大小

驱动类中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);

//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
//运行如果为3个切片。

//虚拟存储切片最大值设置20m   运行结果为1个切片
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);

每一行均为一条记录,被分隔符分割为key value。可以通过在驱动类中设置
conf. set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");来设定分隔符。默认分隔符是tab (\t)
 NLinelnputF ormat
如果使用NlineInputFormat,代表每个map进程处理的InputSplit不再按B1ock块去划分,而是按NineInputF ormat指定的行数N来划分。即输入文件的总行数/N=切片数,如果不整除,切片数=+1// 设置每个切片InputSplit中划分三条记录
        NLineInputFormat.setNumLinesPerSplit(job, 3);
          
        // 使用NLineInputFormat处理记录数  
        job.setInputFormatClass(NLineInputFormat.class); 

自定义InputFormat

在企业开发中,Hadoop框架自带的InputF ormat类型不能满足所有应用场景,需要自定义InputF ormat来解决实际问题。自定义InputF ormat步骤如下:
(1)自定义一个类继承FileInputFormat。

(1)重写isSplitable0方法,返回false不可切割.

	@Override
	protected boolean isSplitable(JobContext context, Path filename) {
		return false;
	}

(2)重写createRecordReader),创建自定义的RecordReader对象,并初始化

@Override 	public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)	throws IOException, InterruptedException {
		 		WholeRecordReader recordReader = new WholeRecordReader(); 		
		 		recordReader.initialize(split, context);
		 		return recordReader;

(2)改写RecordReader, 实现-次读取一个完整文件 封装为KV。

(1)采用I0流一次读取一个文件输出到value中,因为设置了不可切片,最终把所有文 件都封装到了value中

(2)获取文件路径信息+名称,并设置key

public class WholeRecordReader extends RecordReader<Text, BytesWritable>{

	private Configuration configuration;
	private FileSplit split;
	
	private boolean isProgress= true;
	private BytesWritable value = new BytesWritable();
	private Text k = new Text();

	@Override
	public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
		
		this.split = (FileSplit)split;
		configuration = context.getConfiguration();
	}

	@Override
	public boolean nextKeyValue() throws IOException, InterruptedException {
		
		if (isProgress) {

			// 1 定义缓存区
			byte[] contents = new byte[(int)split.getLength()];
			
			FileSystem fs = null;
			FSDataInputStream fis = null;
			
			try {
				// 2 获取文件系统
				Path path = split.getPath();
				fs = path.getFileSystem(configuration);
				
				// 3 读取数据
				fis = fs.open(path);
				
				// 4 读取文件内容
				IOUtils.readFully(fis, contents, 0, contents.length);
				
				// 5 输出文件内容
				value.set(contents, 0, contents.length);

				// 6 获取文件路径及名称
				String name = split.getPath().toString();

				// 7 设置输出的key值
				k.set(name);

			} catch (Exception e) {
				
			}finally {
				IOUtils.closeStream(fis);
			}
			
			isProgress = false;
			
			return true;
		}
		
		return false;
	}

	@Override
	public Text getCurrentKey() throws IOException, InterruptedException {
		return k;
	}

	@Override
	public BytesWritable getCurrentValue() throws IOException, InterruptedException {
		return value;
	}

	@Override
	public float getProgress() throws IOException, InterruptedException {
		return 0;
	}

	@Override
	public void close() throws IOException {
	}
}

(3)在输出时使用SequenceF ileOutPutFormat输出合并文件。

 //设置输入的inputFormat
		job.setInputFormatClass(WholeFileInputformat.class);
       // 8设置输出的outputFormat
	 job.setOutputFormatClass(SequenceFileOutputFormat.class);

添加分区

  1. 继承Partitioner并重写getPartition()
#手机归属地不同省份输出到不同文件中(分区)
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {

	@Override
	public int getPartition(Text key, FlowBean value, int numPartitions) {

		// 1 获取电话号码的前三位
		String preNum = key.toString().substring(0, 3);
		
		int partition = 4;
		
		// 2 判断是哪个省
		if ("136".equals(preNum)) {
			partition = 0;
		}else if ("137".equals(preNum)) {
			partition = 1;
		}else if ("138".equals(preNum)) {
			partition = 2;
		}else if ("139".equals(preNum)) {
			partition = 3;
		}

		return partition;
	}
}
  1. 在驱动函数中增加自定义数据分区设置和ReduceTask设置

     // 指定自定义数据分区
     job.setPartitionerClass(ProvincePartitioner.class);
    
     // 同时指定相应数量的reduce task
     job.setNumReduceTasks(5);
    

WritableComparable排序

bean对象做为key传输,需要实现WritableComparable接口重写compareTo方法,就可以实现排序。

@Override
public int compareTo(FlowBean o) {

	int result;
		
	// 按照总流量大小,倒序排列
	if (sumFlow > bean.getSumFlow()) {
		result = -1;
	}else if (sumFlow < bean.getSumFlow()) {
		result = 1;
	}else {
		result = 0;
	}

	return result;
}

MapReduce开发总结(重点)

1.输入数据接口: InputFormat

(1)默认使用的实现类是: TextInputFormat
(2) TextInputFormat的功能逻辑是: 一次读一行文本, 然后将该行的起始 偏移量作为key,行内容作为value返回。
(3) KeyValue TextInputFormat每一行均为一 条记录,被分隔符分割为key, value。默认分隔符是tab (\t)
(4) NlineInputF ormat按照指定的行数N来划分切片。
(5) CombineTextInputFormat可以把多个小文件合并成一个切片处理,提高 处理效率。
(6)户还可以自定义InputF ormat。

2.逻辑处理接口: Mapper

用户根据业务需求实现其中三个方法: map() setup() cleanup()

  1. Partitioner分区

(1)有默认实现HasPatitioner,逻辑是根据key的哈希值和numReduces来返回-个分区号; key
hashCoe()&Intger.MAXVALUE % numReduces (2)如果业务上有特别的需求,可以自定义分区。

  1. Comparable排序

(1) 当我们用自定义的对象作为key来输出时,就必须要实现
WritableComparable接口,重写其中的compareTo()方法。
(2)部分排序:对最终输出的每一个文件进行内部排序。
(3)全排序:对所有数据进行排序,通常只有一个Reduce。
(4)二次排序:排序的条件有两个。

  1. Combiner合并

Combiner合并可以提高程序执行效率,减少I0传输。但是使用时必须不能影响原有的业务处理结果

  1. Reduce端分组: GroupingC omparator

在Reduc e端对kexj进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一-个reduce方法时,可以采用分组排序。

7.逻辑处理接口: Reducer

用户根据业务需求实现其中三个方法: reduce() setup() cleamp ()

8.输出数据接口: OutputFormat

(1)默认实现类是TextOutputFormat, 功能逻辑是:将每-个KV对,向目标文 本文件输出一行。
(2)将SequenceFileOutputFormat输出作为后续MapReduce任务的输入,这便是 一种好的输出格式,因为它的格式紧凑,很容易被压缩。
(3)用户还可以自定义OutputFormat。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;