MapReduce

MapReduce

013、014-MapReduce

MapReduce 的思想是后续大数据组件影响是一直存在的。它提供了非常多的接口,能做到分布式。但分布式是无感知的,我们编写的单机应用程序在MapReduce上运行其实是分布式的。但它只适用于离线计算,也叫批计算的场景。不适用实时、流式计算。

MapTask 的个数

某些文件并不是一定能分片,只有一个 task来处理。
能分片成几个 128M,则可以有几个task 并发处理。

不同的文件是在不同的MapTask(一定个数内,有几个文件就几个task)
只有一个文件且小于128M,只有一个task
只有一个文件(大于128M),且不能分片,只有一个task
只有一个文件(大于128M),且可以分片,有几个分片数就有几个task

MapTask 并不是越多越好:MapTask还是ReducerTask都是以JVM的方式进行运行的
MapTask过多,会启动N个JVM,JVM的启动和销毁,是需要一定的成本开销的
一般情况下不去调整MapTask个数,交给框架处理。

使用MR来完成WC统计,按MR的编程规范:

自定义实现Mapper: 拆分单词,每个赋值1。 每一行运行一次mapper方法(不代表一行就有一个Maptask,IntputSplit 也不等同于代码内每行split(","))
自定义实现Reduecer:统计。 每个重复key运行一次reducer方法
自定义实现Driver : 八股文编程,固定的7个步骤:把我们自定义的Mapper Reducer  Input Output组装起来 形成一个MR作业 => job
Mapper类四个泛型的意思:
  KEYIN: 输入数据的KEY的数据类型
  VALUEIN:输入数据的VALUE的数据类型
  KEYOUT: 输出数据的KEY的数据类型
  VALUEOUT:输出数据的VALUE的数据类型

Mapper的输出是要交给Reducer作为输入的
所以一般情况下:Mapper的输出类型 == Reducer的输入类型

Mapper<K1,V1,K2,V2>
Reduce<K2,V2,K3,V3>
Mapper类中提供了模板方法,管理了Mapper的生命周期:
 setup:  每个MapTask开始执行一次
 cleanup:每个MapTask结束执行一次
 map:    我们自定义的类中要实现的map方法
 run:典型的模板模式,定义执行流程:setup  map  cleanup

每一次进入一个mapper的是一行数据。

Split: 只是一个逻辑概念,并不会存储
Block: 是一个物理概念,数据是按Block存储。128M
Maptask个数:文件按Block分块后,Block块的个数

IntputSplit大小 == Block 大小
IntputSplit个数 == Maptask个数
Reduce:聚合,每个单词出现的次数 累加起来
KEYIN     Text
VALUEIN   IntWritable
KEYOUT    Text
VALUEOUT  IntWritable

经过shuffle是按照指定的规则进行的: 相同的key才会发到同一个reducer去执行。所以在reducer中直接将进来的value都求和,最后再统一输出

输入 ==> 处理 ==> 输出
输入:InputFormat
         getSplits: 拿到你要处理的输入数据的分片信息(数据的大小、是否能分片。)
         createRecordReader:如何去读数据
处理:
    map
    reduce
输出:OutputFormat

序列化

为什么大数据中类型是 LongWritable:序列化
分布式应用程序是运行不同的机器上的 ==> 网络传输。就需要序列化。
序列化:内存中的对象 ==>字节数组
反序列化:字节数组 ==>内存中的对象

默认Java的序列化效率会低一点
Hadoop序列化的机制kryo:紧凑 快速 扩展性 互操作

引入了自己的序列化机制:Writable
Writable 接口规范了两个方法,write():实现序列化,readField() :实现反序列化

分区

自定义分区器


public class PhonePartitioner extends Partitioner<Text, Access> {
    public int getPartition(Text text, Access access, int numPartitions) {
      // 继承Partitioner抽象类并重写getPartition,指定分区规则
    }
}

默认是一个reduce
MR中,有几个reduce,就表示最终有几个文件输出: part-r-00000、part-r-00001、part-r-00002

设定的reduce个数 = 1 (默认)): 输出只有一个文件(不分区)
设定的reduce个数 > 分区数:输出文件个数==reduce个数,但是存在空文件
设定的reduce个数(但不等于1) < 分区数:会报错,会存在分区无法放入reduce


如果只有map没有reduce,那么最终的文件个数就由map个数决定

Combiner

Combiner 是介于 mapper 和 reducer 之间的,是运行在map阶段时的一次局部聚合(运行在map阶段的reduce),但仅仅是局部,将相同key的数据事先放到一起了,可以减少shuffle传输到reduce时的网络开销。

Combiner 的逻辑和 reducer 逻辑是相关甚至相同的。对于wc来说:统计每个单词的出现的频次。

reducer: 次数累加
所以combiner是直接使用reducer就可以
// 设定Combiner,直接照搬 reducer
 job.setCombinerClass(WordCountReducer.class);

并不适合所有业务场景,如平均数计算。如果是在Combiner中做了一次平均数,再传输到reduce做均数结果是不对的。
spark、flink也有。

计数器Counter

计数器的使用
    1)处理的数据的行数
    2)单词总数

// 在map端获取计数器,并将结果累加1
context.getCounter(COUNTER_GROUP, LINES_GROUP).increment(1L); // 行数
context.getCounter(COUNTER_GROUP, WORDS_GROUP).increment(1L); // 单词总数,写在value的循环中

// 在 driver 中从计数器获取结果[迭代]
CounterGroup counters = job.getCounters().getGroup(COUNTER_GROUP);

排序

在大数据中,全局排序

只有一个reduce
 1)挂了
 2)非常慢

全局排序在Hive、SparkSQ中: order by

大数据中排序,用的比较多的是:分区排序
分区排序在Hive、SparkSQ中: sort by

二次排序(待补充)。

intputFormat 和 outputFormat

job.setInputFormatClass(KeyValueTextInputFormat.class);
job.setOutputFormatClass(MyOutputFormat.class);  // 指定自定义的OutputFormat
group by / distinct

GroupBy 就是一个wc,不再直接用key,而是按value中指定的字段来聚合而已
Distinct 就是在map 里分词后,相同的key只保留一个呗 – 甚至不需要我们写这个去重逻辑,交给shuffle就去重了

Join

reduceJoin

mapper: 将两个表数据一起读取到同一个类,但是要区分来在哪个表,并以期望关联的字段作为key(IntWritable),以对象作为value(Info)输出。

reducer: 经过shuffle后的相同的key已经被放到同一个reducer,所以在reducer中将两个对象们的信息组装到同一个对象中即可。

reduceJoin会经过shuffle,必然有网络IO、磁盘IO的开销,效率是有损耗的。


mapJoin

也叫广播join

driver: 将小表数据在driver端addCacheFile。

mapper: 在Mapper端setup()方法通过上下文拿到缓存的小表:context.getCacheFiles();map()方法中将每一个大表数据都去缓存里比对并关联。对象直接作为key(Info),value(NullWritable)输出。

不需要reducer,也不经过shuffle。

压缩

不同的压缩格式使用不同的压缩算法来实现。不同压缩格式的压缩比、压缩时间都有区别。
解压缩:

  有损: 压缩前     解压后   大小可能有丢失
  无损: 前后是一样的

压缩、解压缩的静态工具类。

MapReduce整合压缩。
受MR支持压缩的格式可以直接被input。
对于压缩后output

// 一个是开关,一个是指定具体的codec类型  (只是Output)
FileOutputFormat.setCompressOutput(job, true);
FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);

提交作业的源码

016-MapReduce-04

submiter.submitJobInternal(true){
    submit(){
        connect(){
            Cluster // 描述是否要提交到集群(Yarn 和 Local)
        }
        
        submiter.submitJobInternal{
            checkSpecs(job){
                output.checkOutputSpecs(job); // 检查文件是否已存在。通过反射拿到outputFormat来判断
            } 
            addMRFrameworkToDistributedCache(); // 添加到分布式缓存
            JobSubmissionFiles.getStagingDir(cluster, conf); // 得到本地的工作空间的目录
            InetAddress.getLocalHost() // 获取ip
            
            JobID jobId = submitClient.getNewJobID();
            job.setJobID(jobId); // 关联jobId
            Path submitJobDir = new Path(jobStagingArea, jobId.toString()); // 提交本作业时的工作空间
            
            copyAndConfigureFiles(job, submitJobDir); // 
            
            // 计算分片数
            int maps = writeSplits(job, submitJobDir){
                writeNewSplits(job, jobSubmitDir){
                    InputFormat // 反射,根据driver中设置拿到
                    input.getSplits(job); // 得到分片信息 (抽象类,需要找一个实现如FileInputFormat#getSplits()) 
                    getSplits(){
                        long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); // Math.max(1, 1) = 1
                        long maxSize = getMaxSplitSize(job); // maxSize = Long.MAX_VALUE
                        
                        // 根据文件blockSize计算splitSize
                        long splitSize = computeSplitSize(blockSize, minSize, maxSize){ // computeSplitSize(32M, 1, Long.MAX_VALUE)
                            return Math.max(minSize, Math.min(maxSize, blockSize)); // Math.max(1, returnMath.min(Long.MAX_VALUE, 32M)) ==32M
                        }
                    }
                }
            }; 
            
            writeConf(conf, submitJobFile);  // 相关配置写入到该作业
            status = submitClient.submitJob(); // 
        }
        
    }
  
}


数据本地性

存储计算分离:计算时hdfs的数据节点DN 与 Yarn中NM(Container)不在同一节点,每次获取到Container都要去其他节点获取数据用于计算,增加网络IO和磁盘IO。

让hdfs的数据节点DN 与 Yarn中NM(Container)部署在同一节点,移动计算要远远好于移动数据。

DN与NM部署在同一节点也可能出现 NM去拉取了其他节点DN的数据过来计算。
所以又引入时间降级概念,如果在DN当前节点的container正在被其他节点使用,可以短暂等待container释放,牺牲了一部分时间换来了减少IO。

官网解读

Shuffle 洗牌

在分布式框架中都离不开的概念。

Mapper<K1,V1,K2,V2>
Reduce<K2,V2,K3,V3>

经过shuffle后,相同的key一定在一个reduce,不同的key也有可能在一个reduce(比如只有一个reduce)。注:这里的一个reduce不等于MR编程中的调用一次reducer方法。

MR过程

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

MR 优化涉及参数

优化点:

  • Input:
    压缩,可以节省hdfs空间和io,但是要消耗CPU的
  • Mapper:
    缓冲区Buffer:
    mapred-default.xml
// 用于排序的总的Buffer的大小,单位是M。
mapreduce.task.io.sort.mb = 100
// 百分比。当达到Buffer总大小的80%时,就会开启一个线程将buffer内容写入磁盘。
mapreduce.map.sort.spill.percent = 0.8
  • Reducer:
mapreduce.map.maxattempts   // map最大重试次数,当任务挂掉后最大可以重试的次数,默认4。  
mapreduce.reduce.maxattempts  // reduce最大重试次数,默认4。
mapreduce.am.max-attempts  // AM 最大重试次数,默认2。一般不改变。
  • 为什么Hadoop不适合小文件存储
  1. 元数据维护压力大
  2. 分块存储浪费空间
  • 小文件:尽可能规避(废话,规避不了)。用Spark读取再写入,就能合并小文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值