六、 组合器(Combiner)

Combiner的作用就是对map端的输出先做一次合并,以减少在map和****reduce节点之间的数据传输量,以提高网络IO性能,是MapReduce的一种优化手段之一,根据业务来选用

I、Combiner合并
  1. Combiner是MR程序中Mapper和Reducer之外的一种组件。
  2. Combiner组件的父类就是Reducer。
  3. Combiner和Reducer的区别在于运行的位置
Combiner是在每一个MapTask所在的节点运行;
Reducer是接收全局所有Mapper的输出结果;

  1. Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
  2. Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。
//适用于汇总,不适合求平均值业务场景
Mapper
3 5 7 ->(3+5+7)/3=5 
2 6 ->(2+6)/2=4
Reducer
(3+5+7+2+6)/5=23/5    不等于    (5+4)/2=9/2

  1. 自定义Combiner实现步骤
  • 自定义一个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));
    }
}

  • 在Job驱动类中设置:
job.setCombinerClass(WordcountCombiner.class);

II、Combiner合并案例实操

需求

统计过程中对每一个MapTask的输出进行局部汇总,以减小网络传输量即采用Combiner功能。

1、数据输入

banzhang ni hao                 <banzhang,4>
xihuan hadoop                   < ni ,2>
banzhang                        <hao,2>
banzhang ni hao                 <xihuan,2>
xihuan hadoop                   <Hadoop,2>
banzhang

2、期望输出 IV、MapReduce 分布式计算框架(二)_MapReduce

方案一

  • 增加一个WordcountCombiner类继承Reducer
  • 在WordcountCombiner中
    • 统计单词汇总
    • 将统计结果输出

方案二

  • 将WordcountReducer作为Combiner在WordcountDriver驱动类中指定
job.setCombinerClass(WordcountReducer.class);

方案一

combiner.txt

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;public class WordcountCombiner extends Reducer<Text, IntWritable,Text,IntWritable> {    IntWritable intWritable = new IntWritable();
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        //1. 累加求和
        for (IntWritable value : values) {
            sum += value.get();
         }
        intWritable.set(sum);
        //2.写入
        context.write(key,intWritable );    }

}

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 {
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/combiner","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/combiner"};
        Configuration conf = new Configuration();
        //1. 获取Job对象
        Job job = Job.getInstance(conf);
        //2. 设置java存储位置
        job.setJarByClass(WordCountDriver.class);
        //3. 关联Map和Reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        //4. 设置Mapper阶段数据 key value 类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        //5. 设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);        job.setCombinerClass(WordcountCombiner.class);
        /**
         * 6. 设置输入路径和输出路径
         *
         * 输入输出参数写在 programs arguments
         */
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //7. 提交Job
        boolean completion = job.waitForCompletion(true);
        System.exit(completion ? 0 : 1);    }
}

结果

part-r-00000

方案二

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 {
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/combiner","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/combiner2"};        Configuration conf = new Configuration();
        //1. 获取Job对象
        Job job = Job.getInstance(conf);
        //2. 设置java存储位置
        job.setJarByClass(WordCountDriver.class);
        //3. 关联Map和Reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        //4. 设置Mapper阶段数据 key value 类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        //5. 设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);        job.setCombinerClass(WordCountReducer.class);
        /**
         * 6. 设置输入路径和输出路径
         *
         * 输入输出参数写在 programs arguments
         */        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //7. 提交Job
        boolean completion = job.waitForCompletion(true);
        System.exit(completion ? 0 : 1);    }
}

IV、MapReduce 分布式计算框架(二)_MapReduce_02

七、GroupingComparator 分组(辅助排序)

对Reduce阶段的数据根据某一个或几个字段进行分组。

分组排序步骤:

  1. 自定义类继承WritableComparator
  2. 重写compare()方法
class OrderBean implements WritableComparable<OrderBean> 
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
    // 比较的业务逻辑
      return result;
    }
}

  1. 创建一个构造将比较对象的类传给父类
protected OrderGroupingComparator() {
  super(OrderBean.class, true);

}

I、需求

需求

有如下订单数据

订单id商品id成交金额
0000001Pdt_01222.8

Pdt_0233.8
0000002Pdt_03522.8

Pdt_04122.4

Pdt_05722.4
0000003Pdt_06232.8

Pdt_0233.8
  1. 输入数据
  2. Comparator.txt期望输出数据
1	222.8
2	722.4
3	232.8

需求分析

  • 利用“订单id和成交金额”作为key,可以将Map阶段读取到的所有订单数据按照id升序排序,如果id相同再按照金额降序排序,发送到Reduce。
  • 在Reduce端利用groupingComparator将订单id相同的kv聚合成组,然后取第一个即是该订单中最贵商品,如图4-18所示。

IV、MapReduce 分布式计算框架(二)_MapReduce_03

II、案例实操

OrderBean

import org.apache.hadoop.io.WritableComparable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;public class OrderBean implements WritableComparable<OrderBean> {
private int orderId;
private double price;public OrderBean() {
}public OrderBean(int orderId, double price) {
this.orderId = orderId;
this.price = price;
}public int getOrderId() {
return orderId;
}public void setOrderId(int orderId) {
this.orderId = orderId;
}public double getPrice() {
return price;
}public void setPrice(double price) {
this.price = price;
}@Override
public String toString() {
return orderId +
"\t" + price;
}@Override
public int compareTo(OrderBean o) {
int result;
//先按照orderId进行升序排序,相同按照价格进行降序排序if (orderId > o.getOrderId()) {
result = 1;
} else if (orderId < o.getOrderId()) {
result = -1;
} else {
if (price > o.getPrice()) {
result = -1;
} else if (price < o.getPrice()) {
result = 1;
} else {
result = 0;
}
}
return result;
}@Override
public void write(DataOutput out) throws IOException {
out.writeInt(orderId);
out.writeDouble(price);
}@Override
public void readFields(DataInput in) throws IOException {
orderId = in.readInt();
price = in.readDouble();
}
}

OrderMapper

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 OrderMapper extends Mapper<LongWritable, Text,OrderBean, NullWritable> {    OrderBean orderBean = new OrderBean();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1. 获取一行
        String line = value.toString();
        //2. 切割
        String[] fileds = line.split("\t");
        //3. 封装对象
        orderBean.setOrderId(Integer.parseInt(fileds[0]));
        orderBean.setPrice(Double.parseDouble(fileds[2]));
        //4. 写出
        context.write(orderBean,NullWritable.get());
    }
}

OrderGroupingComparator

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;public class OrderGroupingComparator extends WritableComparator {
    //不写会有空指针
    protected OrderGroupingComparator() {
        super(OrderBean.class,true);
    }    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        //只要ID相同,认为相同的key
        int result;
        OrderBean aOrderBean = (OrderBean) a;
        OrderBean bOrderBean = (OrderBean) a;
        if (aOrderBean.getOrderId() > bOrderBean.getOrderId()) {
            result = 1;
        } else if (aOrderBean.getOrderId() < bOrderBean.getOrderId()) {
            result = -1;
        } else {
            result = 0;
        }
        return result;
    }
}

OrderReducer

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;import java.io.IOException;public class OrderReducer extends Reducer<OrderBean, NullWritable,OrderBean,NullWritable> {
    @Override
    protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        for (NullWritable value : values) {
            context.write(key,NullWritable.get());
        }
    }
}

OrderDriver

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;import java.io.IOException;public class OrderDriver {
    public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/groupComparator","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/groupComparator"};
        // 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输出数据key和value类型
        job.setMapOutputKeyClass(OrderBean.class);
        job.setMapOutputValueClass(NullWritable.class);
        // 5 设置最终输出数据的key和value类型
        job.setOutputKeyClass(OrderBean.class);
        job.setOutputValueClass(NullWritable.class);
        // 6 设置输入数据和输出数据路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        // 8 设置reduce端的分组
        job.setGroupingComparatorClass(OrderGroupingComparator.class);
        // 7 提交
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

结果

part-r-00000

八、MapTask 和 ReduceTaskI、MapTask

MapTask 包括五个阶段:

  1. Read阶段
  2. Map阶段
  3. Collection阶段
  4. 溢写阶段
  5. Combiner阶段

a. Read阶段

获取待处理的文件,处理切片信息。提交切片等信息到Yarn集群,集群启动相应的MrAppMaster,开启相应的MapTask。用TextInputFormat去读一行行数据。

b.Map阶段

读完后返回相应的K,V数据。将数据写入到Map里面。 逻辑处理,context.write(K,V);

c.Collect阶段

通过outputCollector写出到环形缓冲区。分区,排序。

d.溢写阶段

排完序之后向磁盘中溢写。

e.Combiner阶段

溢写完后再进行合并。归并排序

II、ReduceTask

IV、MapReduce 分布式计算框架(二)_MapReduce_04

ReduceTask 包括四个阶段:

  1. Copy阶段
  2. Merge阶段
  3. Sort阶段
  4. Reduce阶段

a. Copy阶段

所有的MapTask结束之后,将所有的数据拷贝到ReduceTask中。

b. Merge阶段

如果数据量小没有达到设置的上限,则放在内存中;如果消耗过上限,溢写到磁盘。

c. Sort阶段

最终将多个文件合并成一个大的有序的文件,将相同key 的数据拷贝到相同的Reduce里面。如果有需要进行分组。

d. Reduce阶段

输出

III、如何设置ReduceTask数量

ReduceTask的并行度同样影响整个Job的执行并发度和执行效率,但与MapTask的并发数由切片数决定不同,ReduceTask数量的决定是可以直接手动设置:

// 默认值是1,手动设置为4
job.setNumReduceTasks(4);

实验:测试ReduceTask多少合适

(1)实验环境:1个Master节点,16个Slave节点:CPU:8GHZ,内存: 2G

(2)实验结论:

MapTask =16









ReduceTask151015162025304560
总时间8921461109288100128101145104

注意事项

(1)ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致。

(2)ReduceTask默认值就是1,所以输出文件个数为一个。

(3)如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜

(4)ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个ReduceTask。

(5)具体多少个ReduceTask,需要根据集群性能而定。

(6)如果分区数不是1,但是ReduceTask为1,是否执行分区过程。

答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceTaskNum个数是否大于1。不大于1肯定不执行。

IV、Shuffle机制回顾

IV、MapReduce 分布式计算框架(二)_MapReduce_05

九、OutputFormat 数据输出I、OutputFormat接口实现类

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。下面我们介绍几种常见的OutputFormat实现类。

1.文本输出TextOutputFormat

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

2.SequenceFileOutputFormat

将SequenceFileOutputFormat输出作为后续 MapReduce任务的输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩。

3.自定义OutputFormat

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

II、自定义OutputFormat

1.使用场景

为了实现控制最终文件的输出路径和输出格式,可以自定义OutputFormat。

例如:要在一个MapReduce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义OutputFormat来实现。

2.自定义OutputFormat步骤

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

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

III、自定义OutputFormat案例实操

**需求:**过滤输出的 log 日志,包含 Adrien 的网站输出到 adrien.log 其余的 输出到 other.log

IV、MapReduce 分布式计算框架(二)_MapReduce_06

FilterMapper

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 FilterMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        context.write(value,NullWritable.get());
    }
}

FilterReducer

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;import java.io.IOException;public class FilterReducer extends Reducer<Text, NullWritable,Text,NullWritable> {
    Text text = new Text();
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        //输出分行展示
        String line = key.toString();
        line = line + "\r\t";
        text.set(line);
        //防止有重复数据
        for (NullWritable value : values) {
            context.write(key,NullWritable.get());
        }
    }
}

FilterOutputFormat

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;
import java.io.IOException;public class FilterOutputFormat extends FileOutputFormat<Text, NullWritable> {
    @Override
    public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
        return new FRecordWriter(job);
    }
}

FRecordWriter

import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
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 java.io.IOException;public class FRecordWriter extends RecordWriter<Text, NullWritable> {
    FSDataOutputStream outputStreamAdrien;
    FSDataOutputStream outputStreamOthers;
    //核心业务逻辑
    public FRecordWriter(TaskAttemptContext job) {
        try {
            //1. 获取文件系统
            FileSystem fileSystem = FileSystem.get(job.getConfiguration());
            //2. 创建输出到adrien.log的输出流
            outputStreamAdrien = fileSystem.create(new Path("/Users/luohaotian/Downloads/Jennifer/HelloApp/output/outputFormat/adrien.log"));
            //3. 创建输出到others.log的输出流
            outputStreamOthers = fileSystem.create(new Path("/Users/luohaotian/Downloads/Jennifer/HelloApp/output/outputFormat/others.log"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }    //写
    @Override
    public void write(Text key, NullWritable value) throws IOException, InterruptedException {
        //1. 判断key中有Adrien 写出到adrien.log;反之写到others.log
        if (key.toString().contains("adrien")) {
            outputStreamAdrien.write(key.toString().getBytes());
        } else {
            outputStreamOthers.write(key.toString().getBytes());
        }    }    @Override
    public void close(TaskAttemptContext context) throws IOException, InterruptedException {
        IOUtils.closeStream(outputStreamAdrien);
        IOUtils.closeStream(outputStreamOthers);
    }

} FilterDriver

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;import java.io.IOException;public class FilterDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 输入输出路径需要根据自己电脑上实际的输入输出路径设置
        args = new String[] { "/Users/luohaotian/Downloads/Jennifer/HelloApp/input/outputFormat", "/Users/luohaotian/Downloads/Jennifer/HelloApp/output/outputFormat2" };
        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);
    }
}

IV、MapReduce 分布式计算框架(二)_MapReduce_07

十、JOIN操作及案例I、Reduce Join 工作原理

Map端的主要工作:为来自不同表或文件的 key/value 对,打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。

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

II、Reduce Join 案例

需求

IV、MapReduce 分布式计算框架(二)_MapReduce_08

输入

order.txtpd.txt代码

TableBean

import org.apache.hadoop.io.Writable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;public class TableBean implements Writable {
    /**
     * id  pid    amount
     * pid pname
     */
    private String id;
    private String pid;
    private int amount;
    private String pname;
    //标记位,记录来自哪张表
    private String flag;    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(id);
        out.writeUTF(pid);
        out.writeInt(amount);
        out.writeUTF(pname);
        out.writeUTF(flag);
    }    @Override
    public void readFields(DataInput in) throws IOException {
        id = in.readUTF();
        pid = in.readUTF();
        amount = in.readInt();
        pname = in.readUTF();
        flag = in.readUTF();
    }    @Override
    public String toString() {
        return id + "\t" + amount + "\t" + pname;
    }    public TableBean() {
    }    public TableBean(String id, String pid, int amount, String pname, String flag) {
        this.id = id;
        this.pid = pid;
        this.amount = amount;
        this.pname = pname;
        this.flag = flag;
    }    public String getId() {
        return id;
    }    public void setId(String id) {
        this.id = id;
    }    public String getPid() {
        return pid;
    }    public void setPid(String pid) {
        this.pid = pid;
    }    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;
    }    public String getFlag() {
        return flag;
    }    public void setFlag(String flag) {
        this.flag = flag;
    }
}

TableMapper

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;import java.io.IOException;public class TableMapper extends Mapper<LongWritable, Text,Text,TableBean> {
    String name;
    TableBean tableBean = new TableBean();
    Text text = new Text();    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        //获取文件名称
        FileSplit inputSplit = (FileSplit) context.getInputSplit();
        name = inputSplit.getPath().getName();
    }    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        /**
         * id  pid    amount
         * 1001    01 1
         *
         * pid pname
         * 01  小米
         */
        //1. 获取一行
        String line = value.toString();
        if (name.startsWith("order")) {
            String[] fields = line.split("\t");
            tableBean.setId(fields[0]);
            tableBean.setPid(fields[1]);
            tableBean.setAmount(Integer.parseInt(fields[2]));
            tableBean.setPname("");
            tableBean.setFlag("order");
            text.set(fields[1]);
        } else {
            String[] fields = line.split("\t");
            tableBean.setPid(fields[0]);
            tableBean.setAmount(0);
            tableBean.setPname(fields[1]);
            tableBean.setFlag("pd");
            tableBean.setId("");
            text.set(fields[0]);
        }
        //写出
        context.write(text,tableBean);
    }
}

TableReducer

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;public class TableReducer extends Reducer<Text,TableBean,TableBean, NullWritable> {    @Override
    protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {
        //存放所有订单集合
        ArrayList<TableBean> orderBeans = new ArrayList<>();
        //存放产品信息
        TableBean pdBean = new TableBean();        for (TableBean value : values) {
            //订单表
            if ("order".equals(value.getFlag())) {
                TableBean tempBean = new TableBean();                try {
                    BeanUtils.copyProperties(tempBean,value);
                    orderBeans.add(tempBean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } else {
                try {
                    BeanUtils.copyProperties(pdBean,value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }        for (TableBean orderBean : orderBeans) {
            orderBean.setPname(pdBean.getPname());
            context.write(orderBean,NullWritable.get());
        }    }
}

TableDriver

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 {
        // 0 根据自己电脑路径重新配置
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/table","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/table"};        // 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);
    }
}

输出

part-r-00000III、Reduce Join缺点及解决方案

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

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

IV、Map Join 工作原理

1.使用场景

Map Join适用于一张表十分小、一张表很大的场景。

2.优点

思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?

在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜。

3.具体办法:采用DistributedCache

* 在Mapper的setup阶段,将文件读取到缓存集合中。
* 在驱动函数中加载缓存。

// 缓存普通文件到Task运行节点。

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

IV、MapReduce 分布式计算框架(二)_MapReduce_09

V、Map Join 案例

输入:

IV、MapReduce 分布式计算框架(二)_MapReduce_10

order.txtpd.txt

DistributedCacheMapper

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.IOUtils;
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.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;public class DistributedCacheMapper extends Mapper<LongWritable,Text, Text, NullWritable> {
    HashMap<String, String> pdMap = new HashMap<>();
    
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        URI[] cacheFiles = context.getCacheFiles();
        String path = cacheFiles[0].getPath().toString();
        //缓存小表
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));
        String line;        /**
         * pid pname
         * 01  小米
         */
        while (StringUtils.isNotEmpty(line = reader.readLine())) {
            //1. 切割
            String[] fields = line.split("\t");
            pdMap.put(fields[0],fields[1]);
        }
        //2. 关闭资源
        IOUtils.closeStream(reader);
    }    Text text = new Text();    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        /**
         * id  pid    amount
         * 1001    01 1
         *
         * pid pname
         * 01  小米
         */
        //1. 获取一行
        String line = value.toString();
        //2. 切割
        String[] fields = line.split("\t");
        //3. 获取pid
        String pid = fields[1];
        //4. 获取pname
        String pname = pdMap.get(pid);
        //5. 拼接
        line = line + "\t" + pname;
        text.set(line);
        context.write(text,NullWritable.get());
    }
}

DistributedCacheDriver

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;import java.net.URI;public class DistributedCacheDriver {
    public static void main(String[] args) throws Exception {        // 0 根据自己电脑路径重新配置
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/table","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/table2"};
        // 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:///Users/luohaotian/Downloads/Jennifer/HelloApp/input/cache/pd.txt"));
        // 7 Map端Join的逻辑不需要Reduce阶段,设置reduceTask数量为0
        job.setNumReduceTasks(0);
        // 8 提交
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

输出

part-m-00000

十一、计数器与数据清洗I、计数器

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

1.计数器API

  • 采用枚举的方式统计计数
enum MyCounter{MALFORORMED,NORMAL}
//对枚举定义的自定义计数器加1
context.getCounter(MyCounter.MALFORORMED).increment(1);

  • 采用计数器组、计数器名称的方式统计
context.getCounter("counterGroup", "counter").increment(1);

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

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

2.  计数器案例实操

详见数据清洗案例。

II、数据清洗(ETL)

在运行核心业务MapReduce程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序。

数据清洗案例实操-简单解析版

1.需求

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

(1)输入数据

web.log

(2)期望输出数据

每行字段长度都大于11。

2.需求分析

需要在Map阶段对输入的数据根据规则进行过滤清洗。

3.实现代码

**LogMapper**
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 LogMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1. 获取一行
        String line = value.toString();
        //2. 解析数据
        boolean result = parseLog(line,context);
        if (!result) {
            return;
        }
        //3. 解析通过 完成输出
        context.write(value,NullWritable.get());
    }    private boolean parseLog(String line, Context context) {
        //判断长度是否大于11
        String[] fields = line.split(" ");
        if (fields.length > 11) {
            context.getCounter("map","true").increment(1);
            return true;
        } else {
            context.getCounter("map","false").increment(1);
            return false;
        }
    }
}

LogDriver

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[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/weblog","/Users/数据清洗案例实操-简单解析版
/Downloads/Jennifer/HelloApp/output/weblog"};
        // 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);
    }
}

输出

part-m-00000数据清洗案例实操-复杂解析版

HardLogBean

public class HardLogBean {
    private String remote_addr;// 记录客户端的ip地址
    private String remote_user;// 记录客户端用户名称,忽略属性"-"
    private String time_local;// 记录访问时间与时区
    private String request;// 记录请求的url与http协议
    private String status;// 记录请求状态;成功是200
    private String body_bytes_sent;// 记录发送给客户端文件主体内容大小
    private String http_referer;// 用来记录从那个页面链接访问过来的
    private String http_user_agent;// 记录客户浏览器的相关信息    private boolean valid = true;// 判断数据是否合法    @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();
    }    public HardLogBean() {
    }    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;
    }
}

HardLogMapper

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 HardLogMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
    Text textKey = new Text();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 1 获取1行
        String line = value.toString();        // 2 解析日志是否合法
        HardLogBean bean = parseLog(line);        if (!bean.isValid()) {
            return;
        }        textKey.set(bean.toString());        // 3 输出
        context.write(textKey, NullWritable.get());
    }    private HardLogBean parseLog(String line) {
        HardLogBean logBean = new HardLogBean();
        // 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]);
            }            // 大于400,HTTP错误
            if (Integer.parseInt(logBean.getStatus()) >= 400) {
                logBean.setValid(false);
            }
        }else {
            logBean.setValid(false);
        }        return logBean;    }

} HardLogDriver

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 HardLogDriver {
    public static void main(String[] args) throws Exception {
        // 输入输出路径需要根据自己电脑上实际的输入输出路径设置
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/weblog","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/weblog2"};
        // 1 获取job信息
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        // 2 加载jar包
        job.setJarByClass(HardLogDriver.class);
        // 3 关联map
        job.setMapperClass(HardLogMapper.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);
    }
}

III、MapReduce开发总结

1.输入数据接口:InputFormat

  • 默认使用的实现类是:TextInputFormat
  • TextInputFormat的功能逻辑是:默认按照块大小切片。 一次读一行文本,然后将该行的起始偏移量作为key,行内容作为value返回。key偏移量,value是一行数据。
  • KeyValueTextInputFormat每一行均为一条记录,被分隔符分割为key,value。默认分隔符是tab(\t)。
  • NlineInputFormat按照指定的行数N来划分切片。
  • CombineTextInputFormat可以把多个小文件合并成一个切片处理,提高处理效率。
  • 用户还可以自定义InputFormat。

2.逻辑处理接口:Mapper

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

3.Partitioner分区

  • 有默认实现 HashPartitioner,逻辑是根据key的哈希值和numReduces来返回一个分区号;key.hashCode()&Integer.MAXVALUE % numReduces
  • 如果业务上有特别的需求,可以自定义分区。

4.Comparable排序

  • 当我们用自定义的对象作为key来输出时,就必须要实现WritableComparable接口,重写其中的compareTo()方法。
  • 部分排序:对最终输出的每一个文件进行内部排序。
  • 全排序:对所有数据进行排序,通常只有一个Reduce。
  • 二次排序:排序的条件有两个。

5.Combiner合并

Combiner合并可以提高程序执行效率,减少IO传输。但是使用时必须不能影响原有的业务处理结果。

6.Reduce端分组:GroupingComparator

在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。

7.逻辑处理接口:Reducer

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

**十二、**Hadoop数据压缩I、压缩概述

压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行MR程序时,I/O操作、网络数据传输、 Shuffle和Merge要花大量的时间,尤其是数据规模很大和工作负载密集的情况下,因此,使用数据压缩显得非常重要。

鉴于磁盘I/O和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O和网络传输非常有帮助。可以在任意MapReduce阶段启用压缩。不过,尽管压缩与解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。

压缩策略和原则

压缩是提高Hadoop运行效率的一种优化策略。

通过对Mapper、Reducer运行过程的数据进行压缩,以减少磁盘IO,提高MR程序运行速度。

注意:采用压缩技术减少了磁盘IO,但同时增加了CPU运算负担。所以,压缩特性运用得当能提高性能,但运用不当也可能降低性能。

压缩基本原则:

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

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

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

为了支持多种压缩/解压缩算法,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

压缩性能的比较

压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17.5MB/s58MB/s
bzip28.3GB1.1GB2.4MB/s9.5MB/s
LZO8.3GB2.9GB49.3MB/s74.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.

III、压缩方式选择

A、Gzip压缩

**优点:**压缩率比较高,而且压缩/解压速度也比较快;Hadoop本身支持,在应用中处理Gzip格式的文件就和直接处理文本一样;大部分Linux系统都自带Gzip命令,使用方便。

**缺点:**不支持Split。

**应用场景:**当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用Gzip压缩格式。例如说一天或者一个小时的日志压缩成一个Gzip文件。

B、Bzip2压缩

**优点:**支持Split;具有很高的压缩率,比Gzip压缩率都高;Hadoop本身自带,使用方便。

**缺点:**压缩/解压速度慢。

**应用场景:**适合对速度要求不高,但需要较高的压缩率的时候;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序的情况。

C、Lzo压缩

**优点:**压缩/解压速度也比较快,合理的压缩率;支持Split,是Hadoop中最流行的压缩格式;可以在Linux系统下安装lzop命令,使用方便。

**缺点:**压缩率比Gzip要低一些;Hadoop本身不支持,需要安装;在应用中对Lzo格式的文件需要做一些特殊处理(为了支持Split需要建索引,还需要指定InputFormat为Lzo格式)。

**应用场景:**一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,Lzo优点越越明显。

D、Snappy压缩

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

**缺点:**不支持Split;压缩率比Gzip要低;Hadoop本身不支持,需要安装。

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

IV、压缩位置的选择

IV、MapReduce 分布式计算框架(二)_MapReduce_11

V、压缩参数配置

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

参数默认值阶段建议
io.compression.codecs<br>(在core-site.xml中配置)org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec<br>输入压缩Hadoop使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compress(在mapred-site.xml中配置)falsemapper输出这个参数设为true启用压缩
mapreduce.map.output.compress.codec(在mapred-site.xml中配置)org.apache.hadoop.io.compress.DefaultCodecmapper输出企业多使用LZO或Snappy编解码器在此阶段压缩数据
mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置)falsereducer输出这个参数设为true启用压缩
mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置)org.apache.hadoop.io.compress. DefaultCodecreducer输出使用标准工具或者编解码器,如gzip和bzip2
mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置)RECORDreducer输出SequenceFile输出使用的压缩类型:NONE和BLOCK
VI、压缩实操案例

A、数据流的压缩和解压缩

CompressionCodec有两个方法可以用于轻松地压缩或解压缩数据。

  • 要想对正在被写入一个输出流的数据进行压缩,我们可以使用createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream,将其以压缩格式写入底层的流。
  • 相反,要想对从输入流读取而来的数据进行解压缩,则调用createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,从而从底层的流读取未压缩的数据。

测试一下如下压缩方式:

DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
gziporg.apache.hadoop.io.compress.GzipCodec
bzip2org.apache.hadoop.io.compress.BZip2Codec

解压缩测试

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;import java.io.*;public class TestCompress {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //压缩
        //compress("/Users/luohaotian/Downloads/激活码","org.apache.hadoop.io.compress.BZip2Codec");
        //compress("/Users/luohaotian/Downloads/激活码","org.apache.hadoop.io.compress.GzipCodec");
        //compress("/Users/luohaotian/Downloads/激活码","org.apache.hadoop.io.compress.DefaultCodec");
        decompress("/Users/luohaotian/Downloads/激活码.bz2");
    }    private static void compress(String fileName, String method) throws IOException, ClassNotFoundException {
        //1. 获取输入流
        FileInputStream fileInputStream = new FileInputStream(new File(fileName));
        //2. 获取输出流
        //获取压缩方式扩展名
        Class<?> classCodec = Class.forName(method);
        CompressionCodec classc = (CompressionCodec) ReflectionUtils.newInstance(classCodec, new Configuration());        FileOutputStream fileOutputStream = new FileOutputStream(new File(fileName + classc.getDefaultExtension()));
        //有压缩方式的输出流
        CompressionOutputStream classcOutputStream = classc.createOutputStream(fileOutputStream);
        //3. 流的对拷 false 结束时不关闭流
        IOUtils.copyBytes(fileInputStream,classcOutputStream,1024 * 1024,false);
        //4. 关闭资源
        IOUtils.closeStream(classcOutputStream);
        IOUtils.closeStream(fileOutputStream);
        IOUtils.closeStream(fileInputStream);
    }    private static void decompress(String fileName) throws IOException {
        //1. 压缩方式检查
        CompressionCodecFactory codecFactory = new CompressionCodecFactory(new Configuration());
        CompressionCodec codec = codecFactory.getCodec(new Path(fileName));
        //校验是否可以压缩
        if (codec == null){
            //不能处理
            return;
        }
        //2. 获取输入流
        FileInputStream fileInputStream = new FileInputStream(new File(fileName));
        CompressionInputStream codecInputStream = codec.createInputStream(fileInputStream);
        //3. 获取输出流
        FileOutputStream fileOutputStream = new FileOutputStream(new File(fileName + ".decode"));
        //4. 流的对拷
        IOUtils.copyBytes(codecInputStream,fileOutputStream,1024 * 1024,false);
        //关闭资源
        IOUtils.closeStream(fileOutputStream);
        IOUtils.closeStream(codecInputStream);
        IOUtils.closeStream(fileInputStream);
    }
}

IV、MapReduce 分布式计算框架(二)_MapReduce_12

B、Map输出端采用压缩

即使你的MapReduce的输入输出文件都是未压缩的文件,你仍然可以对Map任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到Reduce节点,对其压缩可以提高很多性能,这些工作只要设置两个属性即可,我们来看下代码怎么设置。

1.给大家提供的Hadoop源码支持的压缩格式有:BZip2Codec 、DefaultCodec

修改驱动类,其他的不变。生成结果格式不变

public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/combiner","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/combiner21"};        Configuration conf = new Configuration();
               ...  ...    
        // 开启map端输出压缩
        conf.setBoolean("mapreduce.map.output.compress", true);
        // 设置map端输出压缩方式
        conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
                  
        }
}

C、Reduce输出端采用压缩

修改驱动类,其他的不变。生成结果格式改变

public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        args = new String[]{"/Users/luohaotian/Downloads/Jennifer/HelloApp/input/combiner","/Users/luohaotian/Downloads/Jennifer/HelloApp/output/combiner21"};
               ... ...         // 设置reduce端输出压缩开启
        FileOutputFormat.setCompressOutput(job, true);
        // 设置压缩的方式
        FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
                  
        }
}