MapTask并行度和切片机制

290 阅读6分钟

一. MapTask 并行度决定机制

maptask 的并行度决定 map 阶段的任务处理并发度,进而影响到整个 job 的处理速度

那么, mapTask 并行实例是否越多越好呢?其并行度又是如何决定呢?

  • mapTask 并行度的决定机制

( 1 )一个 job map 阶段并行度由客户端在提交 job 时决定

( 2 )而客户端对 map 阶段并行度的规划的基本逻辑为:

将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个 split ),然后 每一个 split 分配一个 mapTask 并行实例处理

( 3 )注意: block 是 HDFS 上物理上存储的存储的数据,切片是对数据逻辑上的划分。两者之间没有关系。即使 hdfs 上是 128M 存储, Mapreduce 也会切片,只是默认切片也是 128M 。也可以非 128M 切片,如 100M ,多余的部门由框架内部处理和其他结点进行拼接切片。因为切片决定了给其分配 mapTask 进程数量。

( 4 )在 HDFS 上 ,map 默认运算切片大小是 128M ,但如果是本地运行的话, maP 默认切片大小是 32M. 。 maptask  和 reducetask 其实都框架下的一个类。

这段逻辑及形成的切片规划描述文件, 由 FileInputFormat 实现类的 getSplits() 方法 完成,其过程如下图:

二.InputFormat 数据切片机制

主要有两个方法:getsplit(),客户端用来切片。

creatRecordReader()MR用来读数据

1.FileInputFormat 切片机制(在提交yarn前已完成,客户端完成)

         注解: FileInputFormat 继承与 InputFormat 类,都是 mapreduce 包下的类。 归其管理

         其余的常用类,如 TextInputFormat 和 CombinInputFormat 都是 FileInputFormat 的子类。

 1、切片定义在InputFormat类中的getSplit()方法

 2、FileInputFormat中默认的切片机制:

  • 简单地按照文件的内容长度进行切片

  • 切片大小,默认等于block大小(这样如果有很多小文件时,就会产生很多切片,造成很多个maptask,降低系统性能)

  • 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

比如待处理数据有两个文件:

file1.txt    320Mfile2.txt    10M
 

经过 FileInputFormat 的切片机制运算后,形成的切片信息如下:  

file1.txt.split1--  0~128file1.txt.split2--  128~256file1.txt.split3--  256~320file2.txt.split1--  0~10M

. FileInputFormat 中切片的大小的参数配置

\

  • FileInputFormat 源码解析 (input.getSplits(job))从job.waitforComplecation开始断点,直到提交到yarn上。

( 1 ) 找到你数据存储的目录。(可以是目录,多个文件同时进行统计,然后将统计结果装载到一个文件或者多个文件里。。也可以单独统计分析 1 个文件。)

( 2 ) 开始遍历处理(规划切片)目录下的每一个文件

( 3 )遍历第一个文件 ss.txt

              a) 获取文件大小 fs.sizeOf(ss.txt);

              b) 计算切片大小 computeSliteSiz e(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M

              c) 开始切,形成第 1 个切片: ss.txt—0:128M 第 2 个切片 ss.txt—128:256M 第 3 个切片 ss.txt—256M:300M ( 每次切片时,都要判断切完剩下的部分是否大于块的 1.1 *倍, *不大于 1.1 倍就划分一块切片 )当然类中方法也会判断,如果是压缩文件之类的,是不会切片的。报异常。

              d) 将切片信息写到一个切片规划文件中

              e) 整个切片的核心过程在 getSplit() 方法中完成。(是 FileInputFormat 类中的方法)

( 4 ) 提交切片规划文件到 yarn 上, yarn 上的 MrAppMaster 就可以根据切片规划文件计算开启 maptask 个数。

通过分析源码,在 FileInputFormat 中,计算切片大小的逻辑: Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定

  FileInputFormat 中默认的切片机制:

(1)简单地按照文件的内容长度进行切片
(2)切片大小,默认等于block大小
(3)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片\

比如待处理数据有两个文件:

file1.txt    320Mfile2.txt    10M

经过 FileInputFormat 的切片机制运算后,形成的切片信息如下:  

file1.txt.split1--  0~128file1.txt.split2--  128~256file1.txt.split3--  256~320file2.txt.split1--  0~10M

4 ) FileInputFormat 切片大小的参数配置

通过分析源码,在 FileInputFormat 中,计算切片大小的逻辑: Math.max(minSize, Math.min(maxSize, blockSize));  

切片主要由这几个值来运算决定

mapreduce.input.fileinputformat.split.minsize=1 默认值为 1

mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认值 Long.MAXValue

因此, 默认情况下,切片大小 =blocksize 。

maxsize (切片最大值):参数如果调得比 blocksize 小,则会让切片变小,而且就等于配置的这个参数的值。

minsize (切片最小值):参数调的比 blockSize 大,则可以让切片变得比 blocksize 还大。

5 )获取切片信息 API  : FileSplit 是 inputSplit 的子类。

// 根据文件类型获取切片信息FileSplit inputSplit = (FileSplit) context.getInputSplit();// 获取切片的文件名称String name = inputSplit.getPath().getName(); // 获取的是被切片文件名:

选择并发数的影响因素:

1- 运算节点的硬件配置

2- 运算任务的类型:CPU密集型还是IO密集型

3- 运算任务的数据量

  • map并行度的经验之谈
  1. 如果硬件配置为 2*12core + 64G ,恰当的 map 并行度是大约每个节点 20-100 个 map , 最好每个 map 的执行时间至少一分钟。

  2. 如果 job 的每个 map 或者  reduce task 的运行时间都只有 30-40 秒钟,那么就减少该 job 的 map 或者 reduce 数,每一个 task(map|reduce) 的 setup 和加入到调度器中进行调度,这个中间的过程可能都要花费几秒钟,所以如果每个 task 都非常快就跑完了,就会在 task 的开始和结束的时候浪费太多的时间。

配置 task 的 JVM 重用可以改善该问题:

mapred.job.reuse.jvm.num.tasks 默认是 1 ,表示一个 JVM 上最多可以顺序执行的 task

数目(属于同一个 Job )是 1 。也就是说一个 task 启一个 JVM    

注释: JVM 重用技术不是指同一 Job 的两个或两个以上的 task 可以同时运行于同一 JVM 上,而是排队按顺序执行

  1. 如果** input 的文件非常的大,比如 1TB ,可以 考虑将 hdfs 上的每个 block size 设大,比如设成 256MB 或者 512MB
  • ReduceTask并行度的决定

reducetask的并行度同样影响整个job的执行并发度和执行效率,但与 maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置,默认值为1.

//默认值是1,手动设置为4

job.setNumReduceTasks(4);

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

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

尽量不要运行太多的reduce task。对大多数job来说,最好rduce的个数最多和集群中的reduce持平,或者比集群的 reduce slots小。这个对于小集群而言,尤其重要。

1)job提交流程源码详解

waitForCompletion()

submit();

// 1建立连接

       connect();

              // 1)创建提交job的代理

              new Cluster(getConfiguration());

                     // (1)判断是本地yarn还是远程

                     initialize(jobTrackAddr, conf);

       // 2 提交job

submitter.submitJobInternal(Job.this, cluster)

       // 1)创建给集群提交数据的Stag路径

       Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

       // 2)获取jobid ,并创建job路径

       JobID jobId = submitClient.getNewJobID();

       // 3)拷贝jar包到集群

copyAndConfigureFiles(job, submitJobDir);

       rUploader.uploadFiles(job, jobSubmitDir);

// 4)计算切片,生成切片规划文件

writeSplits(job, submitJobDir);

       maps = writeNewSplits(job, jobSubmitDir);

              input.getSplits(job);

// 5)向Stag路径写xml配置文件

writeConf(conf, submitJobFile);

       conf.writeXml(out);

// 6)提交job,返回提交状态

status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());