谈笑间学会MapReduce-优化Map任务核心原理
优化Map任务
如何优化呢?首先我们要知道map任务这一阶段的流程和瓶颈,才可以进行“因材施教”进行优化,map任务流程图如下:
由图可知,详细流程如下:在Read阶段,map任务从Hadoop分布式文件系统(HDFS)读取固定大小(如64MB)的数据块。而写入的文件根据实际的情况也不同,可以是任意大小的数据块(如80MB)。这种情况下,为了存储数据,就有两种数据块:一种是64MB,另一种是16MB。在这个阶段进行性能分析时,我们不仅要测量Read阶段的持续时间,还要记录map任务读取的数据量。
要分析map阶段,就要测量整个map函数的执行时间和处理的记录总数,并规范化为每条记录的处理时间。测量执行时间时需要检查超常规数据,这通常是大量小文件或者单个不可拆分的大文件造成的。可以通过比较所以的map任务(同一作业)的输入数据大小,来发现是否存在超常规数据。
在spill阶段,框架对中间数据进行本地排序,并针对不同reduce任务进行划分,如果有可用的combiner则进行合并,然后把中间数据写入磁盘,要对这个阶段进行性能分析,我们要测了执行上述全部任务的时间。如果使用了combiner,处理时间应该包含执行时间。
在Fetch阶段,我们要测量框架把Map阶段的输出缓冲到内存花费的时间以及产生的中间数据的量。最后一个Merge阶段,针对每一个reduce任务,我们要测量框架把不通的溢写文件合并成单个溢写文件花费的时间。
输入数据和块大小的影响
在进入Read阶段之前,需要先在文件系统中定位数据。数据模式也会影响MapReduce作业性能。要使map任务高效运行,数据必须可拆分,比如文本文件,这样MapReduce就可以把任务分块分别处理。拆分后的文件大小应该足以填充一个数据块。数据块的大小非常重要,它决定了数据如何被拆分,以及把每个输入分片如何分配给mapper。因此,如果数据集很大而数据块很小,就会导致mapper过多。这意味着每个mapper执行很快,但要花时间进行拆分,所以如果输入文件大,数据块的大小值也要相应加大(如256MB)。默认的Hadoop数据块大小是64MB(小数据集的最佳选择)、128MB(中等数据集)和256MB(大数据集)。大的数据块会加速磁盘I/O,但会增加跨网数据传输因而在Map阶段造成记录溢写。
map任务有两种从存储系统读取数据的方式:直接从磁盘读取(直接I/O)和流式和流式数据读取(通过进程间通信手段,如TCP/IP或者JDBC,从存储系统进行流式I/O),流式读取更加通用,可以用于从本地节点读取数据和从远程节点读取数据。
Hadoop通过向多个节点分发数据的方式平衡集群的负载,并把任务分配给数据被定为到的计算节点。这就是数据本地性非常重要,并且可能影响集群性能的原因。如果数据定位到的节点并非mapper处理数据的节点,数据就会跨网传输,因而加重网络负担。如果map函数从本地节点读取数据(数据本地map),直接I/O更高效。如果map函数不是数据本地map,流式I/O是唯一的选择。
处置小文件和不可拆分文件
业务上可能要求你处理HDFS不可拆分的文件,包括大文件或者小文件,二进制文件或者压缩文件。二进制文件和压缩文件本质上就不是基于块的。因此由于数据本地性的缺失,这可能会影响MapReduce作业性能。
处理小文件并非Hadoop的设计目标,HDFS的设计目标是存储和处理大数据集(TB级别)。因而,在HDFS中存储大量小文件是很低效的。大量小文件带来的问题是必须有很多并行任务来处理这些小文件,而大量并行任务不必要的消耗了更多资源,这就影响了作业的运行时间。而且,在系统中存在的大量小文件的情况下,其元数据也占据系统的一大部分,而这受到NameNode物理内存容量的限制。
当一个文件的大小小于HDFS的块大小(默认64MB,2.x+新版本128MB),就被认定为小文件,否则就是大文件。为了检测输入文件的大小,可以浏览Hadoop 的web页面,一般是 http://ip+50070 ,具体如下:
在Hadoop下处理HDFS小文件的问题,最简单的办法就是把它们打包成大文件。这种情况下,在NameNode内存中存储的文件就会减少,增加了数据交换,所有文件都存储在本地磁盘的大文件中。要用Hadoop打包小文件,可以选择下面任一种方法:
- 使用Avro对数据序列化来创建容器文件。
- 使用Hadoop Archives(HAR文件)。HAR文件是一种特殊格式的归档文件,它在HDFS之上构建一个分层的文件系统,使原始文件能够被Hadoop以透明的方式并行高效地访问,而无需扩展文件。呀创建HAR文件,可以使用hadoop archive命令,并且可以使用har://URL访问这些文件。
- 使用序列文件(SequenceFile)把小文件存储成单个大文件。SequenceFile用于键值对相同的方式结构化:文件名为键,而其内容为值。使用序列文件的另一个好处是这种文件可拆分且允许块压缩。(Deflate、gzip、lzo和Snappy等Hadoop压缩编解码方式是不可拆分的)
在Map阶段压缩溢写记录
在Map阶段,map函数可能向本地文件系统写入大量数据。map任务运行期间,会产生中间数据输出并保存在默认大小为100MB(io.sort.mb)的内存缓冲区中。此缓冲区是预留内存的一块,而预留内存是MapJVM对空间的一部分。一旦达到占用阈值(io.sort.spill.percent),缓冲区的内容就会刷写到本地磁盘,这就是所谓的溢写(spill)。为了存储溢写记录的元数据(每条记录的元数据长度为16字节),Hadoop框架使用io.sort.mb分配内存的5%(由io.sort.record.percent参数指定),也就是说5MB,分配给元数据,95MB分配给缓冲区使用。详情见下图
下面列举一下这些参数的调优列表:
当向磁盘多次进行记录溢写时,可能会产生性能问题和读取过载。检测map任务是否存在多余的溢写,试分析MapReduce数据流步骤的方式。要判断是否存在多余的溢写,你应该比较Map output records(map输出记录)和Spilled Records(溢写记录)这两个Hadoop计数器。如果Spilled Records的值大于Map output records的值,可以确定发生了多余的溢写。下图表示一个存在溢写记录的MapReduce作业的Hadoop计数器。
要增强框架在这一阶段的性能并消除多余的磁盘溢写,需要精确分配内存缓冲区,并把 io.sort.spill.percent 设置为0.99,接近整个缓冲区的容量。
要确定缓冲区所需的内存空间,需要计算缓冲区的总大小(记录+元数据)。要计算 io.sort.spill.percent 和 io.sort.mb,需要计算上面截图中标注的计数器。
计算Map任务的吞吐量
在Map阶段,map任务可能因众多小文件变慢,这意味着Hadoop在启动和停止任务上花费很多时间。对于大的不可拆分的文件,Hadoop在从其他节点读取数据上花费I/O时间。而且,不好的磁盘读写操作会影响Hadoop MapReduce性能。要判断map任务是否运行缓慢且吞吐量下降,需要使用基于单个map任务写入(或读出)文件大小的Hadoop I/O计数器,以及处理该作业的到期时间计算得出。如果计算得出的吞吐量与本地I/O吞吐量相当,可认为是最优的吞吐量,否则,说明其他因素正在影响map任务的性能。
假设有一个使用了N个map任务的MapReduce作业,可通过一下公式计算吞吐量(字节/秒):
吞吐量(N个map任务) = sum(每个map输入字节数)/ sum(每个map执行秒数)
下图为计算map任务吞吐量所需的Hadoop计数器
得到map函数的执行时间如下图:
参考资料:Hadoop MapReduce性能优化