优化Map Reduce Jobs,技术实现投资回报

来源:互联网 发布:linux服务器巡检报告 编辑:程序博客网 时间:2024/05/16 11:29
摘要:一说到大数据,就不得不提Hadoop;一提到Hadoop,就必要关注Map/Reduce。作为一个使用简易的软件框架,基于Map/Reduce的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理TB级别以上的数据集。本文教你如何优化一个大型Map/Reduce Jobs,并将其立即转化为投资回报。

一说到大数据,就不得不提Hadoop;一提到Hadoop,就必要关注Map/Reduce。众所周知,作为一个使用简易的软件框架,基于Map/Reduce的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理TB级别以上的数据集。讲述Map/Reduce的框架构成和逻辑工作原理的文章已经很多,这些博文都不错(可以在雅虎开源社区搜索下),但是这些文章尽管解释了技术层面,但却对性能影响少有涉及。

有些朋友也许愿意增加更多硬件来运行Map/Reduce以提升效率,但是这不是我喜欢的方式。事实上,我认为完全可以通过一个通用解决方案以及很多Map/Reduce程序来优化实现这一目标。只要你知道想要的是什么,优化一个大型Map/Reduce Jobs可以立即转化为投资回报。

字计数例子

现在有很多博客和教程来表现Map/Reduce,我很欣赏这些,尤其是提到Map/Reduce时有很多好的提示。比如提示和利用各种情况下的Map/Reduce的选择。这很重要,但是我认为是时候要去尝试解决Map/Reduce本身存在的问题了。

为了使事情更简单,我选择了Amazons Elastic Map Reduce,先是建立了一个新工作流程,确定了每个执行中的多个步骤。其包含一个主节点和两个任务节点,且都采用了小标准实例(Small Standard instance)。

尽管AWS Elastic Map Reduce也有缺点:比如启动和文件延迟(Amazon S3波动性很高),但是在执行Map/Reduce工作时(不需要启动你自己的Hadoop集群时)很简洁且易于应用。 你只需要为你所需要的资源而付费。我开始了计数测试,你会看到这其中的每一个Map Reduce文件、教程或博客。而这项工作的得到的文件总是类似这样:

the: 5967

all: 611

a: 21586

这项测试是计算在大量文本文件中每一个字的出现次数。我处理了共计200MB大小的30个文件。而利用original python版本做了一个小修改,在没有进行任何Hadoop修改的情况下,我将execoriginal python的执行时间缩短了一半。

http://articles.csdn.net/uploads/allimg/120920/139_120920164811_1.jpg

The Original Code

 

http://articles.csdn.net/uploads/allimg/120920/139_120920164754_1.jpg

The Optimized Code

用value 1代替每一个“emitting” ,在emitting之前先做了一个reduce。结果是,最终结果相同,但是仅用了之前的一半的时间。要明白这一点需要看Map Reduce的执行流程图。

执行路径和分布

看看下面来自““Hadoop Tutorial from Yahoo!”的流程图!

http://articles.csdn.net/uploads/allimg/120920/139_120920163138_1.png

Map Reduce Flow

Elastic Map Reduce第一个调度是每个Map Task上的任务文件(或文件的部分内容)。然后,它将文件的每一行都反馈给地映射函数。映射函数记录该行的每个字,每个键/值。每个发射键/值在被写入到中间文件后会减少。随机过程将确保每个键,在这种情况下的每一个字,都被发送到相同的reduce任务(Hadoop节点)来聚集。如果我们多次发射相同的字,它也需要被写入和发送多次,这导致更多的I / O(磁盘和网络)。合乎逻辑的结论是,我们应该“pre-reduce”,这每个任务节点为基础,并发送少量的数据。这就是Combiner的工作,在Mapping后的本地运行的同一节点上做Reducer。理论挺好,对不对?不尽然。

在Amazons Elastic Map Reduce之中

在将dynaTrace部署到Amazons Elastic Map Reduce以内时,我想到一个好主意。其利用一个简单的引导行动可以实现完全自动化

original python持续运行了5分钟(在290秒和320秒之间),而优化后的仅跑了3分钟(160 - 170秒)。我将dynaTrace分割成不同部分来观测不同的运行时间。有些数值反映出很高的波动性,正如我所发现的,这是Amazon S3以及一些较小程度的垃圾收集造成的。我执行好几次,发现这些波动性并没有对整体工作时间造成太大影响。

http://articles.csdn.net/uploads/allimg/120920/139_120920163308_1.png

在Map Reduce Job Business Transaction所花的时间(点击看大图)

点击图片,查看所有分析细节,你会看到出现显著改善的Mapping,组合和排序的时间。

在这个例子中所有的Mapping时间就是全部执行scheduled map tasks的时间。而优化代码的执行时间不到60%。这很大程度上是因为映射函数的功能,相当惊人。

接下来可以看到组合时间急剧下降,甚至说接近消失。这是有道理的,毕竟我们确保发出了更少的重复,从而减少了组合,加强了排序。这很有意义。虽然大多数组合和排序发生在一个单独的线程中,但是其仍然节省了大量CPU和I/O时间!另一方面,shuffle和reduce都没有发生变化。这方面没有看到明显的改进意义。由此在每个map task产生的中间文件看起来没有什么不不同,无论是使用组合还是优化的代码。

所以,真正优化的还是map计算,只有这样才能全面改善工作运行时间。虽然通过将map节点翻一番也能达到这一目标,但是成本显然更为高昂。

在mapping期间会发生什么?

要理解为什么这么简单的变化却产生了如此大的影响,先要看一下在Map Job的发射键上发生了什么。

http://articles.csdn.net/uploads/allimg/120920/139_120920163648_1.jpg

 

数据从mapper到memory buffer,排序与组合后最终合并

大多数Map/Reduce教程忘记提及的是,被称为Mapper的收集方法直接将键/值序列化到内存缓冲区,这在上面和下面的热点图中都可以看到。

当mapper发出collect,其会被写入到一个内存缓冲区中

一旦该缓冲区已经达到了一定的饱和度, Spill Thread将数据踢出并写入中间文件(由io.sort.spill. options控制)。Map/Reduce通常要处理大量潜在可能不重复的数据,因此最终都会有溢出。

http://articles.csdn.net/uploads/allimg/120920/139_120920163801_1.png

Spill Thread进行排序,结合并将数据写入到文件

通过将文件中的数据做简单排序是不够的,内容还需要被先被排序和整合。排序是对shuffle过程的准备,且相对有效(基于二进制字节排序,因为实际的顺序并不重要)。整合则需要在writing之前对键/值进行反序列化处理。

http://articles.csdn.net/uploads/allimg/120920/139_120920163906_1.png

在spill thread整合需要再次进行反序列化处理

因此,多次发出一个键会造成以下影响:

1.由于越来越多的序列化会为map time和CPU usage带来直接的负面影响;

2.更多的溢出和其他整合步骤中的反序列化将渐进影响CPU;

3.更多的中间文件会直接影响map task,并使得最终合并变得更加昂贵。

越慢的mapping自然会直接影响整体工作实践。我们需要发出的数据越多,通过Spill Thread的CPU和I/0的输出消费就越多。如果Spill Thread过慢,内存缓存区会完全饱和,这样情况下,map task只能等待。(这是可以通过io.sort.spill.percent hadoop option来调整的)。

http://articles.csdn.net/uploads/allimg/120920/139_120920164020_1.png

由于太多数据需要排序和整合,Spill Thread造成了Mapper的暂停

在Map Task完成实际mapping之后,其会对剩余文件进行排序和整合。最后,所有的中间文件被合并为一个单一的输出文件(也可能再次被合并)。更多的发射键意味着更多中间文件被合并。

http://articles.csdn.net/uploads/allimg/120920/139_120920164050_1.png

完整的Map Task显示了mapping分类、整合、合并的整个过程

虽然最后结果仅仅显示我们只“减缓”了1.5秒,但这相当于8%的Mapper task。我们看到在整合或reduce之前,其中确实有很多的优化输出Map的操作。这将节省CPU、硬盘和I/0资源,当然,也就意味着可以用更少的节点来满足同样的工作需求。

百万次执行带来的损耗

直到现在,我仍然试图解释发射多次键会对CPU和I/O带来的影响。但是当写入Map/Reduce jobs时也有另外一个因素需要考虑。映射函数可能会被数百万次执行。每一次的执行都会导致分钟级的时间消耗。事实上,我通过加快mapping本身而不是采用更多的有效组合和磁盘写入来实现了大部分的“优化”。

在看Map/Reduce时,我最先关注的的是大多数样本和教程使用的脚本语言,如python, perl或其他。这会对Hadoop流媒体框架有所判断。虽然我明白,这会降低写的Map / Reduce作业的障碍,但是其不应该用于严重的任务!为了说明这一点,我跑了一个随机选择的Java版本的字数统计样本。结果是另一个采用了optimized python的有了50-60%的提升。(如果是更大的任务,相信结果会更好)

http://articles.csdn.net/uploads/allimg/120920/139_120920164244_1.png

过Java和Python完成的Word Count Map Reduce Jobs,显示了巨大的时间执行差异。

这张表说明了以下几点:注意说明。

http://articles.csdn.net/uploads/allimg/120920/139_120920164309_1.jpg

事实上,Java一直要比Python快。尤其是优化的Java版本要比同样优化的Python版快两倍。记住,这是一个小例子,在Hadoop参数完全一致的情况下。此外,CPU从来不是限制因素。如果要执行相同的小代数百万次,即使是小差异问题也能有所体现。但如果是在java和python上一行小映射,那么显然不可衡量。但若是200MB的文本,其差异则会到一分钟。同样细小的的改变也会影响到Java Map/Reduce job。比如,采用原始Java版本要比优化Java版本之间落后60%以上的性能表现。

对一些map/reduce jobs而言,字计数是非常简单的比较,但是其很好地说明了我们自己的代码的性能有多么重要。关键是,我们仍然需要将分析和优化map task和我们的代码。只有做到这一点,我们才能发挥各类Hadoop选项的优势要实现更好的分配和利用。 

结论:

在诸多主机上大数据的分发处理方面,Map Reduce是一种非常强大并优雅的方式。但在进行Hadoop选项前,Map和Reduce通过分析和性能优化来完成相关任务时也不乏几分粗鲁。Map/Reduce可以减少工作时间以及解决硬件投入过多的问题,易于优化的方式往往可以达到类似的效果。当然,在云和AWS Elastic Map Reduce中,这些意味着更低的成本。(编译/郭雪梅)