MapReduce分布式编程模型

大数据技术之HadoopMapReducehtml

MapReduce入门

1.1 MapReduce定义

Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架。前端

Mapreduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。java

1.2 MapReduce优缺点

1.2.1 优势

1MapReduce 易于编程它简单的实现一些接口,就能够完成一个分布式程序,这个分布式程序能够分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是如出一辙的。就是由于这个特色使得MapReduce编程变得很是流行。node

2)良好的扩展性当你的计算资源不能获得知足的时候,你能够经过简单的增长机器来扩展它的计算能力。linux

3高容错性MapReduce设计的初衷就是使程序可以部署在廉价的PC机器上,这就要求它具备很高的容错性。好比其中一台机器挂了,它能够把上面的计算任务转移到另一个节点上运行,不至于这个任务运行失败,并且这个过程不须要人工参与,而彻底是由 Hadoop内部完成的。git

4)适合PB级以上海量数据的离线处理这里加红字体离线处理,说明它适合离线处理而不适合在线处理。好比像毫秒级别的返回一个结果,MapReduce很难作到。github

1.2.2 缺点

MapReduce擅长作实时计算、流式计算、DAG有向图计算。web

1)实时计算。MapReduce没法像Mysql同样,在毫秒或者秒级内返回结果。算法

2)流式计算。流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是由于MapReduce自身的设计特色决定了数据源必须是静态的。spring

3DAG(有向图)计算。多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种状况下,MapReduce并非不能作,而是使用后,每一个MapReduce做业的输出结果都会写入到磁盘,会形成大量的磁盘IO,致使性能很是的低下。

1.3 MapReduce核心思想

 

 

1)分布式的运算程序每每须要分红至少2个阶段。

2)第一个阶段的maptask并发实例,彻底并行运行,互不相干。

3)第二个阶段的reduce task并发实例互不相干,可是他们的数据依赖于上一个阶段的全部maptask并发实例的输出。

4)MapReduce编程模型只能包含一个map阶段和一个reduce阶段,若是用户的业务逻辑很是复杂,那就只能多个mapreduce程序,串行运行。

1.4 MapReduce进程

一个完整的mapreduce程序在分布式运行时有三类实例进程:

1MrAppMaster:负责整个程序的过程调度及状态协调。

2MapTask:负责map阶段的整个数据处理流程。

3ReduceTask:负责reduce阶段的整个数据处理流程。

1.5 MapReduce编程规范

用户编写的程序分红三个部分:MapperReducerDriver(提交运行mr程序的客户端)

1Mapper阶段

1)用户自定义的Mapper要继承本身的父类

(2Mapper的输入数据是KV对的形式(KV的类型可自定义)

(3Mapper中的业务逻辑写在map()方法中

(4Mapper的输出数据是KV对的形式(KV的类型可自定义)

(5map()方法(maptask进程)对每个<K,V>调用一次

2Reducer阶段

1)用户自定义的Reducer要继承本身的父类

(2Reducer的输入数据类型对应Mapper的输出数据类型,也是KV

(3Reducer的业务逻辑写在reduce()方法中

(4Reducetask进程对每一组相同k<k,v>组调用一次reduce()方法

3Driver阶段

整个程序须要一个Drvier来进行提交,提交的是一个描述了各类必要信息的job对象

Hadoop序列化

2.1 为何要序列化?

        通常来讲,“活的”对象只生存在内存里,关机断电就没有了。并且“活的”对象只能由本地的进程使用,不能被发送到网络上的另一台计算机。 然而序列化能够存储“活的”对象,能够将“活的”对象发送到远程计算机。

2.2 什么是序列化?

序列化就是把内存中的对象,转换成字节序列(或其余数据传输协议)以便于存储(持久化)和网络传输。 

反序列化就是将收到字节序列(或其余数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。

2.3 为何不用Java的序列化?

        Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带不少额外的信息(各类校验信息,header,继承体系等),不便于在网络中高效传输。因此,hadoop本身开发了一套序列化机制(Writable),精简、高效。

2.4 为何序列化对Hadoop很重要?

         由于Hadoop在集群之间进行通信或者RPC调用的时候,须要序列化,并且要求序列化要快,且体积要小,占用带宽要小。因此必须理解Hadoop的序列化机制。

        序列化和反序列化在分布式数据处理领域常常出现:进程通讯和永久存储。然而Hadoop中各个节点的通讯是经过远程调用(RPC)实现的,那么RPC序列化要求具备如下特色:

1)紧凑:紧凑的格式能让咱们充分利用网络带宽,而带宽是数据中心最稀缺的资

2)快速:进程通讯造成了分布式系统的骨架,因此须要尽可能减小序列化和反序列化的性能开销,这是基本的;

3)可扩展:协议为了知足新的需求变化,因此控制客户端和服务器过程当中,须要直接引进相应的协议,这些是新协议,原序列化方式能支持新的协议报文;

4)互操做:能支持不一样语言写的客户端和服务端进行交互; 

2.5 经常使用数据序列化类型

经常使用的数据类型对应的hadoop数据序列化类型

Java类型

Hadoop Writable类型

boolean

BooleanWritable

byte

ByteWritable

int

IntWritable

float

FloatWritable

long

LongWritable

double

DoubleWritable

string

Text

map

MapWritable

array

ArrayWritable

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

1自定义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;

}

MapReduce框架原理

3.1 MapReduce工做流程

1)流程示意图

 

 

 

2)流程详解

上面的流程是整个mapreduce最全工做流程,可是shuffle过程只是从7步开始16结束,具体shuffle过程详解以下:

1maptask收集咱们的map()方法输出的kv对,放到内存缓冲区中

2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件

3)多个溢出文件会被合并成大的溢出文件

4)在溢出过程当中,及合并的过程当中,都要调用partitioner进行分区和针对key进行排序

5)reducetask根据本身的分区号,去各个maptask机器上取相应的结果分区数据

6reducetask会取到同一个分区的来自不一样maptask的结果文件,reducetask会将这些文件再进行合并(归并排序)

7)合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法)

3注意

Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。

缓冲区的大小能够经过参数调整,参数:io.sort.mb  默认100M

3.2 InputFormat数据输入

3.2.1 Job提交流程和切片源码详解

1job提交流程源码详解

 

waitForCompletion()

submit();

// 1创建链接

connect();

// 1建立提交job代理

new Cluster(getConfiguration());

// (1)判断是本地yarn仍是远程

initialize(jobTrackAddr, conf);

// 2 提交job

submitter.submitJobInternal(Job.this, cluster)

// 1)建立给集群提交数据的Stag路径

Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

// 2)获取jobid ,并建立job路径

JobID jobId = submitClient.getNewJobID();

// 3)拷贝jar包到集群

copyAndConfigureFiles(job, submitJobDir);

rUploader.uploadFiles(job, jobSubmitDir);

// 4)计算切片,生成切片规划文件

writeSplits(job, submitJobDir);

maps = writeNewSplits(job, jobSubmitDir);

input.getSplits(job);

// 5Stag路径xml配置文件

writeConf(conf, submitJobFile);

conf.writeXml(out);

// 6提交job,返回提交状态

status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());

2)FileInputFormat源码解析(input.getSplits(job))

1)找到你数据存储的目录

2)开始遍历处理(规划切片)目录下的每个文件

3)遍历第一个文件ss.txt

a)获取文件大小fs.sizeOf(ss.txt);

b)计算切片大小computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M

c默认状况下,切片大小=blocksize

d)开始切,造成第1个切片:ss.txt—0:128M 2个切片ss.txt—128:256M 3个切片ss.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍不大于1.1就划分一块切片)

e)将切片信息写到一个切片规划文件中

f)整个切片核心过程在getSplit()方法中完成。

g)数据切片只是在逻辑上对输入数据进行分片,并不会再磁盘上将其切分红分片进行存储InputSplit只记录了分片的元数据信息,好比起始位置长度以及所在的节点列表等。

h注意:blockHDFS物理上存储的数据,切片是对数据逻辑上的划分。

4)提交切片规划文件到yarn,yarn的MrAppMaster就能够根据切片规划文件计算开启maptask个数。

3.2.2 FileInputFormat切片机制

1FileInputFormat中默认的切片机制:

1)简单地按照文件的内容长度进行切片

2)切片大小,默认等于block大小

3)切片时不考虑数据集总体,而是逐个针对每个文件单独切片

好比待处理数据有两个文件:

file1.txt    320M

file2.txt    10M

通过FileInputFormat的切片机制运算后,造成的切片信息以下:  

file1.txt.split1--  0~128

file1.txt.split2--  128~256

file1.txt.split3--  256~320

file2.txt.split1--  0~10M

2)FileInputFormat切片大小的参数配置

经过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 

切片主要由这几个值来运算决定

mapreduce.input.fileinputformat.split.minsize=1 默认值为1

mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认Long.MAXValue

所以,默认状况下,切片大小=blocksize

maxsize(切片最大值):参数若是调得比blocksize小,则会让切片变小,并且就等于配置的这个参数的值。

minsize(切片最小值):参数调的比blockSize大,则可让切片变得比blocksize还大。

3)获取切片信息API

// 根据文件类型获取切片信息

FileSplit inputSplit = (FileSplit) context.getInputSplit();

// 获取切片的文件名称

String name = inputSplit.getPath().getName();

3.2.3 CombineTextInputFormat切片机制

关于大量小文件的优化策略

1)默认状况下TextInputformat对任务的切片机制是按文件规划切片无论文件多小都会是一个单独的切都会交给一个maptask,这样若是有大量小文件产生大量的maptask处理效率极其低下。

2)优化策略

1)最好的办法,在数据处理系统的最前端(预处理/采集将小文件先合并成大文件,再上传到HDFS作后续分析。

2)补救措施若是已是大量小文件在HDFS中了,能够使用另外一种InputFormat来作切片(CombineTextInputFormat,它的切片逻辑跟TextFileInputFormat不一样:它能够将多个小文件从逻辑上规划到一个切片中,这样,多个小文件能够交给一个maptask。

3优先知足最小切片大小,不超过最大切片大小

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

举例0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m

3)具体实现步骤

//  若是不设置InputFormat,它默认用的是TextInputFormat.class

job.setInputFormatClass(CombineTextInputFormat.class)

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

4)案例实操

3.2.4 InputFormat接口实现

MapReduce任务的输入文件通常是存储在HDFS里面。输入的文件格式包括:基于行的日志文件、二进制格式文件等。这些文件通常会很大,达到数十GB,甚至更大。那么MapReduce是如何读取这些数据的呢?下面咱们首先学习InputFormat接口。

InputFormat常见接口实现类包括:TextInputFormatKeyValueTextInputFormatNLineInputFormatCombineTextInputFormat和自定义InputFormat等

1TextInputFormat

TextInputFormat是默认的InputFormat。每条记录是一行输入。键是LongWritable类型,存储该行在整个文件中的字节偏移量。值是这行的内容,不包括任何行终止符(换行符和回车符)。

如下是一个示例,好比,一个分片包含了以下4条文本记录。

Rich learning form

Intelligent learning engine

Learning more convenient

From the real demand for more close to the enterprise

每条记录表示为如下键/值对:

(0,Rich learning form)

(19,Intelligent learning engine)

(47,Learning more convenient)

(72,From the real demand for more close to the enterprise)

很明显,键并非行号。通常状况下,很难取得行号,由于文件按字节而不是按行切分为分片。

2KeyValueTextInputFormat

每一行均为一条记录,被分隔符分割为keyvalue。能够经过驱动类中设置conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");来设定分隔符。默认分隔符是tab\t)。

如下是一个示例,输入是一个包含4条记录的分片。其中——>表示一个(水平方向的)制表符。

line1 ——>Rich learning form

line2 ——>Intelligent learning engine

line3 ——>Learning more convenient

line4 ——>From the real demand for more close to the enterprise

每条记录表示为如下键/值对:

(line1,Rich learning form)

(line2,Intelligent learning engine)

(line3,Learning more convenient)

(line4,From the real demand for more close to the enterprise)

 此时的键是每行排在制表符以前的Text序列。

 3NLineInputFormat

若是使用NlineInputFormat,表明每一个map进程处理的InputSplit再也不按block块去划分,而是NlineInputFormat指定的行数N来划分。即输入文件的总行数/N=切片(20),若是不整除,切片=+1

如下是一个示例,仍然以上面的4行输入为例。

Rich learning form

Intelligent learning engine

Learning more convenient

From the real demand for more close to the enterprise

 例如,若是N2,则每一个输入分片包含两行。开启2maptask。

(0,Rich learning form)

(19,Intelligent learning engine)

另外一个 mapper 则收到后两行:

(47,Learning more convenient)

(72,From the real demand for more close to the enterprise)

        这里的键和值与TextInputFormat生成的同样。

3.2.5 自定义InputFormat

1概述

1)自定义一个类继承FileInputFormat

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

3)在输出时使用SequenceFileOutPutFormat输出合并文件。

2)案例实操

详见7.4小文件处理(自定义InputFormat

3.3 MapTask工做机制

3.3.1 并行度决定机制

1问题引出

maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么mapTask并行任务是否越多越好呢?

2MapTask并行度决定机制

一个jobmap阶段MapTask并行度个数由客户端提交job时的切片个数决定

 

3.3.2 MapTask工做机制

 

1Read阶段:Map Task经过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。

2Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。

3Collect收集阶段:在用户编写map()函数中,当数据处理完成后,通常会调用OutputCollector.collect()输出结果。在函数内部,它会生成的key/value分区调用Partitioner并写入一个环形内存缓冲区中。

4Spill阶段:即“溢”,当环形缓冲区满后,MapReduce将数据写到本地磁盘上,生成一个临时文件。须要注意的是,将数据写入本地磁盘以前,先要对数据进行一次本地排序,并在必要对数据进行合并压缩等操做

写阶段详情:

步骤1利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition进行排序,而后按照key进行排序。这样通过排序后,数据以分区为单位汇集在一块儿,且同一分区内全部数据按照key有序。

 

步骤2按照分区编号由小到大依次将每一个分区中的数据写入任务工做目录下的临时文件output/spillN.outN表示当前溢写次数)中。若是用户设置了Combiner,则写入文件以前,对每一个分区中数据进行一次汇集操做。

步骤3将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每一个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。若是当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index

5Combine阶段:当全部数据处理完成后,MapTask对全部临时文件进行一次合并,以确保最终只会生成一个数据文件。

全部数据处理完后,MapTask会将全部临时文件合并成一个大文件保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。

进行文件合并过程当中,MapTask以分区为单位进行合并。对于某个分区,将采用多轮递归合并的方式每轮合并io.sort.factor(默认100)个文件,并将产生的文件从新加入待合并列表中,对文件排序后,重复以上过程,直到最终获得一个大文件。

每一个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。

3.4 Shuffle机制

3.4.1 Shuffle机制

Mapreduce确保每一个reducer的输入都是按键排序的。系统执行排序的过程(即map输出做为输入传给reducer称为shuffle。

 

 

3.4.2 Partition分区

0问题引出:要求将统计结果按照条件输出到不一样文件中(分区)。好比:将统计结果按照手机归属地不一样省份输出到不一样文件中(分区)

1默认partition分区

public class HashPartitioner<K, V> extends Partitioner<K, V> {

  public int getPartition(K key, V value, int numReduceTasks) {

    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;

  }

}

默认分区是根据keyhashCode对reduceTasks个数取模获得的。用户无法控制哪一个key存储到哪一个分区

2自定义Partitioner步骤

1)自定义类继承PartitionergetPartition()方法

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);

 

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;

}

}

(2)job驱动中,设置自定义partitioner

job.setPartitionerClass(CustomPartitioner.class);

(3)自定义partition后,要根据自定义partitioner的逻辑设置相应数量的reduce task

job.setNumReduceTasks(5);

3)注意:

若是reduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx

若是1<reduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception

若是reduceTask的数量=1,则无论mapTask端输出多少个分区文件,最终结果都交给这一个reduceTask,最终也就只会产生一个结果文件 part-r-00000

例如假设自定义分区数为5

1job.setNumReduceTasks(1);会正常运行,只不过会产生一个输出文件

2job.setNumReduceTasks(2);会报错

3job.setNumReduceTasks(6);大于5,程序会正常运行,会产生空文件

4案例实操

详见7.2.2 需求2:将统计结果按照手机归属地不一样省份输出到不一样文件中(Partitioner

详见7.1.2 需求2:把单词按照ASCII码奇偶分区(Partitioner

3.4.3 WritableComparable排序

排序是MapReduce框架中最重要的操做之一。Map Task和Reduce Task均会对数据(按照key进行排序。该操做属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而无论逻辑上是否须要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。

对于Map Task,它会将处理的结果暂时放到一个缓冲区中,当缓冲区使用率达到必定阈值后,再对缓冲区中的数据进行一次排序,并将这些有序数据写到磁盘上,而当数据处理完毕后,它会对磁盘上全部文件进行一次合并,以将这些文件合并成一个大的有序文件。

对于Reduce Task,它从每一个Map Task上远程拷贝相应数据文件,若是文件大小超过必定阈值,则放到磁盘上,不然放到内存中。若是磁盘上文件数目达到必定阈值则进行一次合并以生成一个更大文件若是内存中文件大小或者数目超过必定阈值,则进行一次合并后数据写到磁盘上。当全部数据拷贝完毕后,Reduce Task统一对内存和磁盘上的全部数据进行一次合并。

每一个阶段的默认排序

1)排序的分类:

1部分排序:

MapReduce根据输入记录的键对数据集排序。保证输出的每一个文件内部排序。

2全排序:

如何用Hadoop产生一个全局排序的文件?最简单的方法是使用一个分区。但方法在处理大型文件时效率极低,由于一台机器必须处理全部输出文件,从而彻底丧失MapReduce所提供的并行架构。

替代方案:首先建立一系列排好序的文件;其次,串联这些文件;最后,生成一个全局排序的文件。主要思路是使用一个分区来描述输出的全局排序。例如能够为上述文件建立3个分区,在第一分区中,记录的单词首字母a-g第二分区记录单词首字母h-n, 分区记录单词首字母o-z。

3)辅助排序:GroupingComparator分组)

Mapreduce框架在记录到达reducer以前按键对记录排序,但键所对应的值并无被排序。甚至不一样的执行轮次中,这些值的排序也不固定,由于它们来自不一样的map任务且这些map任务在不一样轮次中完成时间各不相同。通常来讲,大多数MapReduce程序会避免让reduce函数依赖于值的排序。可是,有时也须要经过特定的方法对键进行排序和分组等以实现对值的排序。

4)二次排序:

自定义排序过程当中若是compareTo中的判断条件为两个即为二次排序。

2)自定义排序WritableComparable

1)原理分析

bean对象实现WritableComparable接口重写compareTo方法,就能够实现排序

@Override

public int compareTo(FlowBean o) {

// 倒序排列,从大到小

return this.sumFlow > o.getSumFlow() ? -1 : 1;

}

2案例实操

详见7.2.3 需求3:将统计结果按照总流量倒序排序(排序)

详见7.2.4 需求4:不一样省份输出文件内部排序(部分排序)

3.4.4 GroupingComparator分组(辅助排序

1)对reduce阶段的数据根据某一个几个字段进行分组

2)案例实操

详见7.3 求出每个订单中最贵的商品GroupingComparator

3.4.5 Combiner合并

0在分布式的架构中,分布式文件系统HDFS,和分布式运算程序编程框架mapreduce。

HDFS:不怕大文件,怕不少小文件

mapreduce :怕数据倾斜

那么mapreduce是若是解决多个小文件的问题呢?

mapreduce关于大量小文件的优化策略

1) 默认状况下,TextInputFormat对任务的切片机制是按照文件规划切片,无论有多少个小文件,都会是单独的切片,都会交给一个maptask,这样,若是有大量的小文件

就会产生大量的maptask,处理效率极端底下

2)优化策略

最好的方法:在数据处理的最前端(预处理、采集),就将小文件合并成大文件,在上传到HDFS作后续的分析

补救措施:若是已是大量的小文件在HDFS中了,可使用另外一种inputformat来作切片(CombineFileInputformat),它的切片逻辑跟TextInputformat

注:CombineTextInputFormat是CombineFileInputformat的子类

不一样:

它能够将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就能够交给一个maptask了

//若是不设置InputFormat,它默认的用的是TextInputFormat.class

/*CombineTextInputFormat为系统自带的组件类

 * setMinInputSplitSize 中的2048是表示n个小文件之和不能大于2048

 * setMaxInputSplitSize 中的4096是     当知足setMinInputSplitSize中的2048状况下  在知足n+1个小文件之和不能大于4096

 */

job.setInputFormatClass(CombineTextInputFormat.class);

CombineTextInputFormat.setMinInputSplitSize(job, 2048);

CombineTextInputFormat.setMaxInputSplitSize(job, 4096);

1)输入数据:准备5小文件

2)实现过程

1不作任何处理,运行需求1wordcount程序,观察切片个数为5

2WordcountDriver中增长以下代码运行程序,并观察运行的切片个数为1

// 若是不设置InputFormat,它默认用的是TextInputFormat.class

job.setInputFormatClass(CombineTextInputFormat.class);

CombineTextInputFormat.setMaxInputSplitSize(job, 4*1024*1024);// 4m

CombineTextInputFormat.setMinInputSplitSize(job, 2*1024*1024);// 2m

 

注:在看number of splits时,和最大值(MaxSplitSize)有关、整体规律就是和低于最大值是一片、高于最大值1.5+,则为两片;高于最大值2倍以上则向下取整,好比文件大小65MB,切片最大值为4MB,那么切片为16.整体来讲,切片差值不超过1个,不影响总体性能

 

6)自定义Combiner实现步骤:

1)自定义一个combiner继承Reducer,重写reduce方法

public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{

@Override

protected void reduce(Text key, Iterable<IntWritable> values,

Context context) throws IOException, InterruptedException {

        // 1 汇总操做

int count = 0;

for(IntWritable v :values){

count = v.get();

}

        // 2 写出

context.write(key, new IntWritable(count));

}

}

2)在job驱动中设置:  

job.setCombinerClass(WordcountCombiner.class);

3.5 ReduceTask工做机制

1设置ReduceTask并行度(个数)

reducetask的并行度一样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不一样,Reducetask数量的决定是能够直接手动设置:

//默认值是1,手动设置为4

job.setNumReduceTasks(4);

2注意

1reducetask=0 ,表示没有reduce阶段,输出文件个数和map个数一致。

2reducetask默认值就是1,因此输出文件个数为一个。

(3)若是数据分布不均匀,就有可能在reduce阶段产生数据倾斜

(4reducetask数量并非任意设置,还要考虑业务逻辑需求,有些状况下,须要计算全局汇总结果,就只能有1reducetask

(5)具体多少reducetask,须要根据集群性能而定。

(6)若是分区数不是1可是reducetask1是否执行分区过程。答案:不执行分区过程。由于在maptask的源码中,执行分区的前提是先判断reduceNum个数是否大于1大于1确定不执行。

3实验:测试reducetask多少合适。

1)实验环境:1个master节点,16slave节点:CPU:8GHZ,内存: 2G

2)实验结论:

1 改变reduce task (数据量1GB)

Map task =16

Reduce task

1

5

10

15

16

20

25

30

45

60

总时间

892

146

110

92

88

100

128

101

145

104

4ReduceTask工做机制

 

1Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,若是其大小超过必定阈值,则写到磁盘上,不然直接放到内存中。

2Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。

3Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行汇集的一组数据。为了key相同的数据聚在一块儿,Hadoop采用了基于排序的策略。因为各个MapTask已经实现对本身的处理结果进行了局部排序,所以,ReduceTask只需对全部数据进行一次归并排序便可。

4Reduce阶段:reduce()函数将计算结果写到HDFS上。

3.6 OutputFormat数据输出

3.6.1 OutputFormat接口实现类

 OutputFormatMapReduce输出的基类,全部实现MapReduce输出都实现了 OutputFormat接口。下面咱们介绍几种常见的OutputFormat实现

1)文本输出TextOutputFormat

        默认的输出格式是TextOutputFormat,它把每条记录写为文本行。它的键和值能够是任意类型,由于TextOutputFormat调用toString()方法把它们转换为字符串。

2SequenceFileOutputFormat

 SequenceFileOutputFormat将它的输出写为一个顺序文件。若是输出须要做为后续 MapReduce任务的输入,这即是一种好的输出格式,由于它的格式紧凑,很容易被压缩。

3自定义OutputFormat

根据用户需求,自定义实现输出。

3.6.2 自定义OutputFormat

为了实现控制最终文件的输出路径,能够自定义OutputFormat。

要在一个mapreduce程序中根据数据的不一样输出两类结果到不一样目录这类灵活的输出需求能够经过自定义outputformat来实现

1自定义OutputFormat步骤

1)自定义一个类继承FileOutputFormat。

2)改写recordwriter,具体改写输出数据的方法write()

2实操案例:

详见7.5 修改日志内容及自定义日志输出路径(自定义OutputFormat)。

3.7 Join多种应用

3.7.1 Reduce join

1)原理

Map端的主要工做:为来自不一样表(文件)key/value对打标签以区别不一样来源的记录。而后用链接字段做为key,其他部分和新加的标志做为value,最后进行输出。

Reduce端的主要工做:在reduce端以链接字段做为key的分组已经完成,咱们只须要在每个分组当中将那些来源于不一样文件的记录(map阶段已经打标志)分开,最后进行合并就ok了。

2)该方法的缺点

这种方式的缺点很明显就是会形成mapreduce端也就是shuffle阶段出现大量的数据传输,效率很低。

3)案例实操

详见7.6.1 需求1reduce端表合并(数据倾斜

3.7.2 Map joinDistributedcache分布式缓存

1)使用场景:一张表十分小、一张表很大。

2)解决方案

map端缓存多张表,提早处理业务逻辑,这样增长map端业务,减小reduce端数据的压力,尽量的减小数据倾斜。

3)具体办法:采用distributedcache

1)在mappersetup阶段,将文件读取到缓存集合中

2)在驱动函数中加载缓存。

job.addCacheFile(new URI("file:/e:/mapjoincache/pd.txt"));// 缓存普通文件到task运行节点

4)实操案例:

详见7.6.2需求2map端表合并(Distributedcache

3.8 数据清洗(ETL

1概述

运行核心业务Mapreduce程序以前,每每要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程每每只须要运行mapper程序,不须要运行reduce程序。

2)实操案例

详见7.7 日志清洗(数据清洗)。

3.9 计数器应用

Hadoop为每一个做业维护若干内置计数器,以描述多项指标。例如某些计数器记录已处理的字节数和记录数,使用户可监控已处理的输入数据量和已产生的输出数据量。

1API

1)采用枚举的方式统计计数

enum MyCounter{MALFORORMED,NORMAL}

//对枚举定义的自定义计数器加1

context.getCounter(MyCounter.MALFORORMED).increment(1);

2)采用计数器组、计数器名称方式统计

context.getCounter("counterGroup", "countera").increment(1);

名和计数器名称随便起,但最好有意义。

3)计数结果在程序运行后的控制台上查看。

2)案例实操

详见7.7 日志清洗(数据清洗)。

3.10 MapReduce开发总结

在编写mapreduce程序时,须要考虑的几个方面

1)输入数据接口:InputFormat

   默认使用的实现类是:TextInputFormat

   TextInputFormat的功能逻辑是:一次读一行文本,而后将该行的起始偏移量做为key,行内容做为value返回。

KeyValueTextInputFormat每一行均为一条记录,被分隔符分割为keyvalue。默认分隔符是tab\t)。

NlineInputFormat按照指定的行数N来划分切片

CombineTextInputFormat能够把多个小文件合并成一个切片处理提升处理效率。

用户还能够自定义InputFormat。

2)逻辑处理接口:Mapper  

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

3Partitioner分区

有默认实现 HashPartitioner,逻辑是根据key哈希值numReduces来返回一个分区号;key.hashCode()&Integer.MAXVALUE % numReduces

若是业务上有特别的需求,能够自定义分区。

4Comparable排序

当咱们用自定义的对象做为key来输出时,就必需要实现WritableComparable接口,重写其中的compareTo()方法。

部分排序:对最终输出的一个文件进行内部排序。

排序对全部数据进行排序,一般只有一个Reduce

二次排序:排序的条件有两个

5Combiner合并

Combiner合并能够提升程序执行效率,减小io传输。可是使用时必须不能影响原有的业务处理结果。

6reduce端分组:Groupingcomparator

reduceTask拿到输入数据(一个partition的全部数据)后,首先须要对数据进行分组,其分组的默认原则是key相同,而后对每一组kv数据调用一次reduce()方法,而且将这一组kv中的第一个kvkey做为参数传给reducekey,将这一组数据的value的迭代器传给reduce()values参数。

利用上述这个机制,咱们能够实现一个高效的分组取最大值的逻辑。

自定义一个bean对象用来封装咱们的数据,而后改写其compareTo方法产生倒序排序的效果。而后自定义一个Groupingcomparator,将bean对象的分组逻辑改为按照咱们的业务分组id来分组(好比订单号)。这样,咱们要取的最大值就是reduce()方法中传进来key

7)逻辑处理接口:Reducer

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

8)输出数据接口:OutputFormat

默认实现类是TextOutputFormat,功能逻辑是:将每个KV对向目标文本文件中输出为一行。

 SequenceFileOutputFormat将它的输出写为一个顺序文件。若是输出须要做为后续 MapReduce任务的输入,这即是一种好的输出格式,由于它的格式紧凑,很容易被压缩。

用户还能够自定义OutputFormat。

Hadoop数据压缩

4.1 概述

压缩技术可以有效减小底层存储系统(HDFS读写字节数。压缩提升了网络带宽和磁盘空间的效率。在Hadoop下,尤为是数据规模很大和工做负载密集的状况下,使用数据压缩显得很是重要。在这种状况下,I/O操做和网络数据传输要花大量的时间。还有,ShuffleMerge过程一样也面临着巨大的I/O压力

鉴于磁盘I/O网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O网络传输很是有帮助。不过尽管压缩与解压操做CPU开销不高,其性能的提高和资源的节省并不是没有代价。

若是磁盘I/O和网络带宽影响了MapReduce做业性能,在任意MapReduce阶段启用压缩均可以改善端到端处理时间并减小I/O和网络流量。

压缩Mapreduce的一种优化策略:经过压缩编码对Mapper或者Reducer的输出进行压缩,以减小磁盘IO提升MR程序运行速度(但相应增长了cpu运算负担)。

注意压缩特性运用得当能提升性能,但运用不当也可能下降性能。

基本原则:

1)运算密集型的job,少用压缩

2IO密集型的job,多用压缩

4.2 MR支持的压缩编码

压缩格式

hadoop自带?

算法

文件扩展名

是否可切分

换成压缩格式后,原来的程序是否须要修改

DEFAULT

是,直接使用

DEFAULT

.deflate

和文本处理同样,不须要修改

Gzip

是,直接使用

DEFAULT

.gz

和文本处理同样,不须要修改

bzip2

是,直接使用

bzip2

.bz2

和文本处理同样,不须要修改

LZO

否,须要安装

LZO

.lzo

须要建索引,还须要指定输入格式

Snappy

否,须要安装

Snappy

.snappy

和文本处理同样,不须要修改

为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,以下表所示

压缩格式

对应的编码/解码器

DEFLATE

org.apache.hadoop.io.compress.DefaultCodec

gzip

org.apache.hadoop.io.compress.GzipCodec

bzip2

org.apache.hadoop.io.compress.BZip2Codec

LZO

com.hadoop.compression.lzo.LzopCodec

Snappy

org.apache.hadoop.io.compress.SnappyCodec

压缩性能的比较

压缩算法

原始文件大小

压缩文件大小

压缩速度

解压速度

gzip

8.3GB

1.8GB

17.5MB/s

58MB/s

bzip2

8.3GB

1.1GB

2.4MB/s

9.5MB/s

LZO

8.3GB

2.9GB

49.3MB/s

74.6MB/s

http://google.github.io/snappy/

On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.

4.3 压缩方式选择

4.3.1 Gzip压缩

优势:压缩率比较高,并且压缩/解压速度也比较快;hadoop自己支持,在应用中处理gzip格式的文件就和直接处理文本同样;大部分linux系统都自带gzip命令,使用方便。

缺点:不支持split

应用场景:当每一个文件压缩以后在130M之内的(1个块大小内),均可以考虑用gzip压缩格式。例如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候经过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序彻底和文本处理同样,压缩以后原来的程序不须要作任何修改。

4.3.2 Bzip2压缩

优势:支持split;具备很高的压缩率,比gzip压缩率都高;hadoop自己支持,但不支持native(javac互操做的API接口);在linux系统下自带bzip2命令,使用方便。

缺点:压缩/解压速度慢;不支持native

应用场景:适合对速度要求不高,但须要较高的压缩率的时候,能够做为mapreduce做业的输出格式;或者输出以后的数据比较大,处理以后的数据须要压缩存档减小磁盘空间而且之后数据用得比较少的状况;或者对单个很大的文本文件想压缩减小存储空间,同时又须要支持split,并且兼容以前的应用程序(即应用程序不须要修改)的状况

4.3.3 Lzo压缩

优势:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;能够在linux系统下安装lzop命令,使用方便。

缺点:压缩率比gzip要低一些;hadoop自己不支持,须要安装;在应用中对lzo格式的文件须要作一些特殊处理(为了支持split须要建索引,还须要指定inputformatlzo格式)。

应用场景:一个很大的文本文件,压缩以后还大于200M以上的能够考虑,并且单个文件越大,lzo优势越越明显。

4.3.4 Snappy压缩

优势:高速压缩速度和合理的压缩率。

缺点:不支持split;压缩率比gzip要低;hadoop自己不支持,须要安装; 

应用场景:当Mapreduce做业的Map输出的数据比较大的时候,做为MapReduce的中间数据的压缩格式;或者做为一个Mapreduce做业的输出和另一个Mapreduce做业的输入。

4.4 压缩位置选择

压缩能够在MapReduce做用的任意阶段启用。

4.5 压缩配置参数

要在Hadoop中启用压缩,能够配置以下参数:

参数

默认值

阶段

建议

io.compression.codecs   

(在core-site.xml中配置)

org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec

 

输入压缩

Hadoop使用文件扩展名判断是否支持某种编解码器

mapreduce.map.output.compress(在mapred-site.xml中配置)

false

mapper输出

这个参数设为true启用压缩

mapreduce.map.output.compress.codec(在mapred-site.xml中配置)

org.apache.hadoop.io.compress.DefaultCodec

mapper输出

使用LZOsnappy编解码器在此阶段压缩数据

mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置)

false

reducer输出

这个参数设为true启用压缩

mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置)

org.apache.hadoop.io.compress. DefaultCodec

reducer输出

使用标准工具或者编解码器,如gzipbzip2

mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置)

RECORD

reducer输出

SequenceFile输出使用的压缩类型:NONEBLOCK

4.6 压缩实战

压缩案例详见7.10 压缩/解压缩

Yarn

5.1 Hadoop1.xHadoop2.x架构区别

Hadoop1.x时代,Hadoop中的MapReduce同时处理业务逻辑运算和资源的调度,耦合性较大。

Hadoop2.x时代增长Yarn。Yarn只负责资源的调度,MapReduce只负责运算

5.2 Yarn概述

Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,至关于一个分布式的操做系统平台,而MapReduce等运算程序则至关于运行于操做系统之上的应用程序。

5.3 Yarn基本架构

  YARN主要由ResourceManagerNodeManagerApplicationMasterContainer等组件构成。

 

5.4 Yarn工做机制

1)Yarn运行机制

 

2)工做机制详解

(0)Mr程序提交到客户端所在的节点。

(1)YarnrunnerResourcemanager申请一个Application

(2rm将该应用程序的资源路径返回给yarnrunner。

(3)该程序将运行所需资源提交到HDFS

(4)程序资源提交完毕后,申请运行mrAppMaster。

(5RM将用户的请求初始化成一个task。

(6)其中一个NodeManager领取task任务。

(7)该NodeManager建立容器Container并产生MRAppmaster。

(8ContainerHDFS上拷贝资源到本地

(9)MRAppmaster向RM 申请运行maptask资源。

10RM运行maptask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务建立容器。

11MR向两个接收到任务的NodeManager发送程序启动脚本这两个NodeManager分别启动maptask,maptask对数据分区排序。

12MrAppMaster等待全部maptask运行完毕后,向RM申请容器,运行reduce task

13reduce taskmaptask获取相应分区的数据。

14)程序运行完毕后,MR会向RM申请注销本身。

5.5 做业提交全过程

做业提交全过程详解

1做业提交

0client调用job.waitForCompletion方法,向整个集群提交MapReduce做业。

1步:clientRM申请一个做业id

2步:RMclient返回该job资源的提交路径和做业id

3client提交jar包、切片信息和配置文件到指定的资源提交路径。

4步:client提交完资源后,向RM申请运行MrAppMaster

2做业初始化

5步:RM收到client的请求后,将job添加到容量调度器中。

6一个空闲的NM领取到该job

7步:NM建立Container并产生MRAppmaster

8:下载client提交的资源到本地。

3任务分配

9MrAppMasterRM申请运行多个maptask任务资源。

10RM运行maptask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务建立容器。

4任务运行

11MR向两个接收到任务的NodeManager发送程序启动脚本这两个NodeManager分别启动maptask,maptask对数据分区排序。

12MrAppMaster等待全部maptask运行完毕后,向RM申请容器,运行reduce task

13reduce taskmaptask获取相应分区的数据。

14程序运行完毕后,MR会向RM申请注销本身。

5进度和状态更新

YARN中的任务将其进度和状态(包括counter)返回给应用管理器, 客户端每秒(经过mapreduce.client.progressmonitor.pollinterval设置)向应用管理器请求进度更新, 展现给用户。

6做业完成

除了向应用管理器请求做业进度外, 客户端每5分钟都会经过调用waitForCompletion()来检查做业是否完成时间间隔能够经过mapreduce.client.completion.pollinterval来设置做业完成以后, 应用管理器和container会清理工做状态做业的信息会被做业历史服务器存储以备以后用户核查

5.6 资源调度器

目前,Hadoop做业调度器主要有三种:FIFOCapacity SchedulerFair SchedulerHadoop2.7.2默认的资源调度器是Capacity Scheduler。

具体设置详见:yarn-default.xml文件

<property>

    <description>The class to use as the resource scheduler.</description>

    <name>yarn.resourcemanager.scheduler.class</name>

<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>

</property>

1) 先进先出调度器(FIFO

 

优势:调度算法简单,JobTracker工做负担轻。

缺点:忽略了不一样做业的需求差别。例如若是相似对海量数据进行统计分析的做业长期占据计算资源,那么在其后提交的交互型做业有可能迟迟得不处处理,从而影响到用户的体验。

2) 容量调度器(Capacity Scheduler===>Yahoo开发

 

1.多队列支持,每一个队列采用FIFO

2.为了防止同一个用户的做业独占队列中的资源,该调度器会对同一个用户提交多的做业所占资源量进行限定

3.首先,计算每一个队列中正在运行的任务数与其应该分得的计算资源之间的比值,选择一个该比值最小的队列

4.其次,根据做业的优先级和提交时间顺序,同时考虑用户资源量限制和内存限制对队列内任务排序

5.三个队列同时按照任务的前后顺序依次执行,好比,job1job21job31分别排在队列最前面,是最早运行,也是同时运行

 

该调度默认状况下不支持优先级,可是能够在配置文件中开启此选项,若是支持优先级,调度算法就是带有优先级的FIFO

不支持优先级抢占,一旦一个做业开始执行,在执行完以前它的资源不会被高优先级做业所抢占。

对队列中同一用户提交的做业可以得到的资源百分比进行了限制以使同属于一用户的做业不能出现独占资源的状况。

 

3)公平调度器(Fair Scheduler===>Facebook开发

 

1.支持多队列多用户,每一个队列中的资源量能够配置,同一个队列中的做业公平共享队列中全部资源

2.好比有三个队列ABC.每一个队列中的job按照优先级分配资源,优先级越高分配的资源越多,可是每一个job都分配到资源以确保公平。在资源有限的状况下,每一个job理想状况下,得到的计算资源与实际得到的计算资源存在一种差距,这个差距叫作缺额。同一个队列,job的资源缺额越大,越先得到的资源优先执行,做业是按照缺额的高低来前后执行的,并且能够看到上图有多个做业同时运行

 

5.7 任务推测执行

推测执行(Speculative Execution)是指在集群环境下运行MapReduce,多是程序Bug,负载不均或者其余的一些问题,致使在一个JOB下的多个TASK速度不一致,好比有的任务已经完成,可是有些任务可能只跑了10%,根据木桶原理,这些任务将成为整个JOB的短板,若是集群启动了推测执行,这时为了最大限度的提升短板,Hadoop会为该task启动备份任务,让speculative task与原始task同时处理一份数据,哪一个先运行完,则将谁的结果做为最终结果,而且在运行完成后Kill掉另一个任务。

 

1)做业完成时间取决于最慢的任务完成时间

一个做业由若干个Map任务和Reduce任务构成。因硬件老化、软件Bug等,某些任务可能运行很是慢。

典型案例:系统中有99%Map任务都完成了,只有少数几个Map总是进度很慢,完不成,怎么办?

2)推测执行机制:

发现拖后腿的任务,好比某个任务运行速度远慢于任务平均速度。为拖后腿任务启动一个备份任务,同时运行。谁先运行完,则采用谁的结果。

3)执行推测任务的前提条件

1每一个task只能有一个备份任务

2当前job已完成的task必须不小于0.055%

3开启推测执行参数设置,mapred-site.xml文件中默认是打开的。

<property>

  <name>mapreduce.map.speculative</name>

  <value>true</value>

  <description>If true, then multiple instances of some map tasks                may be executed in parallel.</description>

</property>

 

<property>

  <name>mapreduce.reduce.speculative</name>

  <value>true</value>

  <description>If true, then multiple instances of some reduce tasks

               may be executed in parallel.</description>

</property>

4)不能启用推测执行机制状况

   1)任务间存在严重的负载倾斜;

   2)特殊任务,好比任务向数据库中写数据。

Hadoop企业优化

6.1 MapReduce 跑的慢的缘由

Mapreduce 程序效率的瓶颈在于两点:

1)计算机性能

CPU、内存、磁盘健康、网络

2I/O 操做优化

1)数据倾斜

2mapreduce数设置不合理

3map运行时间太长,致使reduce等待太久

4)小文件过多

5大量的不可分块的超大文件

6spill次数过多

7merge次数过多等。

6.2 MapReduce优化方法

MapReduce优化方法主要从六个方面考虑:数据输入、Map阶段Reduce阶段、IO传输、数据倾斜问题和经常使用的调优参数。

6.2.1 数据输入

1)合并小文件:在执行mr任务前将小文件进行合并,大量的小文件会产生大量的map任务,增大map任务装载次数,而任务的装载比较耗时,从而致使mr运行较慢。

2)采用CombineTextInputFormat来做为输入,解决输入端大量小文件场景。

6.2.2 Map阶段

1)减小溢写spill次数:经过调整io.sort.mbsort.spill.percent参数值,增大触发spill的内存上限,减小spill次数,从而减小磁盘IO

2)减小合并merge次数:经过调整io.sort.factor参数,增大merge的文件数目,减小merge的次数,从而缩短mr处理时间。

3)在map以后,不影响业务逻辑前提下,先进行combine处理,减小 I/O

6.2.3 Reduce阶段

1)合理设置mapreduce:两个都不能设置太少,也不能设置太多。太少,会致使task等待,延长处理时间;太多,会致使 mapreduce任务间竞争资源,形成处理超时等错误。

2)设置mapreduce共存:调整slowstart.completedmaps参数,使map运行到必定程度后,reduce也开始运行,减小reduce的等待时间。

3规避使用reduce由于reduce在用于链接数据集的时候将会产生大量的网络消耗。

4)合理设置reduce端的buffer默认状况下,数据达到一个阈值的时候,buffer中的数据就会写入磁盘,而后reduce会从磁盘中得到全部的数据。也就是说,bufferreduce是没有直接关联的,中间多个一个写磁盘->读磁盘的过程,既然有这个弊端,那么就能够经过参数来配置,使得buffer中的一部分数据能够直接输送到reduce,从而减小IO开销:mapred.job.reduce.input.buffer.percent,默认为0.0。当值大于0的时候,会保留指定比例的内存读buffer中的数据直接拿给reduce使用。这样一来,设置buffer须要内存,读取数据须要内存,reduce计算也要内存,因此要根据做业的运行状况进行调整。

6.2.4 IO传输

1采用数据压缩的方式,减小网络IO的时间。安装SnappyLZO压缩编码器

2使用SequenceFile二进制文件

6.2.5 数据倾斜问题

1)数据倾斜现象

数据频率倾斜——某一个区域的数据量要远远大于其余区域。

数据大小倾斜——部分记录的大小远远大于平均值。

2)如何收集倾斜数据

reduce方法中加入记录map输出键的详细状况的功能

public static final String MAX_VALUES = "skew.maxvalues";

private int maxValueThreshold;

 

@Override

public void configure(JobConf job) {

     maxValueThreshold = job.getInt(MAX_VALUES, 100);

}

@Override

public void reduce(Text key, Iterator<Text> values,

                     OutputCollector<Text, Text> output,

                     Reporter reporter) throws IOException {

     int i = 0;

     while (values.hasNext()) {

         values.next();

         i++;

     }

 

     if (++i > maxValueThreshold) {

         log.info("Received " + i + " values for key " + key);

     }

}

3)减小数据倾斜方法

方法1:抽样和范围分区

能够经过对原始数据进行抽样获得的结果集来预设分区边界值。

方法2:自定义分区

基于输出键的背景知识进行自定义分区。例如,若是map输出键的单词来源于一本书。且其中某几个专业词汇较多。那么就能够自定义分区将这这些专业词汇发送给固定的一部分reduce实例。而将其余的都发送给剩余的reduce实例。

方法3Combine

使用Combine能够大量地减少数据倾斜。在可能的状况下,combine的目的就是聚合并精简数据。

方法4采用Map Join尽可能避免Reduce Join

6.2.6 经常使用的调优参数

1资源相关参数

(1)如下参数是在用户本身的mr应用程序中配置就能够生效(mapred-default.xml)

配置参数

参数说明

mapreduce.map.memory.mb

一个Map Task可以使用的资源上限(单位:MB),默认为1024。若是Map Task实际使用的资源量超过该值,则会被强制杀死。

mapreduce.reduce.memory.mb

一个Reduce Task可以使用的资源上限(单位:MB),默认为1024。若是Reduce Task实际使用的资源量超过该值,则会被强制杀死。

mapreduce.map.cpu.vcores

每一个Map task可以使用的最多cpu core数目,默认值: 1

mapreduce.reduce.cpu.vcores

每一个Reduce task可以使用的最多cpu core数目,默认值: 1

mapreduce.reduce.shuffle.parallelcopies

每一个reducemap中拿数据的并行数。默认值是5

mapreduce.reduce.shuffle.merge.percent

buffer中的数据达到多少比例开始写入磁盘。默认值0.66

mapreduce.reduce.shuffle.input.buffer.percent

buffer大小占reduce可用内存的比例。默认值0.7

mapreduce.reduce.input.buffer.percent

指定多少比例的内存用来存放buffer中的数据,默认值是0.0

(2)应该在yarn启动以前就配置在服务器的配置文件中才能生效(yarn-default.xml)

配置参数

参数说明

yarn.scheduler.minimum-allocation-mb   1024

给应用程序container分配的最小内存

yarn.scheduler.maximum-allocation-mb   8192

给应用程序container分配的最大内存

yarn.scheduler.minimum-allocation-vcores 1

每一个container申请的最小CPU核数

yarn.scheduler.maximum-allocation-vcores 32

每一个container申请的最大CPU核数

yarn.nodemanager.resource.memory-mb   8192

给containers分配的最大物理内存

(3shuffle性能优化的关键参数,应在yarn启动以前就配置好(mapred-default.xml)

配置参数

参数说明

mapreduce.task.io.sort.mb   100

shuffle的环形缓冲区大小,默认100m

mapreduce.map.sort.spill.percent   0.8

环形缓冲区溢出的阈值,默认80%

2)容错相关参数(mapreduce性能优化)

配置参数

参数说明

mapreduce.map.maxattempts

每一个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4

mapreduce.reduce.maxattempts

每一个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4

mapreduce.task.timeout

Task超时时间,常常须要设置的一个参数,该参数表达的意思为:若是一个task在必定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该task处于block状态,多是卡住了,也许永远会卡主,为了防止由于用户程序永远block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是600000。若是你的程序对每条输入数据的处理时间过长(好比会访问数据库,经过网络拉取数据等),建议将该参数调大,该参数太小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。

6.3 HDFS文件优化方法

6.3.1 HDFS小文件弊端

HDFS上每一个文件都要在namenode上创建一个索引,这个索引的大小约为150byte,这样当小文件比较多的时候,就会产生不少的索引文件,一方面会大量占用namenode的内存空间,另外一方面就是索引文件过大是的索引速度变慢。

6.3.2 解决方案

1Hadoop Archive:

 是一个高效地将小文件放入HDFS块中的文件存档工具,它可以将多个小文件打包成一个HAR文件,这样减小了namenode的内存使用。

2Sequence file

 sequence file由一系列的二进制key/value组成,若是key文件名,value为文件内容,则能够将大批小文件合并成一个大文件。

3CombineFileInputFormat

  CombineFileInputFormat是一种新的inputformat,用于将多个文件合并成一个单独的split,另外,它会考虑数据的存储位置。

4)开启JVM重用

对于大量小文件Job能够开启JVM重用减小45%运行时间

JVM重用理解:一个map运行一个jvm,重用的话,在一个mapjvm上运行完毕后,jvm继续运行其余map

具体设置mapreduce.job.jvm.numtasks值10-20之间

MapReduce实战

7.1 WordCount案例

7.1.1 需求1统计一堆文件中单词出现的个数

0)需求:在一堆给定的文本文件中统计输出每个单词出现的总次数

1)数据准备

 

2)分析

按照mapreduce编程规范,分别编写MapperReducerDriver

 

 

3)编写程序

1)编写mapper

package com.itstar.mapreduce;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{

 

Text k = new Text();

IntWritable v = new IntWritable(1);

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

 

// 1 获取一行

String line = value.toString();

 

// 2 切割

String[] words = line.split(" ");

 

// 3 输出

for (String word : words) {

 

k.set(word);

context.write(k, v);

}

}

}

2)编写reducer

package com.itstar.mapreduce.wordcount;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{

 

@Override

protected void reduce(Text key, Iterable<IntWritable> value,

Context context) throws IOException, InterruptedException {

 

// 1 累加求和

int sum = 0;

for (IntWritable count : value) {

sum += count.get();

}

 

// 2 输出

context.write(key, new IntWritable(sum));

}

}

3)编写驱动类

package com.itstar.mapreduce.wordcount;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

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 WordcountDriver {

 

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

 

        String[] args=new String{“输入路径”,”输出路径”};

 

// 1 获取配置信息

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 2 设置jar加载路径

job.setJarByClass(WordcountDriver.class);

 

// 3 设置mapReduce

job.setMapperClass(WordcountMapper.class);

job.setReducerClass(WordcountReducer.class);

 

// 4 设置map输出

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

 

// 5 设置Reduce输出

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

 

// 6 设置输入和输出路径

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

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

 

// 7 提交

boolean result = job.waitForCompletion(true);

 

System.exit(result ? 0 : 1);

}

}

4)集群上测试

(1)将程序打成jar包,而后拷贝hadoop集群中

(2)启动hadoop集群

(3)执行wordcount程序

[itstar@hadoop102 software]$ hadoop jar  wc.jar com.itstar.wordcount.WordcountDriver /user/itstar/input /user/itstar/output1

5)本地测试

1)在windows环境上配置HADOOP_HOME环境变量。

2)在idea上运行程序

3)注意:若是idea打印不出日志,在控制台上只显示

1.log4j:WARN No appenders could be found for logger (org.apache.hadoop.util.Shell).  

2.log4j:WARN Please initialize the log4j system properly.  

3.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

须要在项目的src目录下,新建一个文件,命名为“log4j.properties”在文件中填入

log4j.rootLogger=INFO, stdout  

log4j.appender.stdout=org.apache.log4j.ConsoleAppender  

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n  

log4j.appender.logfile=org.apache.log4j.FileAppender  

log4j.appender.logfile.File=target/spring.log  

log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  

log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n  

7.1.2 需求2:把单词按照ASCII码奇偶分区(Partitioner

0分析

1自定义分区

package com.itstar.mapreduce.wordcount;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Partitioner;

 

public class WordCountPartitioner extends Partitioner<Text, IntWritable>{

 

@Override

public int getPartition(Text key, IntWritable value, int numPartitions) {

 

// 1 获取单词key  

String firWord = key.toString().substring(0, 1);

//String -> int

        int result = Integer.valueOf(firWord);

 

// 2 根据奇数偶数分区

if (result % 2 == 0) {

return 0;

}else {

return 1;

}

}

}

2)在驱动中配置加载分区,设置reducetask个数

job.setPartitionerClass(WordCountPartitioner.class);

job.setNumReduceTasks(2);

7.1.3 需求3:对每个maptask的输出局部汇总(Combiner

0)需求:统计过程对每个maptask的输出进行局部汇总,以减少网络传输量即采用Combiner功能。

 

1)数据准备

 

方案一

1增长一个WordcountCombiner类继承Reducer

package com.itstar.mr.combiner;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{

 

@Override

protected void reduce(Text key, Iterable<IntWritable> values,

Context context) throws IOException, InterruptedException {

        // 1 汇总

int count = 0;

for(IntWritable v :values){

count += v.get();

}

// 2 写出

context.write(key, new IntWritable(count));

}

}

2WordcountDriver驱动类中指定combiner

// 9 指定须要使用combiner,以及用哪一个类做为combiner的逻辑

job.setCombinerClass(WordcountCombiner.class);

方案

1)将WordcountReducer做为combinerWordcountDriver驱动类中指定

// 指定须要使用combiner,以及用哪一个类做为combiner的逻辑

job.setCombinerClass(WordcountReducer.class);

 

运行程序

 

 

7.1.4 需求4:大量小文件切片优化(CombineTextInputFormat

0在分布式的架构中,分布式文件系统HDFS,和分布式运算程序编程框架mapreduce。

HDFS:不怕大文件,怕不少小文件

mapreduce :怕数据倾斜

那么mapreduce是若是解决多个小文件的问题呢?

mapreduce关于大量小文件的优化策略

1) 默认状况下,TextInputFormat对任务的切片机制是按照文件规划切片,无论有多少个小文件,都会是单独的切片,都会交给一个maptask,这样,若是有大量的小文件

就会产生大量的maptask,处理效率极端底下

2)优化策略

最好的方法:在数据处理的最前端(预处理、采集),就将小文件合并成大文件,在上传到HDFS作后续的分析

补救措施:若是已是大量的小文件在HDFS中了,可使用另外一种inputformat来作切片(CombineFileInputformat),它的切片逻辑跟TextInputformat

注:combineTextInputFormat是CombineFileInputformat的子类

不一样:

它能够将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就能够交给一个maptask了

//若是不设置InputFormat,它默认的用的是TextInputFormat.class

/*CombineTextInputFormat为系统自带的组件类

 * setMinInputSplitSize 中的2048是表示n个小文件之和不能大于2048

 * setMaxInputSplitSize 中的4096是     当知足setMinInputSplitSize中的2048状况下  在知足n+1个小文件之和不能大于4096

 */

job.setInputFormatClass(CombineTextInputFormat.class);

CombineTextInputFormat.setMinInputSplitSize(job, 2048);

CombineTextInputFormat.setMaxInputSplitSize(job, 4096);

1)输入数据:准备5小文件

2)实现过程

1不作任何处理,运行需求1wordcount程序,观察切片个数为5

 

2WordcountDriver中增长以下代码运行程序,并观察运行的切片个数为1

// 若是不设置InputFormat,它默认用的是TextInputFormat.class

job.setInputFormatClass(CombineTextInputFormat.class);

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

 

注:

文件大小 <  MinSplit  < MaxSplit

number of splits:1

 

 

MinSplit  <  文件大小 < MaxSplit

number of splits:1

 

 

MaxSplit < 文件大小 < 2*MaxSplit

number of splits:2

 

 

2 * MaxSplit < 文件大小

number of splits:3

 

 

测试大小

最大MB

文件大小和最大值倍数

Splits

4.97MB

3MB

1.65

2

4.1MB

3MB

1.36

1

6.51

3MB

2.17

3

 

 

7.2 流量汇总案例

7.2.1 需求1:统计手机号耗费的总上行流量、下行流量、总流量(序列化)

1需求:

统计每个手机号耗费的总上行流量、下行流量、总流量

2)数据准备

 

输入数据格式:

数据格式:时间戳、电话号码、基站的物理地址、访问网址的ip、网站域名、数据包、接包数、上行/传流量、下行/载流量、响应码

 

输出数据格式

1356·0436666 1116       954 2070

手机号码 上行流量        下行流量 总流量

3)分析

基本思路:

Map阶段:

1)读取一行数据,切分字段

2)抽取手机号、上行流量、下行流量

3)以手机key,bean对象为value输出context.write(手机号,bean);

Reduce阶段:

1)累加上行流量和下行流量获得总流量。

(2)实现自定义的bean来封装流量信息,并将bean做为map输出的key来传输

(3MR程序在处理数据的过程当中会对数据排序(map输出的kv对传输到reduce以前,会排序),排序的依据是map输出的key

因此,咱们若是要实现本身须要的排序规则,则能够考虑将排序因素放到key中,key实现接口:WritableComparable

而后重写keycompareTo方法。

4)编写mapreduce程序

1)编写流量统计的bean对象

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import org.apache.hadoop.io.Writable;

 

// 1 实现writable接口

public class FlowBean implements Writable{

 

private long upFlow ;

private long downFlow;

private long sumFlow;

 

//2  反序列化时,须要反射调用空参构造函数,因此必须有

public FlowBean() {

super();

}

 

public FlowBean(long upFlow, long downFlow) {

super();

this.upFlow = upFlow;

this.downFlow = downFlow;

this.sumFlow = upFlow + downFlow;

}

 

//3  写序列化方法

@Override

public void write(DataOutput out) throws IOException {

out.writeLong(upFlow);

out.writeLong(downFlow);

out.writeLong(sumFlow);

}

 

//4 反序列化方法

//5 反序列化方法读顺序必须和写序列化方法的写顺序必须一致

@Override

public void readFields(DataInput in) throws IOException {

this.upFlow  = in.readLong();

this.downFlow = in.readLong();

this.sumFlow = in.readLong();

}

 

// 6 编写toString方法,方便后续打印到文本

@Override

public String toString() {

return upFlow + "\t" + downFlow + "\t" + sumFlow;

}

 

public long getUpFlow() {

return upFlow;

}

 

public void setUpFlow(long upFlow) {

this.upFlow = upFlow;

}

 

public long getDownFlow() {

return downFlow;

}

 

public void setDownFlow(long downFlow) {

this.downFlow = downFlow;

}

 

public long getSumFlow() {

return sumFlow;

}

 

public void setSumFlow(long sumFlow) {

this.sumFlow = sumFlow;

}

 

}

2)编写mapper

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class FlowCountMapper extends Mapper<LongWritable, Text, Text, FlowBean>{

 

FlowBean v = new FlowBean();

Text k = new Text();

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

 

// 1 获取一行

String line = value.toString();

 

// 2 切割字段

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

 

// 3 封装对象

// 取出手机号码

String phoneNum = fields[1];

// 取出上行流量和下行流量

long upFlow = Long.parseLong(fields[fields.length - 3]);

long downFlow = Long.parseLong(fields[fields.length - 2]);

 

v.set(downFlow, upFlow);

 

// 4 写出

context.write(new Text(phoneNum), new FlowBean(upFlow, downFlow));

}

}

(3)编写reducer

import java.io.IOException;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

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

 

@Override

protected void reduce(Text key, Iterable<FlowBean> values, Context context)

throws IOException, InterruptedException {

 

long sum_upFlow = 0;

long sum_downFlow = 0;

 

// 1 遍历所用bean,将其中的上行流量,下行流量分别累加

for (FlowBean flowBean : values) {

sum_upFlow += flowBean.getSumFlow();

sum_downFlow += flowBean.getDownFlow();

}

 

// 2 封装对象

FlowBean resultBean = new FlowBean(sum_upFlow, sum_downFlow);

 

// 3 写出

context.write(key, resultBean);

}

}

(4)编写驱动

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

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;

 

public class FlowsumDriver {

 

public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {

 

// 1 获取配置信息,或者job对象实例

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 6 指定本程序的jar包所在的本地路径

job.setJarByClass(FlowsumDriver.class);

 

// 2 指定本业务job要使用的mapper/Reducer业务类

job.setMapperClass(FlowCountMapper.class);

job.setReducerClass(FlowCountReducer.class);

 

// 3 指定mapper输出数据的kv类型

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(FlowBean.class);

 

// 4 指定最终输出的数据的kv类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(FlowBean.class);

 

// 5 指定job的输入原始文件所在目录

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

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

 

// 7 job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

7.2.2 需求2:将统计结果按照手机归属地不一样省份输出到不一样文件中(Partitioner

0)需求将统计结果按照手机归属地不一样省份输出到不一样文件中(分区)

1数据准备

 

2分析

1Mapreduce中会将map输出的kv对,按照相同key分组,而后分发给不一样的reducetask。默认的分发规则为:根据keyhashcode%reducetask数来分发

2)若是要按照咱们本身的需求进行分组,则须要改写数据分发(分组)组件Partitioner

自定义一个CustomPartitioner继承抽象类:Partitioner

3)在job驱动中,设置自定义partitionerjob.setPartitionerClass(CustomPartitioner.class)

3)在需求1基础上,增长一个分区类

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Partitioner;

 

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);

//注:若是设置的分区数小于下面的分区数,如3、则最后一个分区混数据分区

        //注:如何设置的分区数大于下面的分区数,如5,则报错

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;

}

}

2)在驱动函数中增长自定义数据分区设置和reduce task设置

package com.itstar.mapreduce.flowsum;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

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;

 

public class FlowsumDriver {

 

public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {

 

// 1 获取配置信息,或者job对象实例

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 6 指定本程序的jar包所在的本地路径

job.setJarByClass(FlowsumDriver.class);

 

// 2 指定本业务job要使用的mapper/Reducer业务类

job.setMapperClass(FlowCountMapper.class);

job.setReducerClass(FlowCountReducer.class);

 

// 3 指定mapper输出数据的kv类型

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(FlowBean.class);

 

// 4 指定最终输出的数据的kv类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(FlowBean.class);

 

// 8 指定自定义数据分区

job.setPartitionerClass(ProvincePartitioner.class);

// 9 同时指定相应数量的reduce task

job.setNumReduceTasks(5);

 

// 5 指定job的输入原始文件所在目录

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

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

 

// 7 job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

7.2.3 需求3:将统计结果按照总流量倒序排序(全排序)

0)需求

根据需求1产生的结果再次对总流量进行排序。

1)数据准备

 

2)分析

1)把程序分两步走,第一步正常统计总流量第二步再把结果进行排序

2context.write(总流量,手机号)

3FlowBean实现WritableComparable接口重写compareTo方法

@Override

public int compareTo(FlowBean o) {

// 倒序排列,从大到小

return this.sumFlow > o.getSumFlow() ? -1 : 1;

}

3代码实现

1FlowBean对象在在需求1基础上增长了比较功能

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import org.apache.hadoop.io.WritableComparable;

 

public class FlowBean implements WritableComparable<FlowBean> {

 

private long upFlow;

private long downFlow;

private long sumFlow;

 

// 反序列化时,须要反射调用空参构造函数,因此必须有

public FlowBean() {

super();

}

 

public FlowBean(long upFlow, long downFlow) {

super();

this.upFlow = upFlow;

this.downFlow = downFlow;

this.sumFlow = upFlow + downFlow;

}

 

public void set(long upFlow, long downFlow) {

this.upFlow = upFlow;

this.downFlow = downFlow;

this.sumFlow = upFlow + downFlow;

}

 

public long getSumFlow() {

return sumFlow;

}

 

public void setSumFlow(long sumFlow) {

this.sumFlow = sumFlow;

}

 

public long getUpFlow() {

return upFlow;

}

 

public void setUpFlow(long upFlow) {

this.upFlow = upFlow;

}

 

public long getDownFlow() {

return downFlow;

}

 

public void setDownFlow(long downFlow) {

this.downFlow = downFlow;

}

 

/**

 * 序列化方法

 * @param out

 * @throws IOException

 */

@Override

public void write(DataOutput out) throws IOException {

out.writeLong(upFlow);

out.writeLong(downFlow);

out.writeLong(sumFlow);

}

 

/**

 * 反序列化方法 注意反序列化的顺序和序列化的顺序彻底一致

 * @param in

 * @throws IOException

 */

@Override

public void readFields(DataInput in) throws IOException {

upFlow = in.readLong();

downFlow = in.readLong();

sumFlow = in.readLong();

}

 

@Override

public String toString() {

return upFlow + "\t" + downFlow + "\t" + sumFlow;

}

 

@Override

public int compareTo(FlowBean o) {

// 倒序排列,从大到小

return this.sumFlow > o.getSumFlow() ? -1 : 1;

}

}

2)编写mapper

package com.itstar.mapreduce.sort;

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class FlowCountSortMapper extends Mapper<LongWritable, Text, FlowBean, Text>{

FlowBean k= new FlowBean();

Text v = new Text();

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

 

// 1 获取一行

String line = value.toString();

 

// 2 截取

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

 

// 3 封装对象

String phoneNbr = fields[0];

long upFlow = Long.parseLong(fields[1]);

long downFlow = Long.parseLong(fields[2]);

 

k.set(upFlow, downFlow);

v.set(phoneNbr);

 

// 4 输出

context.write(k, v);

}

}

(3)编写reducer

package com.itstar.mapreduce.sort;

import java.io.IOException;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

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

 

@Override

protected void reduce(FlowBean key, Iterable<Text> values, Context context)

throws IOException, InterruptedException {

 

// 循环输出,避免总流量相同状况

for (Text text : values) {

context.write(text, key);

}

}

}

(4)编写driver

package com.itstar.mapreduce.sort;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

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;

 

public class FlowCountSortDriver {

 

public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException {

 

// 1 获取配置信息,或者job对象实例

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 6 指定本程序的jar包所在的本地路径

job.setJarByClass(FlowCountSortDriver.class);

 

// 2 指定本业务job要使用的mapper/Reducer业务类

job.setMapperClass(FlowCountSortMapper.class);

job.setReducerClass(FlowCountSortReducer.class);

 

// 3 指定mapper输出数据的kv类型

job.setMapOutputKeyClass(FlowBean.class);

job.setMapOutputValueClass(Text.class);

 

// 4 指定最终输出的数据的kv类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(FlowBean.class);

 

// 5 指定job的输入原始文件所在目录

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

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

 

// 7 job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

 

7.2.4 需求4:不一样省份输出文件内部排序(部分排序)

1)需求

要求每一个省份手机号输出的文件中按照总流量内部排序。

2)分析:

基于需求3增长自定义分区类便可。

3)案例实操

1)增长自定义分区类

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Partitioner;

 

public class ProvincePartitioner extends Partitioner<FlowBean, Text> {

 

@Override

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

 

// 1 获取手机号码前三位

String preNum = value.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;

}

}

(2)在驱动类中添加分区类

// 加载自定义分区类

job.setPartitionerClass(FlowSortPartitioner.class);

// 设置Reducetask个数

job.setNumReduceTasks(5);

7.3 辅助排序和二次排序案例(GroupingComparator

1)需求

有以下订单数据

订单id

商品id

成交金额

0000001

Pdt_01

222.8

0000001

Pdt_06

25.8

0000002

Pdt_03

522.8

0000002

Pdt_04

122.4

0000002

Pdt_05

722.4

0000003

Pdt_01

222.8

0000003

Pdt_02

33.8

如今须要求出每个订单中最贵的商品。

2输入数据

 

输出数据预期:

    

3)分析

1)利用“订单id和成交金额”做为key,能够将map阶段读取到的全部订单数据按照id分区,按照金额排序,发送到reduce

2)在reduce端利用groupingcomparator将订单id相同的kv聚合成组,而后取第一个便是最大值。

 

4)代码实现

1)定义订单信息OrderBean

import lombok.AllArgsConstructor;

import lombok.Getter;

import lombok.NoArgsConstructor;

import lombok.Setter;

import org.apache.hadoop.io.WritableComparable;

 

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

 

@Getter

@Setter

@AllArgsConstructor

@NoArgsConstructor

public class OrderBean implements WritableComparable<OrderBean> {

    // 订单id

    private int order_id;

    // 价格

    private double price;

 

    @Override

    public String toString() {

        return order_id + "\t" + price;

    }

 

    @Override

    public int compareTo(OrderBean o) {

        int result;

 

        if (this.order_id > o.getOrder_id()) {

            result = 1;

        } else if (this.order_id < o.getOrder_id()) {

            result = -1;

        } else {

            result = this.price > o.getPrice() ? -1 : 1;

        }

        return result;

    }

 

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeInt(order_id);

        out.writeDouble(price);

    }

 

    @Override

    public void readFields(DataInput in) throws IOException {

        in.readInt();

        in.readDouble();

    }

}

2编写OrderSortMapper

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 OrderMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {

OrderBean k = new OrderBean();

 

@Override

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

 

// 1 获取一行

String line = value.toString();

 

// 2 截取

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

 

// 3 封装对象

k.setOrder_id(Integer.parseInt(fields[0]));

k.setPrice(Double.parseDouble(fields[2]));

 

// 4 写出

context.write(k, NullWritable.get());

}

}

3编写OrderSortPartitioner

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.mapreduce.Partitioner;

 

public class OrderPartitioner extends Partitioner<OrderBean, NullWritable> {

 

@Override

public int getPartition(OrderBean key, NullWritable value, int numReduceTasks) {

 

return (key.getOrder_id() & Integer.MAX_VALUE) % numReduceTasks;

}

}

4)编写OrderSortGroupingComparator

import org.apache.hadoop.io.WritableComparable;

import org.apache.hadoop.io.WritableComparator;

 

public class OrderGroupingComparator extends WritableComparator {

 

protected OrderGroupingComparator() {

super(OrderBean.class, true);

}

 

@SuppressWarnings("rawtypes")

@Override

public int compare(WritableComparable a, WritableComparable b) {

 

OrderBean aBean = (OrderBean) a;

OrderBean bBean = (OrderBean) b;

 

int result;

if (aBean.getOrder_id() > bBean.getOrder_id()) {

result = 1;

} else if (aBean.getOrder_id() < bBean.getOrder_id()) {

result = -1;

} else {

result = 0;

}

 

return result;

}

}

5编写OrderSortReducer

import java.io.IOException;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.mapreduce.Reducer;

 

public class OrderReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {

 

@Override

protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context)

throws IOException, InterruptedException {

 

context.write(key, NullWritable.get());

}

}

6编写OrderSortDriver

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.mapreduce.Job;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

 

public class OrderDriver {

 

public static void main(String[] args) throws Exception {

 

// 1 获取配置信息

Configuration conf = new Configuration();

Job job = Job.getInstance(conf);

 

// 2 设置jar包加载路径

job.setJarByClass(OrderDriver.class);

 

// 3 加载map/reduce

job.setMapperClass(OrderMapper.class);

job.setReducerClass(OrderReducer.class);

 

// 4 设置map输出数据keyvalue类型

job.setMapOutputKeyClass(OrderBean.class);

job.setMapOutputValueClass(NullWritable.class);

 

// 5 设置最终输出数据的keyvalue类型

job.setOutputKeyClass(OrderBean.class);

job.setOutputValueClass(NullWritable.class);

 

// 6 设置输入数据和输出数据路径

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

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

 

// 10 设置reduce端的分组

job.setGroupingComparatorClass(OrderGroupingComparator.class);

 

// 7 设置分区

job.setPartitionerClass(OrderPartitioner.class);

 

// 8 设置reduce个数

job.setNumReduceTasks(3);

 

// 9 提交

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

7.4 小文件处理案例(自定义InputFormat

1需求

不管hdfs仍是mapreduce,对于小文件都有损效率,实践中,又不免面临处理大量小文件的场景,此时,就须要有相应解决方案。多个小文件合并成一个文件SequenceFileSequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key文件内容为value

2输入数据

          

最终预期文件格式:

 

3分析

小文件的优化无非如下几种方式:

1)在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS

2业务处理以前HDFS上使用mapreduce程序对小文件进行合并

3)在mapreduce处理时,可采用CombineTextInputFormat提升效率

4具体实现

本节采用自定义InputFormat方式,处理输入小文件的问题。

1)自定义一个类继承FileInputFormat

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

3)在输出时使用SequenceFileOutPutFormat输出合并文件

5)程序实现:

1)自定义InputFromat

package com.itstar.mapreduce.inputformat;

import java.io.IOException;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.BytesWritable;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.mapreduce.InputSplit;

import org.apache.hadoop.mapreduce.JobContext;

import org.apache.hadoop.mapreduce.RecordReader;

import org.apache.hadoop.mapreduce.TaskAttemptContext;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

 

// 定义类继承FileInputFormat

public class WholeFileInputformat extends FileInputFormat<NullWritable, BytesWritable>{

 

@Override

protected boolean isSplitable(JobContext context, Path filename) {

return false;

}

 

@Override

public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)

throws IOException, InterruptedException {

 

WholeRecordReader recordReader = new WholeRecordReader();

recordReader.initialize(split, context);

 

return recordReader;

}

}

2自定义RecordReader

package com.itstar.mapreduce.inputformat;

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.NullWritable;

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;

 

public class WholeRecordReader extends RecordReader<NullWritable, BytesWritable>{

 

private Configuration configuration;

private FileSplit split;

 

private boolean processed = false;

private BytesWritable value = new BytesWritable();

 

@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 (!processed) {

// 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);

} catch (Exception e) {

 

}finally {

IOUtils.closeStream(fis);

}

 

processed = true;

 

return true;

}

 

return false;

}

 

@Override

public NullWritable getCurrentKey() throws IOException, InterruptedException {

return NullWritable.get();

}

 

@Override

public BytesWritable getCurrentValue() throws IOException, InterruptedException {

return value;

}

 

@Override

public float getProgress() throws IOException, InterruptedException {

return processed? 1:0;

}

 

@Override

public void close() throws IOException {

}

}

3SequenceFileMapper处理流程

package com.itstar.mapreduce.inputformat;

import java.io.IOException;

import org.apache.hadoop.io.BytesWritable;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

import org.apache.hadoop.mapreduce.lib.input.FileSplit;

 

public class SequenceFileMapper extends Mapper<NullWritable, BytesWritable, Text, BytesWritable>{

 

Text k = new Text();

 

@Override

protected void setup(Mapper<NullWritable, BytesWritable, Text, BytesWritable>.Context context)

throws IOException, InterruptedException {

// 1 获取文件切片信息

FileSplit inputSplit = (FileSplit) context.getInputSplit();

// 2 获取切片名称

String name = inputSplit.getPath().toString();

// 3 设置key输出

k.set(name);

}

 

@Override

protected void map(NullWritable key, BytesWritable value,

Context context)

throws IOException, InterruptedException {

 

context.write(k, value);

}

}

(4)SequenceFileReducer处理流程

package com.itstar.mapreduce.inputformat;

import java.io.IOException;

import org.apache.hadoop.io.BytesWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class SequenceFileReducer extends Reducer<Text, BytesWritable, Text, BytesWritable> {

 

@Override

protected void reduce(Text key, Iterable<BytesWritable> values, Context context)

throws IOException, InterruptedException {

 

context.write(key, values.iterator().next());

}

}

(5)SequenceFileDriver处理流程

package com.itstar.mapreduce.inputformat;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.BytesWritable;

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.mapreduce.lib.output.SequenceFileOutputFormat;

 

public class SequenceFileDriver {

 

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

 

args = new String[] { "e:/input/inputinputformat", "e:/output1" };

Configuration conf = new Configuration();

 

Job job = Job.getInstance(conf);

job.setJarByClass(SequenceFileDriver.class);

job.setMapperClass(SequenceFileMapper.class);

job.setReducerClass(SequenceFileReducer.class);

 

        // 设置输入的inputFormat

job.setInputFormatClass(WholeFileInputformat.class);

        // 设置输出的outputFormat

job.setOutputFormatClass(SequenceFileOutputFormat.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(BytesWritable.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(BytesWritable.class);

 

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

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

 

boolean result = job.waitForCompletion(true);

 

System.exit(result ? 0 : 1);

}

}

 

7.5 过滤日志自定义日志输出路径案例(自定义OutputFormat

1需求

过滤输入log日志是否包含itstar

1)包含itstar的网站输出e:/itstar.log

2)不包含itstar的网站输出到e:/other.log

2输入数据

 

输出预期:

  

3)具体程序:

(1)自定义一个outputformat

package com.itstar.mapreduce.outputformat;

import java.io.IOException;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.RecordWriter;

import org.apache.hadoop.mapreduce.TaskAttemptContext;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

 

public class FilterOutputFormat extends FileOutputFormat<Text, NullWritable>{

 

@Override

public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job)

throws IOException, InterruptedException {

 

// 建立一个RecordWriter

return new FilterRecordWriter(job);

}

}

2)具体的写数据RecordWriter

package com.itstar.mapreduce.outputformat;

import java.io.IOException;

import org.apache.hadoop.fs.FSDataOutputStream;

import org.apache.hadoop.fs.FileSystem;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.RecordWriter;

import org.apache.hadoop.mapreduce.TaskAttemptContext;

 

public class FilterRecordWriter extends RecordWriter<Text, NullWritable> {

FSDataOutputStream itstarOut = null;

FSDataOutputStream otherOut = null;

 

public FilterRecordWriter(TaskAttemptContext job) {

// 1 获取文件系统

FileSystem fs;

 

try {

fs = FileSystem.get(job.getConfiguration());

 

// 2 建立输出文件路径

Path itstarPath = new Path("e:/itstar.log");

Path otherPath = new Path("e:/other.log");

 

// 3 建立输出流

itstarOut = fs.create(itstarPath);

otherOut = fs.create(otherPath);

} catch (IOException e) {

e.printStackTrace();

}

}

 

@Override

public void write(Text key, NullWritable value) throws IOException, InterruptedException {

 

// 判断是否包含“itstar”输出到不一样文件

if (key.toString().contains("itstar")) {

itstarOut.write(key.toString().getBytes());

} else {

otherOut.write(key.toString().getBytes());

}

}

 

@Override

public void close(TaskAttemptContext context) throws IOException, InterruptedException {

// 关闭资源

if (itstarOut != null) {

itstarOut.close();

}

 

if (otherOut != null) {

otherOut.close();

}

}

}

3)编写FilterMapper

package com.itstar.mapreduce.outputformat;

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 FilterMapper 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();

 

k.set(line);

 

// 3 写出

context.write(k, NullWritable.get());

}

}

(4)编写FilterReducer

package com.itstar.mapreduce.outputformat;

import java.io.IOException;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class FilterReducer extends Reducer<Text, NullWritable, Text, NullWritable> {

 

@Override

protected void reduce(Text key, Iterable<NullWritable> values, Context context)

throws IOException, InterruptedException {

 

String k = key.toString();

k = k + "\r\n";

 

context.write(new Text(k), NullWritable.get());

}

}

(5)编写FilterDriver

package com.itstar.mapreduce.outputformat;

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 FilterDriver {

public static void main(String[] args) throws Exception {

 

args = new String[] { "e:/input/inputoutputformat", "e:/output2" };

 

Configuration conf = new Configuration();

 

Job job = Job.getInstance(conf);

 

job.setJarByClass(FilterDriver.class);

job.setMapperClass(FilterMapper.class);

job.setReducerClass(FilterReducer.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(NullWritable.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(NullWritable.class);

 

// 要将自定义的输出格式组件设置到job

job.setOutputFormatClass(FilterOutputFormat.class);

 

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

 

// 虽然咱们自定义了outputformat,可是由于咱们的outputformat继承自fileoutputformat

// fileoutputformat要输出一个_SUCCESS文件,因此,在这还得指定一个输出目录

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

 

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

 

7.6 MapReduce中多表合并案例

1)需求:

订单数据表t_order

id

pid

amount

1001

01

1

1002

02

2

1003

03

3

 

商品信息表t_product

pid

pname

01

小米

02

华为

03

格力

 

商品信息表中数据根据商品pid合并到订单数据表中。

最终数据形式:

id

pname

amount

1001

小米

1

1004

小米

4

1002

华为

2

1005

华为

5

1003

格力

3

1006

格力

6

7.6.1 需求1Reduce端表合并(数据倾斜

经过将关联条件做为map输出的key,将两表知足join条件的数据并携带数据所来源的文件信息,发往同一个reduce task,在reduce中进行数据的串联。

 

1)建立商品和订合并后的bean

package com.itstar.mapreduce.table;

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import org.apache.hadoop.io.Writable;

 

public class TableBean implements Writable {

private String order_id; // 订单id

private String p_id; // 产品id

private int amount; // 产品数量

private String pname; // 产品名称

private String flag;// 表的标记

 

public TableBean() {

super();

}

 

public TableBean(String order_id, String p_id, int amount, String pname, String flag) {

super();

this.order_id = order_id;

this.p_id = p_id;

this.amount = amount;

this.pname = pname;

this.flag = flag;

}

 

public String getFlag() {

return flag;

}

 

public void setFlag(String flag) {

this.flag = flag;

}

 

public String getOrder_id() {

return order_id;

}

 

public void setOrder_id(String order_id) {

this.order_id = order_id;

}

 

public String getP_id() {

return p_id;

}

 

public void setP_id(String p_id) {

this.p_id = p_id;

}

 

public int getAmount() {

return amount;

}

 

public void setAmount(int amount) {

this.amount = amount;

}

 

public String getPname() {

return pname;

}

 

public void setPname(String pname) {

this.pname = pname;

}

 

@Override

public void write(DataOutput out) throws IOException {

out.writeUTF(order_id);

out.writeUTF(p_id);

out.writeInt(amount);

out.writeUTF(pname);

out.writeUTF(flag);

}

 

@Override

public void readFields(DataInput in) throws IOException {

this.order_id = in.readUTF();

this.p_id = in.readUTF();

this.amount = in.readInt();

this.pname = in.readUTF();

this.flag = in.readUTF();

}

 

@Override

public String toString() {

return order_id + "\t" + pname + "\t" + amount + "\t" ;

}

}

2)编写TableMapper程序

package com.itstar.mapreduce.table;

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 TableMapper extends Mapper<LongWritable, Text, Text, TableBean>{

TableBean bean = new TableBean();

Text k = new Text();

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

 

// 1 获取输入文件类型

FileSplit split = (FileSplit) context.getInputSplit();

String name = split.getPath().getName();

 

// 2 获取输入数据

String line = value.toString();

 

// 3 不一样文件分别处理

if (name.startsWith("order")) {// 订单表处理

// 3.1 切割

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

 

// 3.2 封装bean对象

bean.setOrder_id(fields[0]);

bean.setP_id(fields[1]);

bean.setAmount(Integer.parseInt(fields[2]));

bean.setPname("");

bean.setFlag("0");

 

k.set(fields[1]);

}else {// 产品表处理

// 3.3 切割

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

 

// 3.4 封装bean对象

bean.setP_id(fields[0]);

bean.setPname(fields[1]);

bean.setFlag("1");

bean.setAmount(0);

bean.setOrder_id("");

 

k.set(fields[0]);

}

// 4 写出

context.write(k, bean);

}

}

3)编写TableReducer程序

package com.itstar.mapreduce.table;

import java.io.IOException;

import java.util.ArrayList;

import org.apache.commons.beanutils.BeanUtils;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class TableReducer extends Reducer<Text, TableBean, TableBean, NullWritable> {

 

@Override

protected void reduce(Text key, Iterable<TableBean> values, Context context)

throws IOException, InterruptedException {

 

// 1准备存储订单的集合

ArrayList<TableBean> orderBeans = new ArrayList<>();

// 2 准备bean对象

TableBean pdBean = new TableBean();

 

for (TableBean bean : values) {

 

if ("0".equals(bean.getFlag())) {// 订单表

// 拷贝传递过来的每条订单数据到集合中

TableBean orderBean = new TableBean();

try {

BeanUtils.copyProperties(orderBean, bean);

} catch (Exception e) {

e.printStackTrace();

}

 

orderBeans.add(orderBean);

} else {// 产品表

try {

// 拷贝传递过来的产品表到内存中

BeanUtils.copyProperties(pdBean, bean);

} catch (Exception e) {

e.printStackTrace();

}

}

}

 

// 3 表的拼接

for(TableBean bean:orderBeans){

bean.setPname (pdBean.getPname());

 

// 4 数据写出去

context.write(bean, NullWritable.get());

}

}

}

4)编写TableDriver程序

package com.itstar.mapreduce.table;

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 TableDriver {

 

public static void main(String[] args) throws Exception {

// 1 获取配置信息,或者job对象实例

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 2 指定本程序的jar包所在的本地路径

job.setJarByClass(TableDriver.class);

 

// 3 指定本业务job要使用的mapper/Reducer业务类

job.setMapperClass(TableMapper.class);

job.setReducerClass(TableReducer.class);

 

// 4 指定mapper输出数据的kv类型

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(TableBean.class);

 

// 5 指定最终输出的数据的kv类型

job.setOutputKeyClass(TableBean.class);

job.setOutputValueClass(NullWritable.class);

 

// 6 指定job的输入原始文件所在目录

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

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

 

// 7 job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

3)运行程序查看结果

1001 小米 1

1001 小米 1

1002 华为 2

1002 华为 2

1003 格力 3

1003 格力 3

缺点:这种方式中,合并的操做是在reduce阶段完成,reduce端的处理压力太大,map节点的运算负载则很低,资源利用率不高,且在reduce阶段极易产生数据倾斜

解决方案: map端实现数据合并

7.6.2 需求2Map端表合并(Distributedcache

1)分析

适用于关联表中有小表的情形;

能够将小表分发到全部的map节点,这样,map节点就能够在本地对本身所读到的大表数据进行合并并输出最终结果,能够大大提升合并操做的并发度,加快处理速度。

 

2)实操案例

1)先在驱动模块中添加缓存文件

import java.net.URI;

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 DistributedCacheDriver {

 

public static void main(String[] args) throws Exception {

// 1 获取job信息

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 2 设置加载jar包路径

job.setJarByClass(DistributedCacheDriver.class);

 

// 3 关联map

job.setMapperClass(DistributedCacheMapper.class);

 

// 4 设置最终输出数据类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(NullWritable.class);

 

// 5 设置输入输出路径

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

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

 

// 6 加载缓存数据

job.addCacheFile(new URI("file:///e:/inputcache/pd.txt"));

 

// 7 mapjoin的逻辑不须要reduce阶段,设置reducetask数量为0

job.setNumReduceTasks(0);

 

// 8 提交

boolean result = job.waitForCompletion(true);

System.exit(result ? 0 : 1);

}

}

2)读取缓存文件数据

import java.io.BufferedReader;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.util.HashMap;

import java.util.Map;

import org.apache.commons.lang.StringUtils;

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 DistributedCacheMapper extends Mapper<LongWritable, Text, Text, NullWritable>{

 

Map<String, String> pdMap = new HashMap<>();

 

@Override

protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context)

throws IOException, InterruptedException {

 

// 1 获取缓存的文件

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("pd.txt"),"UTF-8"));

 

String line;

while(StringUtils.isNotEmpty(line = reader.readLine())){

// 2 切割

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

 

// 3 缓存数据到集合

pdMap.put(fields[0], fields[1]);

}

 

// 4 关流

reader.close();

}

 

Text k = new Text();

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

// 1 获取一行

String line = value.toString();

 

// 2 截取

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

 

// 3 获取产品id

String pId = fields[1];

 

// 4 获取商品名称

String pdName = pdMap.get(pId);

 

// 5 拼接

k.set(line + "\t"+ pdName);

 

// 6 写出

context.write(k, NullWritable.get());

}

}

7.7 日志清洗案例

7.7.1 简单解析版

1)需求:

去除日志中字段长度小于等于11的日志。

2输入数据

 

3)实现代码:

(1)编写LogMapper

package com.itstar.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 获取1行数据

String line = value.toString();

 

// 2 解析日志

boolean result = parseLog(line,context);

 

// 3 日志不合法退出

if (!result) {

return;

}

 

// 4 设置key

k.set(line);

 

// 5 写出数据

context.write(k, NullWritable.get());

}

 

// 2 解析日志

private boolean parseLog(String line, Context context) {

// 1 截取

String[] fields = line.split(" ");

 

// 2 日志长度大于11的为合法

if (fields.length > 11) {

// 系统计数器

context.getCounter("map", "true").increment(1);

return true;

}else {

context.getCounter("map", "false").increment(1);

return false;

}

}

}

(2)编写LogDriver

package com.itstar.mapreduce.weblog;

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 Exception {

 

        args = new String[] { "e:/input/inputlog", "e:/output1" };

 

// 1 获取job信息

Configuration conf = new Configuration();

Job job = Job.getInstance(conf);

 

// 2 加载jar

job.setJarByClass(LogDriver.class);

 

// 3 关联map

job.setMapperClass(LogMapper.class);

 

// 4 设置最终输出类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(NullWritable.class);

 

// 设置reducetask个数为0

job.setNumReduceTasks(0);

 

// 5 设置输入和输出路径

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

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

 

// 6 提交

job.waitForCompletion(true);

}

}

7.7.2 复杂解析版

1)需求:

web访问日志中的各字段识别切分

去除日志中不合法的记录

根据统计需求,生成各种访问请求过滤数据

2输入数据

 

3)实现代码:

1)定义一个bean,用来记录日志数据中的各数据字段

package com.itstar.mapreduce.log;

 

public class LogBean {

private String remote_addr;// 记录客户端的ip地址

private String remote_user;// 记录客户端用户名称,忽略属性"-"

private String time_local;// 记录访问时间与时区

private String request;// 记录请求的urlhttp协议

private String status;// 记录请求状态;成功是200

private String body_bytes_sent;// 记录发送给客户端文件主体内容大小

private String http_referer;// 用来记录从那个页面连接访问过来的

private String http_user_agent;// 记录客户浏览器的相关信息

 

private boolean valid = true;// 判断数据是否合法

 

public String getRemote_addr() {

return remote_addr;

}

 

public void setRemote_addr(String remote_addr) {

this.remote_addr = remote_addr;

}

 

public String getRemote_user() {

return remote_user;

}

 

public void setRemote_user(String remote_user) {

this.remote_user = remote_user;

}

 

public String getTime_local() {

return time_local;

}

 

public void setTime_local(String time_local) {

this.time_local = time_local;

}

 

public String getRequest() {

return request;

}

 

public void setRequest(String request) {

this.request = request;

}

 

public String getStatus() {

return status;

}

 

public void setStatus(String status) {

this.status = status;

}

 

public String getBody_bytes_sent() {

return body_bytes_sent;

}

 

public void setBody_bytes_sent(String body_bytes_sent) {

this.body_bytes_sent = body_bytes_sent;

}

 

public String getHttp_referer() {

return http_referer;

}

 

public void setHttp_referer(String http_referer) {

this.http_referer = http_referer;

}

 

public String getHttp_user_agent() {

return http_user_agent;

}

 

public void setHttp_user_agent(String http_user_agent) {

this.http_user_agent = http_user_agent;

}

 

public boolean isValid() {

return valid;

}

 

public void setValid(boolean valid) {

this.valid = valid;

}

 

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

sb.append(this.valid);

sb.append("\001").append(this.remote_addr);

sb.append("\001").append(this.remote_user);

sb.append("\001").append(this.time_local);

sb.append("\001").append(this.request);

sb.append("\001").append(this.status);

sb.append("\001").append(this.body_bytes_sent);

sb.append("\001").append(this.http_referer);

sb.append("\001").append(this.http_user_agent);

 

return sb.toString();

}

}

2)编写LogMapper程序

package com.itstar.mapreduce.log;

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 获取1

String line = value.toString();

 

// 2 解析日志是否合法

LogBean bean = pressLog(line);

 

if (!bean.isValid()) {

return;

}

 

k.set(bean.toString());

 

// 3 输出

context.write(k, NullWritable.get());

}

 

// 解析日志

private LogBean pressLog(String line) {

LogBean logBean = new LogBean();

 

// 1 截取

String[] fields = line.split(" ");

 

if (fields.length > 11) {

// 2封装数据

logBean.setRemote_addr(fields[0]);

logBean.setRemote_user(fields[1]);

logBean.setTime_local(fields[3].substring(1));

logBean.setRequest(fields[6]);

logBean.setStatus(fields[8]);

logBean.setBody_bytes_sent(fields[9]);

logBean.setHttp_referer(fields[10]);

 

if (fields.length > 12) {

logBean.setHttp_user_agent(fields[11] + " "+ fields[12]);

}else {

logBean.setHttp_user_agent(fields[11]);

}

 

// 大于400HTTP错误

if (Integer.parseInt(logBean.getStatus()) >= 400) {

logBean.setValid(false);

}

}else {

logBean.setValid(false);

}

 

return logBean;

}

}

(3)编写LogDriver程序

package com.itstar.mapreduce.log;

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 Exception {

// 1 获取job信息

Configuration conf = new Configuration();

Job job = Job.getInstance(conf);

 

// 2 加载jar

job.setJarByClass(LogDriver.class);

 

// 3 关联map

job.setMapperClass(LogMapper.class);

 

// 4 设置最终输出类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(NullWritable.class);

 

// 5 设置输入和输出路径

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

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

 

// 6 提交

job.waitForCompletion(true);

}

}

7.8 倒排索引案例(多job串联

0)需求:有大量的文本(文档、网页),须要创建搜索索引

    

 

1)第一次预期输出结果

itstar--a.txt 3

itstar--b.txt 2

itstar--c.txt 2

pingping--a.txt  1

pingping--b.txt 3

pingping--c.txt  1

ss--a.txt 2

ss--b.txt 1

ss--c.txt 1

2第二次预期输出结果

itstar c.txt-->2 b.txt-->2 a.txt-->3

pingping c.txt-->1 b.txt-->3 a.txt-->1

ss c.txt-->1 b.txt-->1 a.txt-->2

1)第一次处理

1)第一次处理,编写OneIndexMapper

package com.itstar.mapreduce.index;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

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 OneIndexMapper extends Mapper<LongWritable, Text, Text , IntWritable>{

 

String name;

Text k = new Text();

IntWritable v = new IntWritable();

 

@Override

protected void setup(Context context)

throws IOException, InterruptedException {

// 获取文件名称

FileSplit split = (FileSplit) context.getInputSplit();

 

name = split.getPath().getName();

}

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

// 1 获取1

String line = value.toString();

 

// 2 切割

String[] fields = line.split(" ");

 

for (String word : fields) {

// 3 拼接

k.set(word+"--"+name);

v.set(1);

 

// 4 写出

context.write(k, v);

}

}

}

(2)第一次处理,编写OneIndexReducer

package com.itstar.mapreduce.index;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class OneIndexReducer extends Reducer<Text, IntWritable, Text, IntWritable>{

 

@Override

protected void reduce(Text key, Iterable<IntWritable> values,

Context context) throws IOException, InterruptedException {

 

int count = 0;

// 1 累加求和

for(IntWritable value: values){

count +=value.get();

}

 

// 2 写出

context.write(key, new IntWritable(count));

}

}

(3)第一次处理,编写OneIndexDriver

package com.itstar.mapreduce.index;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

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 OneIndexDriver {

 

public static void main(String[] args) throws Exception {

 

args = new String[] { "e:/input/inputoneindex", "e:/output5" };

 

Configuration conf = new Configuration();

 

Job job = Job.getInstance(conf);

job.setJarByClass(OneIndexDriver.class);

 

job.setMapperClass(OneIndexMapper.class);

job.setReducerClass(OneIndexReducer.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

 

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

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

 

job.waitForCompletion(true);

}

}

4)查看第一次输出结果

itstar--a.txt 3

itstar--b.txt 2

itstar--c.txt 2

pingping--a.txt 1

pingping--b.txt 3

pingping--c.txt 1

ss--a.txt 2

ss--b.txt 1

ss--c.txt 1

2)第二次处理

1)第二次处理,编写TwoIndexMapper

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class TwoIndexMapper extends Mapper<LongWritable, Text, Text, Text>{

Text k = new Text();

Text v = new Text();

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

 

// 1 获取1行数据

String line = value.toString();

 

// 2“--”切割

String[] fields = line.split("--");

 

k.set(fields[0]);

v.set(fields[1]);

 

// 3 输出数据

context.write(k, v);

}

}

(2)第二次处理,编写TwoIndexReducer

import java.io.IOException;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

public class TwoIndexReducer extends Reducer<Text, Text, Text, Text> {

 

@Override

protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {

// itstar a.txt 3

// itstar b.txt 2

// itstar c.txt 2

 

// itstar c.txt-->2 b.txt-->2 a.txt-->3

 

StringBuilder sb = new StringBuilder();

        // 1 拼接

for (Text value : values) {

sb.append(value.toString().replace("\t", "-->") + "\t");

}

// 2 写出

context.write(key, new Text(sb.toString()));

}

}

(3)第二次处理,编写TwoIndexDriver

import org.apache.hadoop.conf.Configuration;

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;

 

public class TwoIndexDriver {

 

public static void main(String[] args) throws Exception {

 

args = new String[] { "e:/input/inputtwoindex", "e:/output6" };

 

Configuration config = new Configuration();

Job job = Job.getInstance(config);

 

job.setJarByClass(TwoIndexDriver.class);

job.setMapperClass(TwoIndexMapper.class);

job.setReducerClass(TwoIndexReducer.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(Text.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(Text.class);

 

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

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

 

boolean result = job.waitForCompletion(true);

System.exit(result?0:1);

}

}

4第二次查看最终结果

Itstar  c.txt-->2 b.txt-->2 a.txt-->3

pingping c.txt-->1 b.txt-->3 a.txt-->1

ss c.txt-->1 b.txt-->1 a.txt-->2

7.9 找博客共同好友案例

1需求:

如下是博客的好友列表数据,冒号前是一个用户,冒号后是该用户的全部好友(数据中的好友关系是单向的)

 

求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?

2)需求分析:

求出AB、C….等是好友

第一次输出结果

A I,K,C,B,G,F,H,O,D,

B A,F,J,E,

C A,E,B,H,F,G,K,

D G,C,K,A,L,F,E,H,

E G,M,L,H,A,F,B,D,

F L,M,D,C,G,A,

G M,

H O,

I O,C,

J O,

K B,

L D,E,

M E,F,

O A,H,I,J,F,

第二次输出结果

A-B E C

A-C D F

A-D E F

A-E D B C

A-F O B C D E

A-G F E C D

….

3)代码实现:

1)第一次Mapper

package com.itstar.mapreduce.friends;

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class OneShareFriendsMapper extends Mapper<LongWritable, Text, Text, Text>{

 

@Override

protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context)

throws IOException, InterruptedException {

// 1 获取一行 A:B,C,D,F,E,O

String line = value.toString();

 

// 2 切割

String[] fields = line.split(":");

 

// 3 获取person和好友

String person = fields[0];

String[] friends = fields[1].split(",");

 

// 4写出去

for(String friend: friends){

// 输出 <好友,人>

context.write(new Text(friend), new Text(person));

}

}

}

(2)第一次Reducer

package com.itstar.mapreduce.friends;

import java.io.IOException;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class OneShareFriendsReducer extends Reducer<Text, Text, Text, Text>{

 

@Override

protected void reduce(Text key, Iterable<Text> values, Context context)

throws IOException, InterruptedException {

 

StringBuffer sb = new StringBuffer();

//1 拼接

for(Text person: values){

sb.append(person).append(",");

}

 

//2 写出

context.write(key, new Text(sb.toString()));

}

}

(3)第一次Driver

package com.itstar.mapreduce.friends;

import org.apache.hadoop.conf.Configuration;

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;

 

public class OneShareFriendsDriver {

 

public static void main(String[] args) throws Exception {

// 1 获取job对象

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 2 指定jar包运行的路径

job.setJarByClass(OneShareFriendsDriver.class);

 

// 3 指定map/reduce使用的类

job.setMapperClass(OneShareFriendsMapper.class);

job.setReducerClass(OneShareFriendsReducer.class);

 

// 4 指定map输出的数据类型

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(Text.class);

 

// 5 指定最终输出的数据类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(Text.class);

 

// 6 指定job的输入原始所在目录

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

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

 

// 7 提交

boolean result = job.waitForCompletion(true);

 

System.exit(result?0:1);

}

}

(4)第二次Mapper

package com.itstar.mapreduce.friends;

import java.io.IOException;

import java.util.Arrays;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class TwoShareFriendsMapper extends Mapper<LongWritable, Text, Text, Text>{

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

// A I,K,C,B,G,F,H,O,D,

// 友 人,人,人

String line = value.toString();

String[] friend_persons = line.split("\t");

 

String friend = friend_persons[0];

String[] persons = friend_persons[1].split(",");

 

Arrays.sort(persons);

 

for (int i = 0; i < persons.length - 1; i++) {

 

for (int j = i + 1; j < persons.length; j++) {

// 发出 <-人,好友> ,这样,相同的-对的全部好友就会到同1reduce中去

context.write(new Text(persons[i] + "-" + persons[j]), new Text(friend));

}

}

}

}

(5)第二次Reducer

package com.itstar.mapreduce.friends;

import java.io.IOException;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class TwoShareFriendsReducer extends Reducer<Text, Text, Text, Text>{

 

@Override

protected void reduce(Text key, Iterable<Text> values, Context context)

throws IOException, InterruptedException {

 

StringBuffer sb = new StringBuffer();

 

for (Text friend : values) {

sb.append(friend).append(" ");

}

 

context.write(key, new Text(sb.toString()));

}

}

(6)第二次Driver

package com.itstar.mapreduce.friends;

import org.apache.hadoop.conf.Configuration;

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;

 

public class TwoShareFriendsDriver {

 

public static void main(String[] args) throws Exception {

// 1 获取job对象

Configuration configuration = new Configuration();

Job job = Job.getInstance(configuration);

 

// 2 指定jar包运行的路径

job.setJarByClass(TwoShareFriendsDriver.class);

 

// 3 指定map/reduce使用的类

job.setMapperClass(TwoShareFriendsMapper.class);

job.setReducerClass(TwoShareFriendsReducer.class);

 

// 4 指定map输出的数据类型

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(Text.class);

 

// 5 指定最终输出的数据类型

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(Text.class);

 

// 6 指定job的输入原始所在目录

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

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

 

// 7 提交

boolean result = job.waitForCompletion(true);

System.exit(result?0:1);

}

}

7.10 压缩/解压缩案例

7.10.1 数据流的压缩和解压缩

CompressionCodec有两个方法能够用于轻松地压缩或解压缩数据。要想对正在被写入一个输出流的数据进行压缩,咱们可使用createOutputStream(OutputStreamout)方法建立一个CompressionOutputStream,将其以压缩格式写入底层的流。相反,要想对从输入流读取而来的数据进行解压缩,则调用createInputStream(InputStreamin)函数,从而得到一个CompressionInputStream从而从底层的流读取未压缩的数据。

测试一下以下压缩方式

DEFLATE

org.apache.hadoop.io.compress.DefaultCodec

gzip

org.apache.hadoop.io.compress.GzipCodec

bzip2

org.apache.hadoop.io.compress.BZip2Codec

 

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IOUtils;

import org.apache.hadoop.io.compress.CompressionCodec;

import org.apache.hadoop.io.compress.CompressionCodecFactory;

import org.apache.hadoop.io.compress.CompressionInputStream;

import org.apache.hadoop.io.compress.CompressionOutputStream;

import org.apache.hadoop.util.ReflectionUtils;

 

public class TestCompress {

 

public static void main(String[] args) throws Exception {

compress("e:/hello.txt","org.apache.hadoop.io.compress.BZip2Codec");

// decompress("e:/hello.txt.bz2");

}

 

  /**

     * 压缩方法

     *

     * @param fliename 文件路径+文件名

     * @param method 解码器

     */

    private static void compress(String filename, String method) throws Exception {

        //建立输入流

        FileInputStream fis = new FileInputStream(new File(filename));

 

        //经过反射找到解码器的类

        Class codeClass = Class.forName(method);

 

        //经过反射工具类找到解码器对象,须要用到配置conf对象

        CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codeClass, new Configuration());

 

        //建立输出流

        FileOutputStream fos = new FileOutputStream(new File(filename + codec.getDefaultExtension()));

 

        //得到解码器的输出对象

        CompressionOutputStream cos = codec.createOutputStream(fos);

 

        //流拷贝

        IOUtils.copyBytes(fis,cos,5 * 1024 * 1024,false);

 

        //关闭流

        cos.close();

        fos.close();

        fis.close();

}

 

    /**

     * 解开压缩

     *

     * @param 文件路径+文件名

     * @param 后缀

     */

    private static void decompress(String filename, String decoded) throws Exception {

        //获取factory实例

        CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());

 

        CompressionCodec codec = factory.getCodec(new Path(filename));

 

        if (codec == null) {

            System.out.println(filename);

            return;

        }

 

        //解压缩的输入

        CompressionInputStream cis = codec.createInputStream(new FileInputStream(new File(filename)));

 

        //输出流

        FileOutputStream fos = new FileOutputStream(new File(filename + "." + decoded));

 

        //流拷贝

        IOUtils.copyBytes(cis, fos, 5 * 1024 * 1024, false);

 

        cis.close();

        fos.close();

    }

7.10.2 Map输出端采用压缩

即便你的MapReduce的输入输出文件都是未压缩的文件,你仍然能够对map任务的中间结果输出作压缩,由于它要写在硬盘而且经过网络传输到reduce节点,对其压缩能够提升不少性能,这些工做只要设置两个属性便可,咱们来看下代码怎么设置:

1)给你们提供的hadoop源码支持的压缩格式有:BZip2Codec 、DefaultCodec

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.io.compress.BZip2Codec;

import org.apache.hadoop.io.compress.CompressionCodec;

import org.apache.hadoop.io.compress.GzipCodec;

import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

 

public class WordCountDriver {

 

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

 

Configuration configuration = new Configuration();

 

// 开启map端输出压缩

configuration.setBoolean("mapreduce.map.output.compress", true);

// 设置map端输出压缩方式

configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);

 

Job job = Job.getInstance(configuration);

 

job.setJarByClass(WordCountDriver.class);

 

job.setMapperClass(WordCountMapper.class);

job.setReducerClass(WordCountReducer.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

 

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

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

 

boolean result = job.waitForCompletion(true);

 

System.exit(result ? 1 : 0);

}

}

2Mapper保持不变

package com.itstar.mapreduce.compress;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{

 

@Override

protected void map(LongWritable key, Text value, Context context)

throws IOException, InterruptedException {

// 1 获取一行

String line = value.toString();

// 2 切割

String[] words = line.split(" ");

// 3 循环写出

for(String word:words){

context.write(new Text(word), new IntWritable(1));

}

}

}

3)Reducer保持不变

package com.itstar.mapreduce.compress;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{

 

@Override

protected void reduce(Text key, Iterable<IntWritable> values,

Context context) throws IOException, InterruptedException {

 

int count = 0;

// 1 汇总

for(IntWritable value:values){

count += value.get();

}

 

        // 2 输出

context.write(key, new IntWritable(count));

}

}

7.10.3 Reduce输出端采用压缩

基于workcount案例处理

1修改驱动

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.io.compress.BZip2Codec;

import org.apache.hadoop.io.compress.DefaultCodec;

import org.apache.hadoop.io.compress.GzipCodec;

import org.apache.hadoop.io.compress.Lz4Codec;

import org.apache.hadoop.io.compress.SnappyCodec;

import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

 

public class WordCountDriver {

 

public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

 

Configuration configuration = new Configuration();

 

Job job = Job.getInstance(configuration);

 

job.setJarByClass(WordCountDriver.class);

 

job.setMapperClass(WordCountMapper.class);

job.setReducerClass(WordCountReducer.class);

 

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

 

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

 

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

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

 

// 设置reduce端输出压缩开启

FileOutputFormat.setCompressOutput(job, true);

 

// 设置压缩的方式

    FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);

//     FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);

//     FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class);

    

boolean result = job.waitForCompletion(true);

 

System.exit(result?1:0);

}

}

3) MapperReducer保持不变

 

7.11 两表Join

未优化版本

Bean.java

import org.apache.hadoop.io.WritableComparable;

 

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

 

/*

 * 人员和地址的通用bean

 */

public class Bean implements WritableComparable<Bean> {

    private String userNo = "";

    private String userName = "";

    private String addreNo = "";

    private String addreName = "";

    private int flag;

 

    public Bean(Bean bean) {

        this.userName = bean.getUserName();

        this.userNo = bean.getUserNo();

        this.addreName = bean.getAddreName();

        this.addreNo = bean.getAddreNo();

        this.flag = bean.getFlag();

    }

 

    public Bean() {

        super();

        // TODO Auto-generated constructor stub

    }

 

    public Bean(String userNo, String userName, String addreNo,

                String addreName, int flag) {

        super();

        this.userNo = userNo;

        this.userName = userName;

        this.addreNo = addreNo;

        this.addreName = addreName;

        this.flag = flag;

    }

 

    public String getUserNo() {

        return userNo;

    }

 

    public void setUserNo(String userNo) {

        this.userNo = userNo;

    }

 

    public String getUserName() {

        return userName;

    }

 

    public void setUserName(String userName) {

        this.userName = userName;

    }

 

    public String getAddreNo() {

        return addreNo;

    }

 

    public void setAddreNo(String addreNo) {

        this.addreNo = addreNo;

    }

 

    public String getAddreName() {

        return addreName;

    }

 

    public void setAddreName(String addreName) {

        this.addreName = addreName;

    }

 

    public int getFlag() {

        return flag;

    }

 

    public void setFlag(int flag) {

        this.flag = flag;

    }

 

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeUTF(userNo);

        out.writeUTF(userName);

        out.writeUTF(addreNo);

        out.writeUTF(addreName);

        out.writeInt(flag);

 

    }

 

    @Override

    public void readFields(DataInput in) throws IOException {

        this.userNo = in.readUTF();

        this.userName = in.readUTF();

        this.addreNo = in.readUTF();

        this.addreName = in.readUTF();

        this.flag = in.readInt();

 

    }

 

    @Override

    public int compareTo(Bean arg0) {

        // TODO Auto-generated method stub

        return 0;

    }

 

    @Override

    public String toString() {

        return "userNo=" + userNo + ", userName=" + userName + ", addreNo="

                + addreNo + ", addreName=" + addreName;

    }

 

}

PersonAddrMap.java

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

 

import java.io.IOException;

 

public class PersonAddrMap extends Mapper<LongWritable, Text, IntWritable, Bean> {

    @Override

    protected void map(LongWritable key, Text value,

                       Mapper<LongWritable, Text, IntWritable, Bean>.Context context)

            throws IOException, InterruptedException {

        String line = value.toString();

        String str[] = line.split(" ");

        if (str.length == 2) { //地区信息表

            Bean bean = new Bean();

            bean.setAddreNo(str[0]);

            bean.setAddreName(str[1]);

            bean.setFlag(0); // 0表示地区

            context.write(new IntWritable(Integer.parseInt(str[0])), bean);

        } else { //人员信息表

            Bean bean = new Bean();

            bean.setUserNo(str[0]);

            bean.setUserName(str[1]);

            bean.setAddreNo(str[2]);

            bean.setFlag(1); // 1表示人员表

            context.write(new IntWritable(Integer.parseInt(str[2])), bean);

        }

    }

}

 

PersonAddreRedu.java

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

 

public class PersonAddreRedu extends Reducer<IntWritable, Bean, NullWritable,Text> {

    @Override

    protected void reduce(IntWritable key, Iterable<Bean> values,

                          Reducer<IntWritable, Bean, NullWritable, Text>.Context context)

            throws IOException, InterruptedException {

        Bean Addre = null;

        List<Bean> peoples = new ArrayList<Bean>();

/*

 * 若是values的第一个元素信息就是地址Addre的信息的话,

 * 咱们就再也不须要一个List来缓存person信息了,values后面的全是人员信息

 * 将减小巨大的内存空间

 */

/*

 * partitioner和shuffer的过程:

 * partitioner的主要功能是根据reduce的数量将map输出的结果进行分块,将数据送入到相应的reducer.

 * 全部的partitioner都必须实现partitioner接口并实现getPartition方法,该方法的返回值为int类型,而且取值范围在0~(numOfReducer-1),

 * 从而能将map的输出输入到对应的reducer中,对于某个mapreduce过程,hadoop框架定义了默认的partitioner为HashPartioner,

 * 该partitioner使用key的hashCode来决定将该key输送到哪一个reducer;

 * shuffle将每一个partitioner输出的结果根据key进行group以及排序,将具备相同key的value构成一个values的迭代器,并根据key进行排序分别调用

 * 开发者定义的reduce方法进行排序,所以mapreducer的因此key必须实现comparable接口的compareto()方法从而能实现两个key对象的比较

 */

/*

 * 咱们须要自定义key的数据结构(shuffle按照key进行分组)来知足共同addreNo的状况下地址表的更小需求

 *

 */

        for (Bean bean : values) {

            if (bean.getFlag() == 0) { // 表示地区表

                Addre = new Bean(bean);

            } else {

                peoples.add(new Bean(bean)); //添加到peoplelist中

            }

        }

        for (Bean peo : peoples) { // 给peoplelist添加地区名字

            peo.setAddreName(Addre.getAddreName());

            context.write(NullWritable.get(), new Text(peo.toString()));

        }

    }

}

 

PersonAddreMain.java

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

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 PersonAddreMain {

    public static void main(String[] args) throws Exception {

 

        args = new String[] { "F:\\A\\join\\", "F:\\A\\out" };

 

        Configuration conf = new Configuration();

        Job job = Job.getInstance(conf);

        job.setJarByClass(PersonAddreMain.class);

 

        job.setMapperClass(PersonAddrMap.class);

        job.setMapOutputKeyClass(IntWritable.class);

        job.setMapOutputValueClass(Bean.class);

 

        job.setReducerClass(PersonAddreRedu.class);

        job.setOutputKeyClass(NullWritable.class);

        job.setOutputValueClass(Text.class);

 

        FileInputFormat.addInputPath(job, new Path(args[0]));

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

        job.waitForCompletion(true);

    }

}

 

已优化版本

Bean.java

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

 

/*

 * 人员和地址的通用bean

 * 用做map输出的value

 */

public class Bean implements WritableComparable<Bean> {

    private String userNo = " ";

    private String userName = " ";

    private String addreNo = " ";

    private String addreName = " ";

 

    public Bean(Bean bean) {

        this.userName = bean.getUserName();

        this.userNo = bean.getUserNo();

        this.addreName = bean.getAddreName();

        this.addreNo = bean.getAddreNo();

    }

 

    public Bean() {

        super();

        // TODO Auto-generated constructor stub

    }

 

    public Bean(String userNo, String userName, String addreNo,

                String addreName, int flag) {

        super();

        this.userNo = userNo;

        this.userName = userName;

        this.addreNo = addreNo;

        this.addreName = addreName;

    }

 

 

    public String getUserNo() {

        return userNo;

    }

 

    public void setUserNo(String userNo) {

        this.userNo = userNo;

    }

 

    public String getUserName() {

        return userName;

    }

 

    public void setUserName(String userName) {

        this.userName = userName;

    }

 

    public String getAddreNo() {

        return addreNo;

    }

 

    public void setAddreNo(String addreNo) {

        this.addreNo = addreNo;

    }

 

    public String getAddreName() {

        return addreName;

    }

 

    public void setAddreName(String addreName) {

        this.addreName = addreName;

    }

 

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeUTF(userNo);

        out.writeUTF(userName);

        out.writeUTF(addreNo);

        out.writeUTF(addreName);

 

    }

 

    @Override

    public void readFields(DataInput in) throws IOException {

        this.userNo = in.readUTF();

        this.userName = in.readUTF();

        this.addreNo = in.readUTF();

        this.addreName = in.readUTF();

    }

 

    @Override

    public int compareTo(Bean arg0) {

        // TODO Auto-generated method stub

        return 0;

    }

 

    @Override

    public String toString() {

        return "userNo=" + userNo + ", userName=" + userName + ", addreNo="

                + addreNo + ", addreName=" + addreName;

    }

}

BeanKey.java

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

 

/*

 * map输出的key

 */

public class BeanKey implements WritableComparable<BeanKey> {

    private int AddreNo;

    private boolean isPrimary; // true:address false:person

 

    public BeanKey(int addreNo, boolean isPrimary) {

        super();

        this.AddreNo = addreNo;

        this.isPrimary = isPrimary;

    }

 

    public BeanKey() {

        super();

        // TODO Auto-generated constructor stub

    }

 

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeInt(AddreNo);

        out.writeBoolean(isPrimary);

 

    }

 

    @Override

    public void readFields(DataInput in) throws IOException {

        this.AddreNo = in.readInt();

        this.isPrimary = in.readBoolean();

 

    }

 

    // partitioner执行时调用hashcode()方法和compareTo()方法

    // compareTo()方法做为shuffle排序的默认方法

    @Override

    public int hashCode() {

        return this.AddreNo; // 按AddreNo进行分组

    }

 

    //用于排序,将相同的AddressNo的地址表和人员表,将地址表放到首位

    @Override

    public int compareTo(BeanKey o) {

        if (this.AddreNo == o.getAddreNo()) { // 若是是同一个AddressNo的数据则判断是Person仍是Address表

            if (this.isPrimary == o.isPrimary()) {  //若是属性相同属于同种类型的表,返回0

                return 0;

            } else {

                return this.isPrimary ? -1 : 1; // true表示Address表 返回更小的值,将排至values队首

            }

        } else {

            return this.AddreNo - o.getAddreNo() > 0 ? 1 : -1;  //按AddressNo排序

        }

    }

 

    public int getAddreNo() {

        return AddreNo;

    }

 

    public void setAddreNo(int addreNo) {

        AddreNo = addreNo;

    }

 

    public boolean isPrimary() {

        return isPrimary;

    }

 

    public void setPrimary(boolean isPrimary) {

        this.isPrimary = isPrimary;

    }

}

PersonAddrMap.java

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

 

/*

 * map类使key,value分别进行处理

 */

public class PersonAddreMap extends Mapper<LongWritable, Text, BeanKey, Bean> {

    @Override

    protected void map(LongWritable key, Text value,

                       Mapper<LongWritable, Text, BeanKey, Bean>.Context context)

            throws IOException, InterruptedException {

        String line = value.toString();

        String str[] = line.split(" ");

        if (str.length == 2) {

            // Addre表

            Bean Addre = new Bean();

            Addre.setAddreNo(str[0]);

            Addre.setAddreName(str[1]);

 

            BeanKey AddreKey = new BeanKey();

            AddreKey.setAddreNo(Integer.parseInt(str[0]));

            AddreKey.setPrimary(true); // true表示地区表

            context.write(AddreKey, Addre);

        } else {

            // Person表

            Bean Person = new Bean();

            Person.setUserNo(str[0]);

            Person.setUserName(str[1]);

            Person.setAddreNo(str[2]);

 

            BeanKey PerKey = new BeanKey();

            PerKey.setAddreNo(Integer.parseInt(str[2]));

            PerKey.setPrimary(false);// false表示人员表

            context.write(PerKey, Person);

 

        }

    }

 

}

 

PersonAddreRedu.java

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

import java.io.IOException;

 

public class PersonAddreReduce extends Reducer<BeanKey, Bean, NullWritable, Text> {

    @Override

    protected void reduce(BeanKey key, Iterable<Bean> values,

                          Reducer<BeanKey, Bean, NullWritable, Text>.Context context)

            throws IOException, InterruptedException {

        Bean Addre = null;

        int num = 0;

        for (Bean bean : values) {

            if (num == 0) {

                Addre = new Bean(bean); // Address地址表为values的第一个值

                num++;

            } else {

                // 其他全为person表

                // 没有list数组,节省大量内存空间

                bean.setAddreName(Addre.getAddreName());

                context.write(NullWritable.get(), new Text(bean.toString()));

            }

        }

    }

}

 

PersonAddreRedu.java

import org.apache.hadoop.io.NullWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

 

import java.io.IOException;

 

public class PersonAddreReduce extends Reducer<BeanKey, Bean, NullWritable, Text> {

    @Override

    protected void reduce(BeanKey key, Iterable<Bean> values,

                          Reducer<BeanKey, Bean, NullWritable, Text>.Context context)

            throws IOException, InterruptedException {

        Bean Addre = null;

        int num = 0;

        for (Bean bean : values) {

            if (num == 0) {

                Addre = new Bean(bean); // Address地址表为values的第一个值

                num++;

            } else {

                // 其他全为person表

                // 没有list数组,节省大量内存空间

                bean.setAddreName(Addre.getAddreName());

                context.write(NullWritable.get(), new Text(bean.toString()));

            }

        }

    }

}

 

PKFKCompartor.java

import org.apache.hadoop.io.WritableComparable;

import org.apache.hadoop.io.WritableComparator;

 

/*

 * 实现Group分组

 * shuffle的group过程默认的是使用的key(BeanKey)的compareTo()方法

 * 刚才咱们添加的自定义的Key没有办法将具备相同AddressNo的地址和人员放到同一个group中(由于从compareTo()方法中能够看出他们是不相等的)

 * 咱们须要的就是本身定义一个groupComparer就能够

 * 实现比较器

 */

public class PKFKCompartor extends WritableComparator {

 

    protected PKFKCompartor() {

        super(BeanKey.class, true);

    }

 

    //两个BeanKey进行比较排序

    @Override

    public int compare(WritableComparable a, WritableComparable b) {

        BeanKey a1 = (BeanKey) a;

        BeanKey b1 = (BeanKey) b;

        if (a1.getAddreNo() == b1.getAddreNo()) {

            return 0;

        } else {

            return a1.getAddreNo() > b1.getAddreNo() ? 1 : -1;

        }

    }

}

 

PersonAddreMain.java

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 PersonAddreMain {

    public static void main(String[] args) throws Exception {

 

        args = new String[]{"F:\\A\\join\\", "F:\\A\\out_Andy1"};

 

        Configuration conf = new Configuration();

        Job job = Job.getInstance(conf);

        job.setJarByClass(PersonAddreMain.class);

 

        //设置自定义的group

        job.setGroupingComparatorClass(PKFKCompartor.class);

 

        job.setMapperClass(PersonAddreMap.class);

        job.setMapOutputKeyClass(BeanKey.class);

        job.setMapOutputValueClass(Bean.class);

 

        job.setReducerClass(PersonAddreRedu.class);

        job.setOutputKeyClass(NullWritable.class);

        job.setOutputValueClass(Text.class);

 

        FileInputFormat.addInputPath(job, new Path(args[0]));

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

        job.waitForCompletion(true);

    }

}

相关文章
相关标签/搜索