MapReduce入门 ( 二 )

MapReduce入门( 二 )

mr编程中, 利用好key的特性

  • 排序 默认为字典序
  • 分区 默认为key的哈希值对reducertask数量取模
  • 分组 默认为key相同的为一组

在mr编程中,可以把上一个mr的输出目录直接作为下一个mr的输入 mr程序能够自动识别里面什么是检验性文件 什么是成功标识文件 什么是真正的数据文件

mr默认分区

源码: 类HashPartition

在这里插入图片描述

mr解决切片时 数据分割的问题

# 在切分时, 到达切片规则 正好将某个单词切成两半
原来: hadoop spark spark
split1 : hadoop spa
split2 : rk spark
# mr解决方案: 如果这个切片不是第一个,则在读取数据时,会额外读取下一个切片的第一行, 保证数据准确

源码 : 类 LineRecordReader

在这里插入图片描述

mr程序执行步骤

在这里插入图片描述
在这里插入图片描述

Mapper任务( 分 )

  • 将输入目录下的文件逐个进行逻辑切片(默认和分块Size一样), 每个切片由一个MapTask处理

    • 有几个文件, 则最少有几个maptask

    • split1—>1.txt 0M-128M

      split2—>1.txt 128M-200M

      split3—>1.txt 0M-10M

  • TextInPutFormat组件解析文本内容 生成map的输入kv [偏移量,“每一行的文本”]

  • 调用map方法, 执行业务逻辑, 输出0个或多个键值对到内存缓冲区

  • kv输出时先输出到内存缓冲区中, 缓冲区大小默认为100M, 缓冲区存到100M时, 溢出,写入磁盘

    • 每溢出一次, 生成一个小文件
    • 溢出行为最少发生一次 不能根据切片大小判断, 因为业务逻辑不确定,输出kv内容也不确定
  • 进入磁盘的小文件, 会进行合并, 排序, 分区

    • 对kv进行分区 , 分区的数量是reducer的数量 默认为1
    • 对分区的键值对进行排序 , 先根据key排序, key相同的, 根据value排序, 默认为字典序
  • 如果需要 : 局部聚合处理, 经过combiner, 键相等的kv会调用一次reduce方法

  • 磁盘中的数据等待reducer拉取

Reducer任务 ( 合 )

  • reducer去每个map所在磁盘中, 拿到属于自己的kv分区数据
  • reducer任务获得多个mapper任务的输出kv , 作为自己的输入kv
  • 合并数据, 进行排序
  • 排序后, key相同的kv分为一组. 调用一次reduce, 产生0个或多个键值对
  • reducer调用TextOutPutFormat组件最终输出到文件
Mapreduce序列化 排序

序列化(Serialization)是指把结构化对象转化为字节流

反序列化(Deserialization)是序列化的逆过程。把字节流转为结构化对象

java的序列化太重, 一个对象被序列化后, 会携带很多额外信息(校验,继承等等) , 网络IO开销太大

hadoop有自己的一套序列化机制, 实现Writable接口

  • 因为要进行网络IO 所以自定义bean应该实现序列化接口WritableComparable
    • 在实现接口时, 会实现read(反序列化方法)和write(序列化方法)
    • 这两个方法在实现时, 各个字段的顺序应该保持一致
package com.hrh;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @Title: FlowBean
 * @ProjectName BigData
 * @Description: TODO  流量实体类
 */
public class FlowBean  implements WritableComparable<FlowBean>{

    //上传流量 下载流量 总流量
    private long upFlow;
    private long downFlow;
    private long sumFlow;

    //反序列化时 用到空参构造
    public FlowBean() {
    }

    public FlowBean(long upFlow, long downFlow, long sumFlow) {
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.sumFlow = sumFlow;
    }

    //序列化方法  各个字段的顺序应该保持一致
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);
    }

    //反序列化方法  各个字段的顺序应该保持一致
    @Override
    public void readFields(DataInput in) throws IOException {
        this.upFlow = in.readLong();
        this.downFlow = in.readLong();
        this.sumFlow = in.readLong();
    }
}

Mapreduce 排序

如果自定义bean作为key, 还要在reduce阶段进行排序

  • 实现WritableComparable接口, 实现compareTo方法
package com.hrh;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @Title: FlowBean
 * @ProjectName BigData
 * @Description: TODO  流量实体类
 */
public class FlowBean implements WritableComparable<FlowBean> {

    //.....上述方法省略 getter setter 构造
    
    //按流量总值倒序排列
    @Override
    public int compareTo(FlowBean o) {
        
        return this.sumFlow>o.getSumFlow()?-1:1;
    }
}

MapReducer分区

mapper输出的kv对, 每一个都会经过Partitioner分发给reducer处理

默认分发规则为 key的hashcode%reducetask数

如果需要配置自己的分发规则( 分给多个reducer )

  • 自定义类继承Partitioner
  • getPartition 返回的int值表示这个kv交给第几个reducer处理
  • 在driver类中, job设置partitioner 设置reducetask的个数
package com.hrh;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
import java.util.HashMap;

/**
 * @Title: ProvincePartitioner
 * @ProjectName BigData
 * @Description: TODO
 */
public class ProvincePartitioner extends Partitioner<Text,FlowBean> {

    public static HashMap<String,Integer> provinceMap=new HashMap<>();

    static{
        provinceMap.put("134",0);
        provinceMap.put("135",1);
        provinceMap.put("136",2);
        provinceMap.put("137",3);
        provinceMap.put("138",4);
    }


    @Override
    public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
        Integer code = provinceMap.get(text.toString().substring(0, 3));

        if(code!=null){
            return code;
        }

        return 5;
    }
}
Mapreduce 分组

在reduce阶段, 默认为key相同的kv 分为一组, 调用一次reduce方法

如果key不相同, 又想分为一组

  • 自定义类GroupingComparator实现WritableComparator
  • Driver类 job.setGroupingComparatorClass();
package com.hrh.group;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

/**
 * @Title: GroupComparator
 * @ProjectName BigData
 * @Description: TODO
 */
public class GroupComparator extends WritableComparator {

    //必须写 传入作为key的bean的class类型,以及制定需要让框架做反射获取实例对象
    public GroupComparator() {
        super(FlowBean.class,true);
    }

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        FlowBean aa= (FlowBean) a;
        FlowBean bb= (FlowBean) b;
		//只要上传流量相同视为key相同, 分为一组
        return aa.getUpFlow()==bb.getUpFlow()?0:1;
    }
}

Mapreduce Combiner

mapper的每一次map输出都可能生成很多kv combiner的作用就是先对map端的输出做局部合并

减少map-reduce的数据传输( 网络IO) 提高性能

  • combiner组件的父类是reducer
    • combiner在每个maptask节点运行
    • reducer接收全局所有mapper的输出结果
  • 可以没有, 是用来优化的
    • 能够应用的前提是不能影响最终的业务逻辑
    • 不适用于所有场景, 适合sum求和, 具体看业务需求
  • 代码实现
    • 自定义类实现reducer 重写reduce方法
      • combiner 的输出 kv 应该跟 reducer 的输入 kv 类型要对应起来
    • driver中 job设置 job.setCombinerClass

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值