• Shuffle流程


    俗称:洗牌

    1.MapReduce的执行流程(官网图)

      InputFormat-->InputSplit-->map函数(mapper)-->环形缓冲区-->partition(分区)-->sort(排序)-->spill to disk(溢写至磁盘)-->merge(合并)-->存储在maptask节点的本地(本地存储)-->fetch(通过http协议拉取map端的输出结果,按照partititon)-->merge(合并)将来自不同节点的map输出数据,合并成一个大的文件,供reduce使用-->reduce函数(reducer)-->output

    2.shuffle流程(源代码分析)

     

    1.【MapTask.class】类的分析
    run()-->首先,判定是否存在Reduce阶段,通过conf.getNumReduceTasks()的值;如果无Reduce阶段,将mapPhase参数设置成1(100%),无sort阶段;
    如果存在Reduce阶段,将MapTask分成两个阶段:一个是mapPhase(67%);另一个为sortPhase(33%).
      -->initialize(job, getJobID(), reporter, useNewApi);
        -->将job的state设置成RUNNING,并且验证output
      -->runNewMapper(job, splitMetaInfo, umbilical, reporter);
        -->【make a mapper(创建Mapper)】ReflectionUtils.newInstance(taskContext.getMapperClass(), job);
          -->conf.getClass(MAP_CLASS_ATTR, Mapper.class);
    说明:通过反射机制获取由taskContext.getMapperClass()定义的相关Mapper类;
    MAP_CLASS_ATTR = "mapreduce.job.map.class";
    参照dirver类:job.setMapperClass(AirMapper.class)--> conf.setClass(MAP_CLASS_ATTR, cls, Mapper.class);
        -->【make the input format(创建InputFormat)】 略!!!
        -->【获取当前的InputSplit】getSplitDetails()
        -->定义input、output
          -->通过reduce的个数,判定output路径:
            如果reduce=0,output路径由FileOutputFormat.OUTDIR决定;
            如果reduce!=0,output路径由MapOutputCollector决定;
        -->包装context上下文对象
        -->【RecordReaer的初始化方法】input.initialize(split, mapperContext);
        --> mapper.run(mapperContext);
    说明:真正加载自定义的Mapper类,调用run(),循环遍历(context.nextKeyValue())
          -->context.write(new Text(year), air);
          -->【WrappedMapper.class】write(KEYOUT key, VALUEOUT value)
          -->【TaskInputOutputContextImpl】write(KEYOUT key, VALUEOUT value)
          -->【MapTask.class$NewOutputCollector<K,V>.class】write(K key, V value)
          -->【MapTask.class$MapOutputBuffer.class】collect(K key, V value, final int partition)
            -->说明:collect收集来自mapper.writer处理的k2,v2,partition
    判定keyClass和ValueClass是否符合格式,确保partition数不能为复数,并且不能大于reduce数量,否则抛异常IOException;
    检查spill溢写异常处理:checkSpillException()
            -->【缓冲区剩余大小计算为:阈值0.8(80%),80m】bufferRemaining=83886080(80m)
    【缓冲区每个元数据默认大小】METASIZE=16=NMETA * 4;NMETA:元数据个数为4个int,每个int占4个字节
    【(元数据)kvmeta:VALSTART+KEYSTART+PARTITION+VALLEN(4个int类型)】
          -->
          -->
      -->【MapTask.class$MapOutputBuffer.class】环形缓冲区
        说明:init()关键代码:
          a. mapOutputFile = mapTask.getMapOutputFile()
            -->map本地存储的目录为:
            在mapred-site.xml文件中:mapreduce.cluster.local.dir = ${hadoop.tmp.dir}/mapred/local
          b. partitions = job.getNumReduceTasks()
            --> 默认分区数取决于reduce数
          c. rfs = ((LocalFileSystem)FileSystem.getLocal(job)).getRaw()
            -->说明map阶段将结果写入到localFileSystem
          d. spillper = job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8)
            -->在mapred-site.xml文件中:mapreduce.map.sort.spill.percent = 0.8
          e.sortmb = job.getInt(JobContext.IO_SORT_MB, 100);
            -->定义默认缓冲区值为100;mapreduce.task.io.sort.mb=100
    f.sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class",QuickSort.class, IndexedSorter.class), job);
            -->定义排序算法默认为QuickSort.class
          g. kvbuffer = new byte[maxMemUsage];
            -->定义初始化缓冲区大小
          h. kvmeta =         ByteBuffer.wrap(kvbuffer).order(ByteOrder.nativeOrder()).asIntBuffer();
            -->定义元数据存储大小:IntBuffer
          i.setEquator(0);
            -->设置默认原点;
            说明:flush()关键代码:
    a. sortAndSpill()
      排序并溢写:

      

      1). -->Path filename =mapOutputFile.getSpillFileForWrite(numSpills, size);
        说明:创建mapoutputfile:默认路径【 mphadoop-centosmapredlocallocalRunnercentosjobcachejob_local397295198_0001attempt_local397295198_0001_m_000000_0outputspill0.out】
    ------------------------------Map阶段第一次sort开始---------------------------------------------------
      2). -->sorter.sort(MapOutputBuffer.this, mstart, mend, reporter);
        说明:先执行sort,默认采用的是QuickSort。
      3). -->【MapOutputBuffer.class】compare(final int mi, final int mj)
        说明:排序比较:先按partition分区排序,然后再按key排序;按kvmeta(元数据)进行整体sort
      4). -->【MapOutputBuffer.class】swap(final int mi, final int mj)
        说明:转换:在metadata中进行转换
    ------------------------------Map阶段第一次sort结束---------------------------------------------------
      5). --> writer.append(key, value);
        说明:先按partition分区进行循环遍历,将<key,value>append至writer,最终写入到spillfile中
    spill格式:p0[<k,v>:<k,v>:<k,v>...:<k,v>:<k,v>];p1[<k,v>:<k,v>:<k,v>...:<k,v>:<k,v>];p2[<k,v>:<k,v>:<k,v>...:<k,v>:<k,v>]
      6). -->spillThread.interrupt();
        说明:spill线程结束。并关闭;
      7). -->mergeParts();
        说明:开启merge阶段
        判定:当numSpills == 1;将spillfile文件重命名为file.out,并生成相应的file.out.index
    当numSpills == 0;创建空文件
    当numSpills > 0; 先循环遍历partition分区,然后再遍历溢写文件,将相同分区的不同spillfile的内容添加至ArrayList<Segment>中
    调用Merger.merge()方法,进行合并;开启Map阶段的第二次排序
    ------------------------------Map阶段第二次sort开始---------------------------------------------------
    说明:默认采用java集工具类的sort方法:Collections.sort(segments, segmentComparator);
    Comparator<Segment<K, V>> segmentComparator =
    new Comparator<Segment<K, V>>() {
    public int compare(Segment<K, V> o1, Segment<K, V> o2) {
    if (o1.getLength() == o2.getLength()) {
    return 0;
    }

    return o1.getLength() < o2.getLength() ? -1 : 1;
    }
    };
    ------------------------------Map阶段第二次sort结束---------------------------------------------------
      8). -->write merged output to disk:将merge后的数据写入到disk

     

    在reduce端通过http协议抓取到磁盘的数据,在进行merge,与之前map端的merge相同,并且进行排序,到reduce函数,最后输出。

     

  • 相关阅读:
    面向对象——多态
    面向对象——继承
    面向对象—封装
    数组
    控制语句
    认识Java
    出入境大厅流程
    2021上半年感想
    记录2020
    读后感《从三分钟热度到一万个小时》
  • 原文地址:https://www.cnblogs.com/lyr999736/p/9381430.html
Copyright © 2020-2023  润新知