MapReduce编程

MapReduce编程

一、MapReduce编程规范

  1. Mapper阶段

    1. 用户自定义的Mapper要继承自己的父类。
    2. Mapper输入的数据是K V对的形式(K V的类型可自定义)。
    3. Mapper中的业务逻辑写在map()方法中。
    4. Mapper的输出数据是K V对的形式(K V的类型可自定义)。
    5. map()方法(MapTask进程)对每一个<K, V>调用一次。
  2. Reduce阶段

    1. 用户自定义的Reduce要继承自己的父类。
    2. Reduce的输入数据类型对应Mapper的输出数据类型,也是K V
    3. Reduce的业务逻辑写在reduce()方法中。
    4. ReduceTask进程对每一组相同的<K, V>组调用一次reduce()方法。
  3. Driver阶段

    相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。

MapReduce内置了很多默认属性,比如:排序、分组等都和数据的K有关。

二、序列化机制

介绍:

  • 序列化Serialization):将结构化对象转换为字节流,方便进行网络传输或写入持久存储过程
  • 反序列化Deserialization):将字节流转换为一系列结构化对象的过程,相当于重新创建该对象。

Hadoop的序列化机制

Hadoop通过Writable接口实现序列化机制,接口提供两个方法writereadFields

  • write叫做序列化方法,用于把对象指定的字段码写出去。
  • readFields叫做反序列化方法,用于从字节流中读取字段重构对象。

Hadoop没有提供对象比较功能,所以java中的Comparable接口合并,提供一个接口WritableComparableWritableComparable接口可用于用户自定义对象的比较规则。

三、Hadoop封装的数据类型

Hadoop数据类型Java数据类型
BooleanWritableboolean
ByteWritablebyte
IntWritableint
FloatWritablefloat
LongWritablelong
DoubleWritabledouble
TextString
MapWritablemap
ArrayWritablearray
NullWritablenull

四、案例一: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();

    }
}
  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值