MapReduce的详细使用

MapReduce

1、常用数据序列化类型

Java 类型Hadoop Writable 类型
BooleanBooleanWritable
ByteByteWritable
IntIntWritable
FloatFloatWritable
LongLongWritable
DoubleDoubleWritable
StringText
MapMapWritable
ArrayArrayWritable
NullNullWritable




2、编程规范(三个阶段)

  • 用户编写的程序分成三个部分:MapperReducerDriver

Mapper阶段

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



Reducer阶段

  • 用户自定义的Reducer要继承自己的父类
  • Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
  • Reducer的业务逻辑写在reduce()方法中
  • ReduceTask进程对每一组相同k的<k,v>组调用一次reduce()方法



Driver阶段

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




3、编程环境准备

  • 添加依赖:创建Maven工程,添加依赖(pom.xml)

    <dependency>
        <!-- 版本号跟自己的Hadoop版本对应 -->
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.1.4</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>
    
  • 添加日志: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
    




4、简单案例(单词统计)

  • Driver驱动类

    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;
    
    public class WordCountDriver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            //1、获取配置信息以及 job 对象
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
    
            //2、关联 Driver 的 jar
            job.setJarByClass(WordCountDriver.class);
    
            //3、关联 Mapper 和 Reducer 的 jar
            job.setMapperClass(WordCountMapper.class);
            job.setReducerClass(WordCountReduce.class);
    
            //4、设置 Mapper 输出的 key-value
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
    
            //5、设置 Reducer 输出的 key-value
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);
    
            //6、设置 输入 和 输出 路径
            FileInputFormat.setInputPaths(job, new Path("E:\\test\\input\\input001"));
            FileOutputFormat.setOutputPath(job, new Path("E:\\test\\output\\output001"));
    
            //7、提交 job
            System.exit(job.waitForCompletion(true) ? 0 : 1);
        }
    }
    
  • Mapper类

    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;
    
    /**
     * Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
     *     KEYIN     输入key的类型
     *     VALUEIN   输入value的类型
     *     KEYOUT    输出key的类型
     *     VALUEOUT  输出value的类型
     */
    public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    
        private Text outKey = new Text();
        private IntWritable outValue = 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) {
                outKey.set(word);
                context.write(outKey, outValue);
            }
        }
    }
    
  • Reducer类

    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;
    import java.io.IOException;
    
    /**
     * Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT>
     *     KEYIN     输入key的类型
     *     VALUEIN   输入value的类型
     *     KEYOUT    输出key的类型
     *     VALUEOUT  输出value的类型
     */
    public class WordCountReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    
        private int sum;
        private IntWritable outValue = new IntWritable();
    
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            //1、累加求和
            sum = 0;
            for (IntWritable value : values) {
                sum += value.get();
            }
            outValue.set(sum);
    
            //2、输出
            context.write(key, outValue);
        }
    }
    




5、序列化

序列化概述

  • 简介
    • 序列化
      • 就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输
    • 反序列化
      • 就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象
  • 序列化特点
    • 紧凑:高效使用存储空间
    • 快速:读写数据的额外开销小
    • 互操作:支持多语言的交互



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

步骤

  • 第一步:bean对象实现 Writable 接口
  • 第二步:反序列化时,需要反射调用空参构造函数,所以必须有空参构造
  • 第三步:重写序列化方法(write方法)
  • 第四步:重写反序列化方法(readFields方法)
  • 第五步:要想把结果显示在文件中,需要重写 toString(),可用"\t"分开,方便后续用
  • 第六步:Driver、Mapper、Reducer 实现类

注意反序列化的顺序和序列化的顺序完全一致



程序(序列化接口)

import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

//1、bean对象实现 Writable 接口
public class FlowBean implements Writable {

    private long upFlow;
    private long downFlow;
    private long sumFlow;

    //2、创建空参构造函数
    public FlowBean(){}

    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() {
        this.sumFlow = this.upFlow + this.downFlow;
    }

    //3、重写序列化方法(write方法)
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeLong(this.upFlow);
        out.writeLong(this.downFlow);
        out.writeLong(this.sumFlow);
    }

    //4、重写反序列化方法(readFields方法)
    @Override
    public void readFields(DataInput in) throws IOException {
        this.upFlow = in.readLong();
        this.downFlow = in.readLong();
        this.sumFlow = in.readLong();
    }

    //5、要想把结果显示在文件中,需要重写 toString(),可用"\t"分开,方便后续用
    @Override
    public String toString() {
        return upFlow + "\t" + downFlow + "\t" + sumFlow;
    }
}




6、InputFormat 数据输入

TextInputFormat(默认)

  • TextInputFormat默认的 FileInputFormat 实现类
    • 按行读取每条记录。
    • 是存储该行在整个文件中的起始字节偏移量LongWritable 类型。
    • 这行的内容,不包括任何行终止符(换行符和回车符),Text 类型



CombineTextInputFormat

  • 应用场景

    • 用于小文件过多的场景,将多个小文件从逻辑上规划到一个切片中,多个小文件就交给一个 MapTask 处理
  • 虚拟存储切片最大值和最小值设置

    • 最大值CombineTextInputFormat.setMinInputSplitSize(Job job, long size)
    • 最小值CombineTextInputFormat.setMaxInputSplitSize(Job job, long size)
    • 注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值(size是)
    • 示例(设置切片最大值为 4M)
      • CombineTextInputFormat.setMaxInputSplitSize(job, 4194304); // 4M
  • 程序(在 Job驱动类 中添加设置)

    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.CombineTextInputFormat;
    import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    import java.io.IOException;
    
    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);
    
            //设置 InputFormat 使用 CombineTextInputFormat,它默认用的是 TextInputFormat
            job.setInputFormatClass(CombineTextInputFormat.class);
            //虚拟存储切片最大值设置 20M
            CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);
            
            FileInputFormat.setInputPaths(job, new Path("C:\\test\\input\\input001"));
            FileOutputFormat.setOutputPath(job, new Path("C:\\test\\output\\output001"));
    
            System.exit(job.waitForCompletion(true) ? 0 : 1);
        }
    }
    




7、Shuffle 机制

Partition 分区

  • 默认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区

自定义Partitioner步骤

  • 第一步:自定义类继承Partitioner重写getPartition()方法

    package com.itfzk.mapreducer.partition;
    
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Partitioner;
    
    public class PartitionTest extends Partitioner<Text, IntWritable> {
        @Override
        public int getPartition(Text text, IntWritable intWritable, int i) {
            int partitioner;
            
            //逻辑处理,确定text写入到哪个分区中
            
            return partitioner;
        }
    }
    
  • 第二步:在Job驱动中,设置自定义Partitioner

    • job.setPartitionerClass(Partition类名.class)
  • 第三步:自定义Partition后,在Job驱动中根据自定义Partitioner的逻辑设置相应数量的ReduceTask

    • job.setNumReduceTasks(ReduceTask的数量)



Job驱动类 示例

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;

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

        //自定义Partitioner
        job.setPartitionerClass(PartitionTest.class);
        
        //设置相应数量的ReduceTask
        job.setNumReduceTasks(4);

        FileInputFormat.setInputPaths(job, new Path("C:\\test\\input\\input001"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\test\\output\\output002"));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}



分区总结

  • 如果 ReduceTask的数量 > getPartition的结果数,则会多产生几个空的输出文件part-r-000xx
  • 如果 1 < ReduceTask的数量 < getPartition的结果数,则有一部分分区数据无处安放,会报错
  • 如果 ReduceTask的数量 = 1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000
  • 分区号必须从零开始,逐一累加



WritableComparable 排序

自定义排序 WritableComparable

  • 创建bean 对象做为 Mapper的输出key 传输,需要实现 WritableComparable 接口重写 compareTo() 方法,就可以实现排序

  • Mapper的输出keyMapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>中的 KEYOUT

    import org.apache.hadoop.io.WritableComparable;
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    
    //实现 WritableComparable
    public class FlowBean implements WritableComparable<FlowBean> {
        
        //字段
        //setting、getting
        //toString
    
        //重写 compareTo()
        @Override
        public int compareTo(FlowBean o) {
            //需要按照哪个字段排序的逻辑
        }
    
        @Override
        public void write(DataOutput dataOutput) throws IOException {
        }
    
        @Override
        public void readFields(DataInput dataInput) throws IOException {
        }
    }
    



Combiner 合并

介绍

  • Combiner是MR程序中Mapper和Reducer之外的一种组件
  • Combiner组件的父类就是Reducer
  • Combiner和Reducer的区别在于运行的位置
    • Combiner是在每一个MapTask所在的节点运行
    • Reducer是接收全局所有Mapper的输出结果
  • Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量
  • Combiner应用的前提是不能影响最终的业务逻辑,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来



Combiner 合并方法一(自定义)

  • 第一步:自定义一个 Combiner类 继承 Reducer重写 Reduce 方法
  • 第二步:在 Job 驱动类中设置
    • job.setCombinerClass(Combiner类名.class)



Combiner 合并方法二

Reducer类的逻辑跟Combiner类的逻辑一样Combiner的输出kv类型跟Reducer的输入kv类型一样

  • 将 Reducer类 作为 Combiner类
    • job.setCombinerClass(Reducer类名.class)




8、OutputFormat 数据输出

自定义输入

  • 第一步:创建 OutputFormat 类,继承 FileOutputFormat重写 RecordWriter() 方法

    import org.apache.hadoop.io.IntWritable;
    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;
    import java.io.IOException;
    
    public class WordCountOutPutFormat extends FileOutputFormat<Text, IntWritable> {
        @Override
        public RecordWriter<Text, IntWritable> getRecordWriter(TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
            //返回自定义RecordWriter类
            WordCountRecordWriter wordCountRecordWriter = new WordCountRecordWriter(taskAttemptContext);
            return wordCountRecordWriter;
        }
    }
    
  • 第二步:创建 RecordWriter 类,继承 RecordWriter重写 write、close 方法

    import org.apache.hadoop.fs.FSDataOutputStream;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.RecordWriter;
    import org.apache.hadoop.mapreduce.TaskAttemptContext;
    import java.io.IOException;
    
    public class WordCountRecordWriter extends RecordWriter<Text, IntWritable> {
    
        private FSDataOutputStream fsDataOutputStream1;
        private FSDataOutputStream fsDataOutputStream2;
    
        //构造函数
        public WordCountRecordWriter(TaskAttemptContext job) throws IOException {
            //按照自己的输出格式,例如:将数据输出到两个文件中(f.log, z.log)
            FileSystem fs = FileSystem.get(job.getConfiguration());
            fsDataOutputStream1 = fs.create(new Path("C:\\test\\f.log"));
            fsDataOutputStream2 = fs.create(new Path("C:\\test\\z.log"));
        }
    
        //输出方法
        @Override
        public void write(Text text, IntWritable intWritable) throws IOException, InterruptedException {
            //按照自己的逻辑将数据输出到不同的文件中
            if(text.toString().contains("f")){
                fsDataOutputStream1.writeBytes(text.toString() + "\t" + intWritable.get() + "\n");
            }else{
                fsDataOutputStream2.writeBytes(text.toString() + "\t" + intWritable.get() + "\n");
            }
        }
    
        //关闭
        @Override
        public void close(TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
            fsDataOutputStream1.close();
            fsDataOutputStream2.close();
        }
    }
    
  • 第三步:在Job驱动中,设置自定义的 OutputFormat

    • job.setOutputFormatClass(自定义的OutputFormat类名.class);




9、数据压缩

概述

  • 优点:以减少磁盘 IO、减少磁盘存储空间。
  • 缺点:增加 CPU 开销。
  • 压缩原则
    • 运算密集型的 Job,少用压缩
    • IO 密集型的 Job,多用压缩



MR 支持的压缩编码

压缩算法对比介绍

压缩格式Hadoop 自带?算法文件扩展名是否可切片换成压缩格式后,原来的程序是否需要修改
DEFLATE是,直接使用DEFLATE.deflate和文本处理一样,不需要修改
Gzip是,直接使用DEFLATE.gz和文本处理一样,不需要修改
bzip2是,直接使用bzip2.bz2和文本处理一样,不需要修改
LZO否,需要安装LZO.lzo需要建索引,还需要指定输入格式
Snappy是,直接使用Snappy.snappy和文本处理一样,不需要修改

压缩性能的比较

压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17.5MB/s58MB/s
bzip28.3GB1.1GB2.4MB/s9.5MB/s
LZO8.3GB2.9GB49.3MB/s74.6MB/s
Snappy250MB/s500MB/s

压缩算法的优缺点比较

压缩算法优点缺点
Gzip压缩率比较高不支持 Split;压缩/解压速度一般
Bzip2压缩率高;支持 Split压缩/解压速度慢
Lzo压缩/解压速度比较快;支持 Split压缩率一般;想支持切片需要额外创建索引
Snappy压缩和解压缩速度快不支持 Split;压缩率一般



压缩参数配置

Hadoop 引入了编码/解码器

压缩格式对应的编码/解码器
DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
gziporg.apache.hadoop.io.compress.GzipCodec
bzip2org.apache.hadoop.io.compress.BZip2Codec
LZOcom.hadoop.compression.lzo.LzopCodec
Snappyorg.apache.hadoop.io.compress.SnappyCodec

配置参数

参数默认值阶段建议
io.compression.codecs
(在 core-site.xml 中配置)
无,这个需要在命令行输入hadoop checknative 查看输入压缩Hadoop 使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compress
(在 mapred-site.xml 中配置)
falsemapper 输出这个参数设为 true 启用压缩
mapreduce.output.fileoutputformat.compress
(在mapred-site.xml 中配置)
falsereducer 输出这个参数设为 true 启用压缩
mapreduce.output.fileoutputformat.compress.codec
(在mapred-site.xml 中配置)
org.apache.hadoop.io.
compress.DefaultCodec
reducer 输出使用标准工具或者编解码器,如 gzip 和bzip2




程序实现压缩

Map 输出端采用压缩

  • 在Driver类中配置,开启压缩和设置压缩方式(以BZip压缩为例)
Configuration configuration = new Configuration();

// 开启 map 端输出压缩
configuration.setBoolean("mapreduce.map.output.compress", true);
// 设置 map 端输出压缩方式
configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);



Reduce 输出端采用压缩

  • 在Driver类中配置,开启压缩和设置压缩方式(以BZip压缩为例)

    // 设置 reduce 端输出压缩开启
    FileOutputFormat.setCompressOutput(job, true);
    // 设置压缩的方式
    FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
    
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值