• MapReduce


    Hadoop-MapReduce

    MapReduce主要包括:map(映射)负责处理原始数据生成中间结果,reduce(归约)处理map输出中中间结果生成最终结果。

    主要处理map与reduce的业务代码,map与reduce之间的shuffle(核心)过程,尤其关注key-value的设置

    优势:MapReduce,多个节点同时运行效率高,数据处理时间较为均衡。

    Map过程

    • block

      • 物理存储128M(除了最后一块)

    • split

      • 切片

      • 定义每个map任务的数据处理量

      • 默认切片大小与block大小相同

        • 切片过小,mapstak开启过多;切片过大,节点空暇,利用率低下

    • maptask

      • 一个split(切片)对应一个maptask,maptask只处理切片的原始数据

      • maptask根据用户的代码执行作业

      • maptask默认每次读取并处理一行数据,得到临时处理结果value

        • 可手动定义key-value读取器

      • 临时结果存在内存中,通过kvbuffer存入,同时还提交了比较器

    • kvbuffer

      • maptask的结果输出目标

      • 内存中的环形空间,默认100M

      • 通过溢写实现循环的数据写入(maptask的行处理结果)和数据写出(将临时结果存入硬盘)

      • 溢写的默认阈值为80%

    • spill

      • 溢写:kvbuffer默认设置80%的阈值,达到阈值时将kvbuffer中旧数据写入硬盘,同时写入新数据将已溢写出的内存区域覆盖,实现kvbuffer循环数据写入。

      • 调整80%的阈值,实现新数据存入速度与溢写数据到硬盘的均衡,否则新数据必须等待旧数据溢写完成

      • 每次溢写输出80M数据,数据大小与阈值关联

      • 溢写时根据map输入的比较器或默认比较器生成输出的每个数据的key

    • partation

      • 将溢写的数据进行分区,分区的数量与reduce数量相同

      • 根据每条数据的key对数据分区(分区比较器)

    • sort

      • 在溢写和分区全部完成后,对分区内的数据基于key进行排序(getOutputKeyComparator)

      • 默认使用快速排序法,排序基于key的内置比较器

    • merge

      • 使用归并算法,合并执行了分区排序的数据,默认根据分区的先后排列

      • 归并算法还是基于key的内置比较器进行排序

    Reduce过程

    • fetch

      • 拉取数据,从每个mapertask节点拉取reduce所需的数据

    • merge

      • 将所有fetch的数据,根据key进行归并排序,生成一个文件。

      • 归并排序规则是基于key对象内置比较器(getOutputKeyComparator)

    • reducetask

      • 分组比较器将相同的key的数据(所有临时处理数据)拉取到一个reducetask中处理

      • 分组比较器将相同key(这是比较器认为的相同)的数据排到一起

      • 每个reducetask生成最终的key与value

    • output

      • reducetask将结果输出到hdfs,避免结果文件过大问题

    源码分析

    map源码

     

    reduce源码

     

    MR的1.0与2.0

    1.0过程

    • Client

      • 客户端执行提交命令

        • hadoop jar wordcount.jar com.shsxt.ly.WordCountJob

      • 命令提交到JobTacker中

    • JobTacker

      • JobTacker与每个节点的taskTrcker保持心跳

      • 监控各节点的资源状态(CPU,IO)

      • jobTracker接收到任务后,将任务分配给(就近,空闲)的节点执行Task

      • 任务开始后,JobTacker监控TaskTracker的任务进度,在其他节点任务完成后,若还存在执行缓慢的TaskTracker,将task任务拷贝到其他节点执行,两个节点同时执行,采纳第一个完成的结果数据。

    • TaskTracker

      • 各节点各具备一个TaskTracker,监控所在节点资源(CPU IO 硬盘)

      • 与JobTacker保持心跳

      • 接收JobTacker的Task,TaskTracker分配节点资源给Task

      • 上述资源的大小单位为slot槽,slot的大小是固定的

    • Task

      • 分为maptask与reducetask

    关于1.0的缺陷

    • 单JobTacker,任务量过大容易宕机,易产生单点故障

    • JobTacker内存无法扩充

    • MR耦合度过高,只服务MR,对其他计算框架支持差

    2.0过程

    引入了yarn技术也就是ResourceManager与ApplicationMaster分别执行资源监控以及任务执行。

    • Client

    • ResourceManager

      • 资源管理集群

        可以使用主备节点架构

      • 可将Container与ApplicationMaster视为由ResourceManager生成

    • NodeManager

      • 负责本节点的资源管理

      • 实时与ResourceManager心跳汇报

      • 使得ResourceManger能够知道各节点的资源状态

    • Container

      • 从ResourceManager申请资源

      • 可以实现资源动态调整,任务查出申请资源额度则kill该task

    • ApplicationMaster

      • 一个MR任务对应的主节点

      • ResourceManager将ApplicationMaster随机分配到任意节点上

      • 负责应用程序相关的事务:任务调度、任务监控、容错

      • 将任务生成Task,分配到各节点上

      • task执行完毕,向ResourceManager发送信号

        • ResourceManger去kill当前对象

        • ResourceManger回收Container

    • Task

      • 实际任务执行者

      • 每个task中存有对应ApplicationTask的地址

      • 分为maptask与reducetask

      • maptask的数量与split数量一致

      • reduce需要手动指定,数量要少于节点数

      • 结果存放在hdfs上,避免容量不可控

    MR集群搭建

    基于hadoop2.x的HA集群搭建,需要hadoop与zookeeper关闭。

    配置文件

    目录/opt/sxt/hadoop-2.6.5/etc/hadoop

    1. 配置mapred-site.xml

      • cp mapred-site.xml.template mapred-site.xml

      • vim mapred-site.xml

        配置yarn框架

        <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
        </property>
    2. 配置yarn-site.xml

      • vim yarn-site.xml

      • yarn.resourcemanager.cluster-id配置yurn的集群名

      • yarn.resourcemanager.ha.rm-ids配置Reduce的主备Reduce(yurn)节点

      • yarn.resourcemanager.hostname.xxx配置Reduce各节点

      • yarn.resourcemanager.zk-address配置Map节点,与DN对应一致

        <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
        </property>
        <property>
        <name>yarn.resourcemanager.ha.enabled</name>
        <value>true</value>
        </property>
        <property>
        <name>yarn.resourcemanager.cluster-id</name>
        <value>mr_shsxt</value>
        </property>
        <property>
        <name>yarn.resourcemanager.ha.rm-ids</name>
        <value>rm1,rm2</value>
        </property>
        <property>
        <name>yarn.resourcemanager.hostname.rm1</name>
        <value>node3</value>
        </property>
        <property>
        <name>yarn.resourcemanager.hostname.rm2</name>
        <value>node1</value>
        </property>
        <property>
        <name>yarn.resourcemanager.zk-address</name>
        <value>node1:2181,node2:2181,node3:2181</value>
        </property>
    3. 将配置文件拷贝到其他节点

      scp mapred-site.xml yarn-site.xml  root@node3:`pwd`

    节点启动

    各节点 zkServer.sh start 启动Zookeeper

    主节点 start-all.sh 启动DFS与主yarn节点

    备用yarn节点 yarn-daemon.sh start resourcemanager

    jsp查看节点启动状态

    DFSZKFailoverController
    NameNode
    JournalNode
    DataNode
    ResourceManager             RS节点(reduce)主备
    NodeManager                 NM节点(map)对应DN
    QuorumPeerMain              
    Jps

    访问节点是:RS节点主机:8088

     

     

    MR计算实现

    1 依赖导入

    导入hadoop121个相关jar

    导入hadoop配置文件core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml到conf资源文件夹中

    2 Job类

    在main方法中写入配置信息

    • job名称,主类,Mapper类,Reduce类,Reduce数量,数据源路径,结果存放路径

    • mapper临时结果的key与value类型

    • 分区比较器,分组比较器

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.mapreduce.Job;
    import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    public class WeatherJob {
    public static void main(String[] args) throws Exception {
    //获取配置文件
    Configuration configuration = new Configuration(true);
    //本地模式运行
    configuration.set("mapreduce.framework.name", "local");
    //创建JOB
    Job job = Job.getInstance(configuration);
    //设置主Class
    job.setJarByClass(WeatherJob.class);
    //设置Job的名称
    job.setJobName("xxx");
    //设置JOB的Reduce数量
    job.setNumReduceTasks(2);
    //设置JOB的输入路径
    FileInputFormat.setInputPaths(job, new Path("/shsxt/java/weather"));
    //设置Job的输出路径
    FileOutputFormat.setOutputPath(job, new Path("/shsxt/java/weather_result_" + System.currentTimeMillis()));
    //设置Job-Mapper的输出key类型
    job.setMapOutputKeyClass(Weather.class);
    //设置Job-Mapper的输出value类型
    job.setMapOutputValueClass(IntWritable.class);
    //设置分区器
    job.setPartitionerClass(WeatherPartitioner.class);
    //设置分组比较器
    job.setGroupingComparatorClass(WeatherGroupingComparator.class);
    //设置Job的Mapper
    job.setMapperClass(WeatherMapper.class);
    //设置Job的Reduce
    job.setReducerClass(WeatherReducer.class);
    //等待Job的完成
    job.waitForCompletion(true);
    }
    }

    job说明

    1. job可以通过内部类直接包含map与reduce

      map与reduce均为静态内部类

    2. 定义K-V行读取方式

      job.setInputFormatClass(KeyValueTextInputFormat.class)

      KeyValueTextInputFormat是基于制表符(/t)分隔行数据,第一个 /t 之前的数据为key,之后为value;若行数据中无/t,默认行数据为key,value为空。

      若不设置默认与普通行读取器

    3. 基于配置文件的全局数据存取

      存:conf.set("key","value") 或setInt(key,2)...

      取:context.getConfiguration().getInt("key", 1) 后一个参数为空值时的默认值

      将数据存到conf配置中,可以存入多种类型的数据,使用对应的get,set方法进行存取

    4. 全局累加

      • 设置job的enum内部类作为累加计数器

        //枚举元素为my
        public static enum Mycounter { my }
      • 存值(在map与reduce)

        • context.getCounter(Mycounter.my).increment(xxx);

        • xxx就是累加到原先my中的值

        • 累加时需要取整

      • 对于循环job,每次job完成,累加计数器会重置

      • 取值

        • job.getCounters().findCounter(Mycounter.my).getValue()

        • 结果返回了一个long数值

    job循环

    • 循环在main方法中,可设置在while(true)死循环内

    • 循环跳出方式:

      • 在每次job执行后,等待waitForCompletion返回的执行结果true

      • 执行比较判断,跳出值与计算中产生的特定值比较,满足条件break

    • 循环外的配置项

      • 获取配置文件Configuration conf = new Configuration(true);

      • 项目执行方式:本地和跨平台

      • 设置循环结束的指标,也就是上述的跳出值

    • 循环内的配置项

      • 迭代的次数计数器 i++(在循环外设置int i=0)

      • 基础配置:当前job,主类,mapper,reduce,map输出的中间K-V的类型

      • 迭代的任务名与数据输入输出路径

        • 迭代的任务名job.setJobName("pagerank" + i)

        • 迭代的结果输出路径

          FileOutputFormat.setOutputPath(job, new Path("/output/xxx" + (i - 1)))

        • 迭代的数据输入路径

          配置第一迭代的数据输入路径(原始文件)

          FileInputFormat.addInputPath(job, new Path("/input"))

          配置第二次以后迭代输入路径,前一次计算结果作为后一次计算的输入

          FileInputFormat.addInputPath(job, new Path("/output/xxx" + i))

        • 最终效果为:第i次循环,数据来源路径为/output/xxx(i-1),计算结果存放路径为/output/xxx(i-1)

      • 等待单次job结束,并执行业务判断是否结束迭代循环

        boolean flag = job.waitForCompletion(true);

        //等待当前循环结束
        boolean flag = job.waitForCompletion(true);
        if (flag) {
           //执行业务判断(例:获取枚举计数器my的值)
           long sum = job.getCounters().findCounter(Mycounter.my).getValue();
           if (sum < xxx) {
               //满足条件跳出while循环
               break;
          }
        }

    3 Mapper类

    • XxxMapper的传入参数为:

      LongWriter行偏移量,Text行数据类型,临时结果key类型,临时结果value类型

    • map方法的参数为:

      key行偏移量,vlaue行数据,context上下文容器

    在map处理行数据,生成key与value的结果,并将行数据写出

    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    public class XxxMapper extends Mapper<LongWriter,Text,Xxx,IntWritable>{
    @Overrider
    protected void map(LongWritable key,Text vlaue,Mapper<LongWritable, Text, Weather, IntWritable>.Context context ){
           //根据需求对value执行分析处理,生成key与value
           //可以将key或value数据存入自定义对象中
           //示例代码将vlaue中数据转为自定义Weather对象和hadoop的IntWritable对象
           String[] ss = value.toString().split(" ");
           Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(ss[0]);
           Calendar calendar = Calendar.getInstance();
           calendar.setTime(date);
           IntWritable outputValue = new IntWritable(Integer.parseInt(ss[1].replace("c", "")));
           Weather weather = new Weather();
           weather.setYear(calendar.get(Calendar.YEAR));
           weather.setMonth(calendar.get(Calendar.MONTH) + 1);
           weather.setDay(calendar.get(Calendar.DAY_OF_MONTH));
           weather.setTemperature(outputValue.get());
           //写出result结果context.write(key,value),注意数据类型
           context.writer(weather,outputValue);
      }
    }

    map端合并

    在job中设置:job.setCombinerClass(xxxReduce.class),该类与reudce基本相同

    处理指定的切片

    //获取切片
    FileSplit fs = (FileSplit) context.getInputSplit();
    //判断切片所在的文件,是否是包含xxx路径文件的数据
    //若包含则处理切片
    //若是取反,则表示不处理包含某个文件数据的切片
    if (fs.getPath().getName().contains("xxx")) {
    //这里执行map代码
    }

    4 Reducer类

    • XxxReducer的传入参数依次为:

      • Xxx临时结果的key类型

      • IntWritable临时结果的value类型

      • Text最终结果的key类型

      • IntWritable最终结果的value类型

    • reduce方法的参数依次为:临时结果的key,key所对应的所有临时结果values,上下文context

    在map方法中

    • 通过迭代器获取每个临时结果的value与对应的key

      • Iterator<IntWritable> iterator = values.iterator();

      • while (iterator.hasNext()) { iterator.next() } 中, iterator.next()为临时value值,key为临时key值

      • key具体值可能不相同,只是分组比较器将其识别为形同的key分到一个reduce中处理

    • 通过上下文context输出

      • context.writer(最终key,最终value)

      • 在一个reduce中context输出多次

    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapred.ReduceTask;
    import org.apache.hadoop.mapreduce.Reducer;
    public class WeatherReducer extends Reducer<Weather, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Weather key, Iterable<IntWritable> values, Reducer<Weather, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
    //设置计数器
    int count = 0;
    //获取临时结果的迭代器
    Iterator<IntWritable> iterator = values.iterator();
    //遍历迭代器中的数据,
    while (iterator.hasNext()) {
    Text outputKey = new Text(key.getYear() + "-" + key.getMonth() + "-" + key.getDay());
    IntWritable outputValue = iterator.next();
    //判断是否需要退出
    count++;
    if (count <= 2)
                //将数据写出
    context.write(outputKey, outputValue);
    }
    }
    }

    5 hadoop数据包装类型

    内部自带比较器

    • Text文本类型

      toString()转为字符串

    • LongWritable和IntWritable表示整形数字,DoubleWritable表示浮点小数

      上述包装类型的get方法返回对应的类型的数值,get()-->int/long/double

    java数值或String通过带参构造转为包装类:new Text("参数")

    6 关于key自定类

    • 实现WritableComparable<>接口,范型传入自身

    • 内部写入若干自定义字段

    • 实现接口的三个方法

      • write(定义序列化的数据)

        out.writeInt(this.year); 定义字段以何种数据类型写入硬盘

      • readFields(定义反序列化的数据)

        this.year=in.readInt(); 定义字段以何种格式从硬盘输出到内存

      • 注:write与readFields的字段的顺序和类型必须一致

      • compareTo(定义比较方法,实现基于key的排序比较)

        使用字段进行多重比较,借助字段本身的compareTo方法实现比较

    • set,get,toString,hashCode方法

    import org.apache.hadoop.io.WritableComparable;
    public class Weather implements WritableComparable<Weather>{
    private Integer year;
    private Integer month;
    private Integer day;
    private Integer temperature;

    //写出数据,序列化
    @Override
    public void write(DataOutput out) throws IOException {
    out.writeInt(this.year);
    out.writeInt(this.month);
    out.writeInt(this.day);
    out.writeInt(this.temperature);
    }
    //读入数据,反序列化
    @Override
    public void readFields(DataInput in) throws IOException {
    this.year=in.readInt();
    this.month=in.readInt();
    this.day=in.readInt();
    this.temperature=in.readInt();
    }

    //比较方法
    @Override
    public int compareTo(Weather o) {
    int result=this.year.compareTo(o.getYear());
    if(result==0){
    result=this.month.compareTo(o.getMonth());
    if(result==0){
    result=this.temperature.compareTo(o.getTemperature());
    }
    }
    return result;
    }
    //set,get,toString,hashCode省略
    }

    7 分区比较器

    控制自定义相同的key进入同一个分区(reduce节点)

    对于自定类,将需要处于同一分组的各字段构建出数字,该数字对分区数(reduces数)取余。

    • 继承Partitioner<>传入需要比较的key与value类型

    • 重写getPartition方法,其中numPartitions就是分区数

    import org.apache.hadoop.mapreduce.Partitioner;
    public class WeatherPartitioner extends Partitioner<Weather, IntWritable>{
    @Override
    public int getPartition(Weather key, IntWritable value, int numPartitions) {
    return (key.getYear() + key.getMonth())%numPartitions;
    }
    }

    控制某些key独享一个分区reduce,使用如下代码

    public class FirstPartition extends HashPartitioner<Text, IntWritable> {
    public int getPartition(Text key, IntWritable value, int reduceCount) {
    //if中执行逻辑判断,使得满足条件的key进入
           if (key.equals(new Text("xxx"))) {
               //指定分配,由于reduceCount个数从0计数,直接分配的最后一个reduce分区
    return reduceCount - 1;
               //可以多个条件,reduce值从大到小分配对应的key
    } else {
               //若并非指定的key,则使用父类的默认分区器,平分剩余的reduce分区
               //注意:reduceCount需要减去已分配的分区,避免冲突
    return super.getPartition(key, value, reduceCount - 1);
    }
    }
    }

    8 分组比较器

    控制自定义相同的key进入同一个分组(reducetask)

    对于自定义类,将需要处于同一reducetask的字段进行比较

    • 继承WritableComparator类

    • 构造器中传入自定义类

    • 重写public int compare(WritableComparable a, WritableComparable b)方法

      其中a和b两个比较的对象,需要强转为自定义类

    import org.apache.hadoop.io.WritableComparable;
    import org.apache.hadoop.io.WritableComparator;
    public class WeatherGroupingComparator extends WritableComparator{
       //构造器传入自定义类
    public WeatherGroupingComparator() {
    super(Weather.class ,true);
    }
       //重写比较器
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
           //强转为自定义类
    Weather w1=(Weather) a;
    Weather w2=(Weather) b;
           //将指定字段的值进行比较
           //本例中相同的year与month能够分在一个reducetask中
    int result=w1.getYear().compareTo(w2.getYear());
    if(result==0){
    result=w1.getMonth().compareTo(w2.getMonth());
    }
    return super.compare(a, b);
    }
    }

    9 运行及打包

    • 打包方式

      项目右键Export,选择JAR file ,选择打包项目,JAR file中选取导出路径。

      将jar文件放入linux节点中,运行命令:hadoop jar xxx.jar com.jay.XxxJob (指定jar文件与主类)

    • 本地运行

      job类中加入配置:configuration.set("mapreduce.framework.name", "local");

      右键-run-hadoop

    • 跨平台运行

      conf.set("mapreduce.app-submission.corss-paltform", "true");

      conf.set("mapreduce.framework.name", "local");

    10 数据拉取到本地进行计算

    • job配置:

      job.addCacheFile(new Path("/xxx/xxx").toUri()); 将hdfs的数据载入缓存

    • map及reduce中的使用

      重写setup方法,该方法是另一个前置方法,且只执行一次

      //获取所有缓存文件的路径数组
      URI[] cacheFiles = context.getCacheFiles();
      //依次遍历缓存文件
      for (int i = 0; i < cacheFiles.length; i++) {
         //获取缓存文件的名字
         URI uri = cacheFiles[i];
         // 获取制定的文件
         if (uri.getPath().endsWith("xxx文件名")) {
             //定义文件路径
             Path path = new Path(uri.getPath());
             //获取字符流
             BufferedReader br = new BufferedReader(new FileReader(path.getName()));
             String line =null;
             String alldata=null;
             //循环一行一行读取,最后拼接起来
             while ((line = br.readLine()) != null) {
                 String alldata=line+"";//可以使用其他方式
      }
             br.close();
        }
      }

       

     

     

  • 相关阅读:
    js原型、原型链、继承的理解
    实用的Object操作方法
    数组操作方法汇总
    ES6数组去重、字符串去重
    ES6新增数据结构:Set和Map
    canvas图片、文字在移动端显示模糊问题
    Tabindex
    javascript 单元测试初入门
    ng-file-upload(在单文件选择,并且通过点击“上传”按钮上传文件的情况下,如何在真正选择文件之前保留上一文件信息?)
    如何优化表单验证
  • 原文地址:https://www.cnblogs.com/javaxiaobu/p/11703001.html
Copyright © 2020-2023  润新知