MapReduce编程
一、MapReduce编程规范
-
Mapper阶段
- 用户自定义的
Mapper
要继承自己的父类。 Mapper
输入的数据是K V
对的形式(K V
的类型可自定义)。Mapper
中的业务逻辑写在map()
方法中。Mapper
的输出数据是K V
对的形式(K V
的类型可自定义)。map()
方法(MapTask
进程)对每一个<K, V>
调用一次。
- 用户自定义的
-
Reduce阶段
- 用户自定义的
Reduce
要继承自己的父类。 Reduce
的输入数据类型对应Mapper
的输出数据类型,也是K V
。Reduce
的业务逻辑写在reduce()
方法中。ReduceTask
进程对每一组相同的<K, V>
组调用一次reduce()
方法。
- 用户自定义的
-
Driver阶段
相当于
YARN
集群的客户端,用于提交我们整个程序到YARN
集群,提交的是封装了MapReduce
程序相关运行参数的job
对象。
MapReduce
内置了很多默认属性,比如:排序、分组等都和数据的K
有关。
二、序列化机制
介绍:
- 序列化(
Serialization
):将结构化对象转换为字节流,方便进行网络传输或写入持久存储过程 - 反序列化(
Deserialization
):将字节流转换为一系列结构化对象的过程,相当于重新创建该对象。
Hadoop的序列化机制
Hadoop
通过Writable
接口实现序列化机制,接口提供两个方法write
和readFields
。
write
叫做序列化方法,用于把对象指定的字段码写出去。readFields
叫做反序列化方法,用于从字节流中读取字段重构对象。
Hadoop
没有提供对象比较功能,所以java
中的Comparable
接口合并,提供一个接口WritableComparable
,WritableComparable
接口可用于用户自定义对象的比较规则。
三、Hadoop封装的数据类型
Hadoop数据类型 | Java数据类型 |
---|---|
BooleanWritable | boolean |
ByteWritable | byte |
IntWritable | int |
FloatWritable | float |
LongWritable | long |
DoubleWritable | double |
Text | String |
MapWritable | map |
ArrayWritable | array |
NullWritable | null |
四、案例一:WordCount
本案例是一个统计单词数量的java代码,依照此代码为模板,进行入门Mapreduce
编程。
问题:统计给出文本中所有单词的出现次数。
0. data.txt
这是一个需要操作的文本文件
a good beginning is half the battel
where there is a will there is a way
1. WordCountMapper
package WordCount;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
* @description: WordCount Mapper类 对应Maptask
* KEYIN: 表示map阶段输入kv中的k类型, 在默认组件下 是起始位置的偏移量,因此是LongWritable
* VALUEIN:表示map阶段输入kv中的v类型, 在默认组件下 是每一行的内容 因此是TEXT
* todo MapReduce 有默认的读取数据的组件,叫做TextInputFormat
* todo 读取的行为是:一行一行读取数据 返回kv键值对
* k: 每一行的起始位置的偏移量 通常无意义
* v: 这一行的文本内容
* KEYOUT: 表示map阶段输出kv中的k类型,跟业务相关 本需求中输出的是单词 因此是TEXT
* VALUEOUT: 表示map阶段输出kv中的v类型, 跟业务相关 本需求中输出的是单词的次数1 因此是LongWritable
* */
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
private Text outKey = new Text();
private final static LongWritable outValue = new LongWritable(1);
/**
* map方法是mapper阶段的核心方法,也是具体业务逻辑实现的方法
* 注意,该方法调用的次数和输入的kv键值对有关,每当TextInputFormat读取返回一个kv键值对,就调用一次map方法进行业务处理
* 默认情况下,map方法是基于行来处理数据的
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 拿取一行数据转换String
String line = value.toString();
// 根据分隔符进行分割
String[] words = line.split("\\s+");
// 遍历数组
for(String word : words){
// 输出数据 把每一个单词标记1 输出的结果<单词, 1>
// 使用上下文对象 将数据输出
// context.write(new Text(word), new LongWritable(1));
outKey.set(word); // 这样写可以为了避免重复的new对象
context.write(outKey, outValue);
}
}
}
2. WordCountReducer
package WordCount;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* KEYIN:表示的是reduce阶段输入kv中k的类型, 对应map的输出的key 因此本需求中就是单词Text
* VALUEIN: 表示的是reduce阶段输入kv中v的类型 对应map的输出value 因此本需求中就是单词次数1 LongWritable
*
* KEYOUT:表示的是reduce阶段输出kv中k的类型, 跟业务相关, 本需求中是单词Text
* VALUEOUT:表示的是reduce阶段输出kv中v的类型, 跟业务相关, 本需求中是单词次数LongWritable
*/
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable>{
private LongWritable outValue = new LongWritable(); // 用于存储全局的输出变量
/**
* todo
* 1. 排序 规则:根据key的字典序进行排序 a-z
* 2. 分组 规则:key相同的分为一组
* 3. 分组之后,同一组的数据组成一个新的kv键值对, 调用一次reduce方法。 reduce方法基于分组调用的,一个分组调用一次
* todo 同一组中数据组成一个新的kv键值对
* 新key:该组共同key
* 新value:高祖素有的value组成要给迭代器Iterator
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
// 统计变量
long count = 0;
// 遍历该数组的values
for(LongWritable value : values) {
// 累加计算总次数
count += value.get();
}
outValue.set(count);
// 最终使用上下文对象输出结果
context.write(key, outValue);
}
}
3. WordCountDriver
package WordCount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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;
/**
* @description: 该类就是一个MapReduce程序客户端驱动类,主要是构建Job对象实例
* 指定各种组件属性 包括:mapper reducer类,输入输出的数据类型,输入输出的数据路径
* 提交Job作业 job.submit()
*/
public class WordCountDirver {
public static void main(String[] args) throws Exception {
// 创建配置对象
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.9.4:8020");
// 构建Job作业的实例 参数(配置对象,Job名字)
Job job = Job.getInstance(conf, WordCountDirver.class.getSimpleName());
// 设置mpareduce程序运行的主类
job.setJarByClass(WordCountDirver.class);
// 设置本次mapreduce程序的mappre类 reduce类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 指定mapper阶段输出的key value类型
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 指定reducer阶段输出的key value类型 也是mapreduce最终的输出数据类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 配置本次作业输入数据路径和输出数据路径
// todo 默认组件 TextInputFormat TextOutputFormat
// FileInputFormat.setInputPaths(job, new Path(args[0]));
// FileOutputFormat.setOutputPath(job, new Path(args[1]));
FileInputFormat.setInputPaths(job, new Path("hdfs://192.168.9.4:8020/user/hadoop/input/data.txt"));
FileOutputFormat.setOutputPath(job, new Path("output/wordCount"));
// 最终提交本次job作业
// 采用waitForCompletion提交job 参数表示是否开启试试监视追踪作业的执行情况
boolean resultflag = job.waitForCompletion(true);
System.exit(resultflag ? 0: 1);
}
}
五、案例二:员工排序
0.emp.csv
这是一个需要操作的文本文件
7369,SMITH,CLERK,7902,1980/12/17,800,,20
7499,ALLEN,SALESMAN,7698,1981/2/20,1600,300,30
7521,WARD,SALESMAN,7698,1981/2/22,1250,500,30
7566,JONES,MANAGER,7839,1981/4/2,2975,,20
7654,MARTIN,SALESMAN,7698,1981/9/28,1250,1400,30
7698,BLAKE,MANAGER,7839,1981/5/1,2850,,30
7782,CLARK,MANAGER,7839,1981/6/9,2450,,10
7788,SCOTT,ANALYST,7566,1987/4/19,3000,,20
7839,KING,PRESIDENT,,1981/11/17,5000,,10
7844,TURNER,SALESMAN,7698,1981/9/8,1500,0,30
7876,ADAMS,CLERK,7788,1987/5/23,1100,,20
7900,JAMES,CLERK,7698,1981/12/3,950,,30
7902,FORD,ANALYST,7566,1981/12/3,3000,,20
7934,MILLER,CLERK,7782,1982/1/23,1300,,10
1. EmployeeSortMapper
package mydir;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class EmployeeSortMapper extends Mapper<LongWritable,Text,Employee, NullWritable> {
// 从写map类
@Override
protected void map(LongWritable key1, Text value1, Context context) throws IOException, InterruptedException {
String data = value1.toString();
String[] words = data.split(","); // 以逗号进行分割,进行读取数据。
Employee e = new Employee();
e.setEmpno(Integer.parseInt(words[0]));
e.setEname(words[1]);
e.setJob(words[2]);
try{
e.setMgr(Integer.parseInt(words[3]));
}catch (Exception ex){
e.setMgr(-1);
}
e.setHiredate(words[4]);
e.setSal(Integer.parseInt(words[5]));
try{
e.setComm(Integer.parseInt(words[6]));
}catch (Exception ex){
e.setComm(0);
}
e.setDeptno(Integer.parseInt(words[7]));
context.write(e,NullWritable.get());
}
}
2. Employee
package mydir;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class Employee implements WritableComparable<Employee> {
private int empno;
private String ename;
private String job;
private int mgr;
private String hiredate;
private int sal;
private int comm;
private int deptno;
@Override
public String toString(){
return "Employee[empno="+empno+",ename="+ename+",sal="+sal+",deptno="+deptno+"]";
}
@Override
public int compareTo(Employee o) { // 重写排序中的比较函数
//多个列的排序:select * from emp order by deptno,sal;
//首先按照deptno排序
if(this.deptno >o.getDeptno()){
return 1;
}else if(this.deptno < o.getDeptno()){
return -1;
}
//如果deptno相等,按照sal排序
if(this.sal >= o.getSal()){
return 1;
}else{
return -1;
}
}
@Override
public void write(DataOutput output) throws IOException { // 将对象构建为字节流
//序列化
output.writeInt(this.empno);
output.writeUTF(this.ename);
output.writeUTF(this.job);
output.writeInt(this.mgr);
output.writeUTF(this.hiredate);
output.writeInt(this.sal);
output.writeInt(this.comm);
output.writeInt(this.deptno);
}
@Override
public void readFields(DataInput input) throws IOException { // 从字节流从新构建对象
//反序列化
this.empno = input.readInt();
this.ename = input.readUTF();
this.job = input.readUTF();
this.mgr = input.readInt();
this.hiredate = input.readUTF();
this.sal = input.readInt();
this.comm = input.readInt();
this.deptno = input.readInt();
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public int getMgr() {
return mgr;
}
public void setMgr(int mgr) {
this.mgr = mgr;
}
public String getHiredate() {
return hiredate;
}
public void setHiredate(String hiredate) {
this.hiredate = hiredate;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
public int getComm() {
return comm;
}
public void setComm(int comm) {
this.comm = comm;
}
public int getDeptno() {
return deptno;
}
public void setDeptno(int deptno) {
this.deptno = deptno;
}
}
3. EmployeeSortMain
package mydir;
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 EmployeeSortMain {
public static void main(String[] args) throws Exception{
//创建一个job
Job job = Job.getInstance(new Configuration());
job.setJarByClass(EmployeeSortMain.class); // 设置主类
//指定job的mapper和输出的类型 k2 v2
job.setMapperClass(EmployeeSortMapper.class); // 指定mapper
job.setOutputKeyClass(Employee.class);
job.setMapOutputValueClass(NullWritable.class);
//设置文件的输入输出路径,输出路径可以是hdfs上的路径,也可以是本地的路径。
FileInputFormat.setInputPaths(job,new Path("hdfs://master:8020/user/hadoop/input/emp.csv"));
FileOutputFormat.setOutputPath(job,new Path("hdfs://master:8020/user/hadoop/output/out-emp"));
//提交程序,并且监控打印程序执行的结果
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
六、案例三:最大薪资
0.emp.csv
这是一个需要操作的文本文件
7369,SMITH,CLERK,7902,1980/12/17,800,,20
7499,ALLEN,SALESMAN,7698,1981/2/20,1600,300,30
7521,WARD,SALESMAN,7698,1981/2/22,1250,500,30
7566,JONES,MANAGER,7839,1981/4/2,2975,,20
7654,MARTIN,SALESMAN,7698,1981/9/28,1250,1400,30
7698,BLAKE,MANAGER,7839,1981/5/1,2850,,30
7782,CLARK,MANAGER,7839,1981/6/9,2450,,10
7788,SCOTT,ANALYST,7566,1987/4/19,3000,,20
7839,KING,PRESIDENT,,1981/11/17,5000,,10
7844,TURNER,SALESMAN,7698,1981/9/8,1500,0,30
7876,ADAMS,CLERK,7788,1987/5/23,1100,,20
7900,JAMES,CLERK,7698,1981/12/3,950,,30
7902,FORD,ANALYST,7566,1981/12/3,3000,,20
7934,MILLER,CLERK,7782,1982/1/23,1300,,10
1. MaxSal
package five;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class MaxSal {
public static class Map extends Mapper<Object, Text, Text, Text> {
private Text job = new Text();
private Text salary = new Text();
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// 解析输入的每行数据,假设输入数据格式为:empno,ename,job,mgr,hiredate,sal,comm,deptno
String[] fields = value.toString().split(",");
job.set(fields[2]); // 设置职位为键
salary.set(fields[5]); // 设置工资为值
context.write(job, salary); // 发送键值对到Reducer
}
}
public static class Reduce extends Reducer<Text, Text, Text, Text> {
public void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
int maxSalary = Integer.MIN_VALUE;
// 找出每种职位的最高工资
for (Text val : values) {
int salary = Integer.parseInt(val.toString());
if (salary > maxSalary) {
maxSalary = salary;
}
}
context.write(key, new Text(String.valueOf(maxSalary))); // 输出最高工资
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "max salary per job");
job.setJarByClass(MaxSal.class);
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// FileInputFormat.addInputPath(job, new Path(args[0]));
FileInputFormat.addInputPath(job, new Path("hdfs://master:8020/user/hadoop/input/emp.csv"));
// FileOutputFormat.setOutputPath(job, new Path(args[1]));
FileOutputFormat.setOutputPath(job, new Path("hdfs://master:8020/user/hadoop/output/maxsal"));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
七、文件的操作
通过java编程,在hdfs上进行文件的创建、删除、上传、下载功能。
1. 创建文件
package ChangeFile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
public class CreateFile {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://master:8020"); // 设置属性,fs.defaultFS表示Hadoop配置属性的键,用于指定默认文件系统的URI,hdfs://master:8020表示Hadoop分布式文件系统(HDFS)的URI
FileSystem fs = FileSystem.get(conf); // 进行连接到hdfs的文件系统
Path path_dfs = new Path("/user/hadoop/hdfs-test/data.txt"); // 设置要在hdfs那个位置创建文件
boolean created = fs.createNewFile(path_dfs); // 进行创建文件
if(created){
System.out.println("success!");
}else {
System.out.println("failed!");
}
fs.close();
}
}
2. 删除文件
package ChangeFile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
public class DeleteFile {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://master:8020");
FileSystem fs = FileSystem.get(conf);
Path path_dfs = new Path("/user/hadoop/hdfs-test/data.txt");
boolean deleted = fs.delete(path_dfs);
if(deleted){
System.out.println("success!");
}else {
System.out.println("failed!");
}
fs.close();
}
}
3. 下载文件
package ChangeFile;
import com.sun.javaws.IconUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
public class LoadFile {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://master:8020");
FileSystem fs = FileSystem.get(conf);
Path local_path = new Path("/home/hadoop/Mydata");
Path hdfs_path = new Path("/user/hadoop/hdfs-test/data.txt");
fs.copyToLocalFile(hdfs_path, local_path);
System.out.println("success!");
fs.close();
}
}
4. 上传文件
package ChangeFile;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
public class UploadFile {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://master:8020"); // 设置属性,fs.defaultFS表示Hadoop配置属性的键,用于指定默认文件系统的URI,hdfs://master:8020表示Hadoop分布式文件系统(HDFS)的URI
FileSystem fs = FileSystem.get(conf); // 得到hdfs的分布式文件系统
Path localFIlePaht = new Path("/home/hadoop/test.txt"); // 设置文件的本地路径
Path hdfsDirPath = new Path("/user/hadoop/hdfs-test"); // 设置文件要上传到hdfs上的位置
// 判断文件夹是否已经存在,如果不存在就创建这个文件夹
if(!fs.exists(hdfsDirPath)){
fs.mkdirs(hdfsDirPath);
}
fs.copyFromLocalFile(localFIlePaht, hdfsDirPath); // 将本地的文件上传(拷贝到hdfs对应的位置)
System.out.println("success!");
fs.close();
}
}