Hadoop 学习日记(3)
MapReduce 概述
MapReduce 定义
MapReduce 是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个 Hadoop 集群上。
MapReduce 定义
1、优点
(1) MapReduce 易于编程
(2) 良好的扩展性
(3) 高容错性
(4) 适合 PB 级以上海量数据的离线处理
2、缺点
(1) 不擅长实时计算
(2) 不擅长流式计算
(3) 不擅长 DAG 计算
MapReduce 核心思想
(1) 分布式的运算程序往往需要分成至少2个阶段
(2) 第一个阶段的 MapTask 并发实例,完全并行运行,互不相干
(3) 第二个阶段的 ReduceTask 并发实例互不相干,但是他们的数据依赖于上一个阶段的所有 MapTask 并发实例的输出。
MapReduce 进程
一个完整的 MapReduce 程序在分布式运行时有三类实例进程:
(1) MrAppMaster:负责整个程序的过程调度及状态协调
(2) MapTask:负责 Map 阶段的整个数据处理流程
(3) ReduceTask:负责 Reduce 阶段的整个数据处理流程
常用的数据序列化类型
MapReduce 编程规范
用户编写的程序分为三个部分:Mapper、Reducer、Driver
1、Mapper 阶段
(1) 用户自定义的 Mapper 要继承自己的父类
(2) Mapper 的输入数据是 KV 对的形式(KV的类型可以自定义)
(3) Mapper 中的业务逻辑写在 map() 方法中
(4) Mapper 的输出数据是 KV 对的形式 (KV 的类型可以自定义)
(5) map() 方法 (MapTask进程) 对每个 <K,V> 调用一次
2、Reducer 阶段
(1) 用户自定义的 Reducer 要继承自己的父类
(2) Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV
(3) Reducer 的业务逻辑写在 reduce() 方法中
(4) ReduceTask 进程对每一组相同 k 的 <k,v> 组调用一次 reduce() 方法
3、Driver 阶段
相当于 YARN 集群的客户端,用于提交我们整个程序到 YARN 集群,提交的是封装了 MapReduce 程序相关运行参数的 job 对象
WordCount 案例实操
本地测试
(1) 需求
在给定的文本文件中统计输出每一个单词出现的次数。
(2) 需求分析
编写 WordCount
创建 Maven 项目
添加依赖
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
在项目的 src/main/resources 目录下,新建一个文件,命名为 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
创建包名:com.an.mapreduce.wordcount
Mapper 类
package com.an.mapreduce.wordcount;
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;
/**
* @author an
* @create 2021-06-08-16:35
*/
/**
* KEYIN,map阶段输入的key的类型,LongWritable类型
* VALUEIN,map阶段输入value类型,Text类型
* KEYOUT,map阶段输出的key类型:Text类型
* VALUEOUT,map阶段输出的value类型,IntWritable类型
*
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text outK = new Text();
private IntWritable outV = 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) {
// 封装 outK
outK.set(word);
// 写出
context.write(outK,outV);
}
}
}
Reducer 类
package com.an.mapreduce.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-16:36
*/
/**
* KEYIN,reducer阶段输入的key的类型,Text
* VALUEIN,reducer阶段输入value类型,IntWritable
* KEYOUT,reducer阶段输出的key类型,Text
* VALUEOUT,reducer阶段输出的value类型,IntWritable类型
*/
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
IntWritable outV = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 累加
for (IntWritable value : values) {
sum += value.get();
}
outV.set(sum);
// 写出
context.write(key,outV);
}
}
Driver 类
package com.an.mapreduce.wordcount;
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;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-16:36
*/
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取 job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置 jar 包路径
job.setJarByClass(WordCountDriver.class);
// 3 关联 Mapper 、Reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 设置 Mapper 的输出的 K,V 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出的 K,V 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交 job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
上述程序在本机运行,下面将这些代码上传到虚拟机运行
在 pom.xml 添加打包的插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
再使用 Maven 进行打包
将打好的带依赖的 jar 包上传至服务器
重命名为 wc.jar,方便调用
上传 input 文件夹,带有 word.txt 包含待统计的单词
hadoop jar wc.jar com.an.mapreduce.wordcount.WordCountDriver /input /output
Hadoop 序列化
序列化概述
序列化就是把内存中的对象,转化成字节序列以便于存储到磁盘(持久化)和网络传输。
反序列化就是将收到的字节序列转化为内存中的对象。
为什么不用 Java 的序列化?
Java 的序列化是一个重量级序列化框架,一个对象被序列化后,会附带很多额外信息,不便于在网络中传输。
序列化实操
1、需求
统计每个手机号耗费的总上行流量、总下行流量、总流量
输入数据格式
7 13560436666 120.196.100.99 1116 954 200
id 手机号码 网络 ip 上行流量 下行流量 网络状态码
期望输出数据格式
13560436666 1116 954 2070
手机号码 上行流量 下行流量 总流量
2、需求分析
3、具体实现
待序列化的对象 Bean 对象
package com.an.mapreduce.writable;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* 1、定义类实现 writable 接口
* 2、重写序列化与反序列化方法
* 3、重写空参构造
* 4、重写 toString 方法
*/
public class FlowBean implements Writable {
private long upFlow; // 上行流量
private long downFlow; // 下行流量
private long sumFlow; // 总流量
public FlowBean() {
}
public long getUpFlow() {
return upFlow;
}
public long getDownFlow() {
return downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public void setSumFlow() {
this.sumFlow = this.upFlow + this.downFlow;
}
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
@Override
public void readFields(DataInput in) throws IOException {
this.upFlow = in.readLong();
this.downFlow = in.readLong();
this.sumFlow = in.readLong();
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow ;
}
}
Mapper
package com.an.mapreduce.writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:06
*/
public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
private Text outK = new Text();
private FlowBean outV = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割
String[] split = line.split("\t");
// 3 抓取想要的数据
// 手机号
String phone = split[1];
// 上行和下行流量
String up = split[split.length - 3];
String down = split[split.length - 2];
// 4 封装
outK.set(phone);
outV.setUpFlow(Long.parseLong(up));
outV.setDownFlow(Long.parseLong(down));
outV.setSumFlow();
// 5 写出
context.write(outK,outV);
}
}
Reducer
package com.an.mapreduce.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:15
*/
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
private FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
// 1 变量集合累加值
long totalUp = 0;
long totaldown = 0;
for (FlowBean value:values) {
totalUp += value.getUpFlow();
totaldown += value.getDownFlow();
}
// 2 封装 outK,outV
outV.setUpFlow(totalUp);
outV.setDownFlow(totaldown);
outV.setSumFlow();
// 3 写出
context.write(key,outV);
}
}
FlowDriver
package com.an.mapreduce.writable;
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;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:36
*/
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取 job
Configuration conf = new Configuration();
Job job = Job.getInstance();
// 2 获取 jar
job.setJarByClass(FlowDriver.class);
// 3 管理 mapper 和 reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
// 4 设置 mapper 输出的 key 和 value 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 5 设置最终输出的 key 和 value 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 6 设置数据的输入路径和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\input\\inputflow"));
FileOutputFormat.setOutputPath(job,new Path("D:\\output\\output3"));
// 7 提交 job
job.waitForCompletion(true);
}
}
MapReduce 框架原理
InputFormat 数据输入
1、切片与 MapTask 并行度决定机制
- 问题引出
MapTask 并行度决定 Map 阶段的任务处理并发度,进而影响到整个 Job 的处理速度。
2)MapTask 并行度决定机制
数据块:Block 是 HDFS 物理上吧数据分为一块一块,数据块是 HDFS 存储数据单位。
数据切片:数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其分片存储。数据切片是 MapReduce 程序计算输入数据的单位,一个切片会对应启动一个 MapTask。
2、Job 提交流程源码和切片源码详解
1)Job 提交流程源码详解
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);
// 5)向 Stag 路径写 XML 配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交 Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(),
job.getCredentials());
2)FileInputFormat 切片源码解析
(1) 程序先找到你数据存储的目录
(2) 开始遍历处理(规划切片)目录下的每一个文件
(3) 遍历第一个文件 ss.txt
a) 获取文件大小 fs.sizeOf(ss.txt)
b) 计算切片大小
computeSplitSize(Math.max(minSize,Math.min(maxSize,blockSize)))=blocksize=128M
c) 默认情况下,切片大小=blocksize
d) 开始切,形成第一个切片:ss.txt - 0:128M 第二个切片 ss.txt - 128-256M 第三个切片 ss.txt 256:300M
e) 将切片信息写到一个切片规划文件中
f) 整个切片的核心过程在 getSplit() 方法中完成
g) InputSplit 只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等
(4) 提交切片规划文件到 YARN 上,YARN 上的 MrAppMaster 可以根据切片规划文件计算开启 MapTask 个数
Shuffle 机制
1、Shuffle 机制
Map 方法之后,Reduce 方法之前的数据处理过程称为 Shuffle。
2、Patition 分区
(1) 问题
要求将统计结果按照条件输出到不同文件中(分区)。比如:将统计结果按照手机归属地不同省份输出到不同文件中(分区)。
(2) 默认 Patition 分区
按照哈希值分区。
3、自定义 Partitioner 步骤
(1) 自定义类继承 Partitioner,重写 getPartition() 方法
(2) 在 Job 驱动中,设置自定义 Partitioner
(3) setNumReduceTasks()
4、案例实操
手机号 136、137、138、139 开头都分别放到一个独立的 4 个文件中,其他开头的放到一个文件中。
Partitioner 类
package com.an.mapreduce.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* @author an
* @create 2021-06-09-10:52
*/
public class ProvincePartitioner extends Partitioner<Text,FlowBean> {
@Override
public int getPartition(Text text, FlowBean flowBean, int i) {
String phone = text.toString();
String prePhone = phone.substring(0, 3);
int partition;
if("136".equals(prePhone)) {
partition=0;
}else if("137".equals(prePhone)) {
partition=1;
}else if("138".equals(prePhone)) {
partition=2;
}else if("139".equals(prePhone)) {
partition=3;
}else {
partition=4;
}
return partition;
}
}
Driver 类
package com.an.mapreduce.writable;
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;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:36
*/
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取 job
Configuration conf = new Configuration();
Job job = Job.getInstance();
// 2 获取 jar
job.setJarByClass(FlowDriver.class);
// 3 管理 mapper 和 reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
// 4 设置 mapper 输出的 key 和 value 类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 5 设置最终输出的 key 和 value 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
job.setPartitionerClass(ProvincePartitioner.class);
job.setNumReduceTasks(5);
// 6 设置数据的输入路径和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\input\\inputflow"));
FileOutputFormat.setOutputPath(job,new Path("D:\\output\\output5"));
// 7 提交 job
job.waitForCompletion(true);
}
}
排序
排序是 MapReduce 框架中最重要的操作之一
MapTask 和 ReduceTask 均会对数据按照 key 进行排序。该操作属于 Hadoop 的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
对于 MapTask,它会将处理的结果暂时先放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,当数据处理完毕后,会对磁盘所有文件进行归并排序。
对于 ReduceTask,它从每个 MapTask 上远程拷贝相应的文件,如果文件大小超过一定阈值,则溢写到磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask 统一对内存和数据和磁盘上的所有数据进行一次归并排序。
(1) 部分排序
MapReduce 根据输入记录对数据集进行排序。保证输入的每个文件内部有序。
(2) 全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置了一个 ReduceTask。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了 MapReduce 所提供的并行架构。
(3) 辅助排序
在 Reduce 端对 key 进行分组。应用于:在接收的 key 为 bean 对象时,想让一个或几个字段相同的 key 进入到相同。
(4) 二次排序
在自定义排序过程中,如果 compareTo 中的判断条件为两个即为二次排序。
案例
按照之前序列化产生的结果再次对总流量进行倒序排序
mapper
package com.an.mapreduce.writableComparable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:06
*/
public class FlowMapper extends Mapper<LongWritable, Text, FlowBean ,Text> {
private FlowBean outK = new FlowBean();
private Text outV = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割
String[] split = line.split("\t");
// 封装
outV.set(split[0]);
outK.setUpFlow(Long.parseLong(split[1]));
outK.setDownFlow(Long.parseLong(split[2]));
outK.setSumFlow();
// 写出
context.write(outK,outV);
}
}
reducer
package com.an.mapreduce.writableComparable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:15
*/
public class FlowReducer extends Reducer< FlowBean,Text,Text, FlowBean> {
private FlowBean outV = new FlowBean();
@Override
protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value : values) {
context.write(value,key);
}
}
}
driver
package com.an.mapreduce.writableComparable;
import com.an.mapreduce.writableComparable.ProvincePartitioner;
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;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:36
*/
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取 job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 获取 jar
job.setJarByClass(FlowDriver.class);
// 3 管理 mapper 和 reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
// 4 设置 mapper 输出的 key 和 value 类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(Text.class);
// 5 设置最终输出的 key 和 value 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
job.setPartitionerClass(ProvincePartitioner.class);
// 6 设置数据的输入路径和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\input\\input3"));
FileOutputFormat.setOutputPath(job,new Path("D:\\output\\output7"));
// 7 提交 job
job.waitForCompletion(true);
}
}
案例2
- 需求
要求每个省份手机号输出的文件中按照总流量内部排序。
2)需求分析
基于前一个需求,增加自定义分区类,分区按照省份手机号设置。
partitioner
package com.an.mapreduce.partitionerandwritableComparable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* @author an
* @create 2021-06-09-10:52
*/
public class ProvincePartitioner extends Partitioner<FlowBean,Text> {
@Override
public int getPartition(FlowBean flowBean, Text text, int i) {
String phone = text.toString();
String prePhone = phone.substring(0,3);
int partition;
if("136".equals(prePhone)) {
partition=0;
}else if("137".equals(prePhone)) {
partition=1;
}else if("138".equals(prePhone)) {
partition=2;
}else if("139".equals(prePhone)) {
partition=3;
}else {
partition=4;
}
return partition;
}
}
driver
package com.an.mapreduce.partitionerandwritableComparable;
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;
import java.io.IOException;
/**
* @author an
* @create 2021-06-08-20:36
*/
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取 job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 获取 jar
job.setJarByClass(FlowDriver.class);
// 3 管理 mapper 和 reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
// 4 设置 mapper 输出的 key 和 value 类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(Text.class);
// 5 设置最终输出的 key 和 value 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
job.setPartitionerClass(ProvincePartitioner.class);
job.setNumReduceTasks(5);
// 6 设置数据的输入路径和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\input\\input3"));
FileOutputFormat.setOutputPath(job,new Path("D:\\output\\output8"));
// 7 提交 job
job.waitForCompletion(true);
}
}