zoukankan      html  css  js  c++  java
  • MapReduce:具体解释Shuffle过程

            Shuffle过程是MapReduce的核心,也被称为奇迹发生的地方。要想理解MapReduce, Shuffle是必需要了解的。我看过非常多相关的资料,但每次看完都云里雾里的绕着,非常难理清大致的逻辑,反而越搅越混。前段时间在做MapReduce job 性能调优的工作,需要深入代码研究MapReduce的执行机制,这才对Shuffle探了个到底。

    考虑到之前我在看相关资料而看不懂时非常恼火。所以在这里我尽最大的可能试着把Shuffle说清楚,让每一位想了解它原理的朋友都能有所收获。假设你对这篇文章有不论什么疑问或建议请留言到后面,谢谢!

     

            Shuffle的正常意思是洗牌或弄乱,可能大家更熟悉的是Java API里的Collections.shuffle(List)方法,它会随机地打乱參数list里的元素顺序。

    假设你不知道MapReduce里Shuffle是什么,那么请看这张图: 


           这张是官方对Shuffle过程的描写叙述。但我能够肯定的是,单从这张图你基本不可能明确Shuffle的过程,由于它与事实相差挺多。细节也是错乱的。

    后面我会详细描写叙述Shuffle的事实情况。所以这里你仅仅要清楚Shuffle的大致范围就成-如何把map task的输出结果有效地传送到reduce端。也能够这样理解, Shuffle描写叙述着数据从map task输出到reduce task输入的这段过程。 

            在Hadoop这种集群环境中,大部分map task与reduce task的运行是在不同的节点上。当然非常多情况下Reduce运行时须要跨节点去拉取其他节点上的map task结果。假设集群正在运行的job有非常多,那么task的正常运行对集群内部的网络资源消耗会非常严重。这种网络消耗是正常的。我们不能限制。能做的就是最大化地降低不必要的消耗。还有在节点内。相比于内存,磁盘IO对job完毕时间的影响也是可观的。从最主要的要求来说。我们对Shuffle过程的期望能够有: 

    • 完整地从map task端拉取数据到reduce 端。
    • 在跨节点拉取数据时。尽可能地降低对带宽的不必要消耗。

    • 降低磁盘IO对task运行的影响。

            OK。看到这里时。大家能够先停下来想想,假设是自己来设计这段Shuffle过程,那么你的设计目标是什么。我想能优化的地方主要在于降低拉取数据的量及尽量使用内存而不是磁盘。 

            我的分析是基于Hadoop0.21.0的源代码。如果与你所认识的Shuffle过程有区别,不吝指出。我会以WordCount为例,并如果它有8个map task和3个reduce task。从上图看出,Shuffle过程横跨map与reduce两端。所以以下我也会分两部分来展开。 

            先看看map端的情况,例如以下图: 


            上图可能是某个map task的执行情况。拿它与官方图的左半边比較,会发现非常多不一致。官方图没有清晰地说明partition。 sort与combiner究竟作用在哪个阶段。

    我画了这张图,希望让大家清晰地了解从map数据输入到map端全部数据准备好的全过程。 

            整个流程我分了四步。简单些能够这样说,每一个map task都有一个内存缓冲区。存储着map的输出结果,当缓冲区快满的时候须要将缓冲区的数据以一个暂时文件的方式存放到磁盘。当整个map task结束后再对磁盘中这个map task产生的全部暂时文件做合并。生成终于的正式输出文件,然后等待reduce task来拉数据。

     

            当然这里的每一步都可能包括着多个步骤与细节,以下我对细节来一一说明: 
    1.        在map task运行时,它的输入数据来源于HDFS的block。当然在MapReduce概念中。map task仅仅读取split。Split与block的相应关系可能是多对一,默认是一对一。在WordCount样例里,如果map的输入数据都是像“aaa”这种字符串。

     

    2.        在经过mapper的执行后,我们得知mapper的输出是这样一个key/value对: key是“aaa”, value是数值1。

    由于当前map端仅仅做加1的操作,在reduce task里才去合并结果集。前面我们知道这个job有3个reduce task,究竟当前的“aaa”应该交由哪个reduce去做呢。是须要如今决定的。 

            MapReduce提供Partitioner接口,它的作用就是依据key或value及reduce的数量来决定当前的这对输出数据终于应该交由哪个reduce task处理。

    默认对key hash后再以reduce task数量取模。默认的取模方式仅仅是为了平均reduce的处理能力,假设用户自己对Partitioner有需求。能够订制并设置到job上。

     

            在我们的样例中,“aaa”经过Partitioner后返回0。也就是这对值应当交由第一个reducer来处理。接下来,须要将数据写入内存缓冲区中,缓冲区的作用是批量收集map结果,降低磁盘IO的影响。我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。

     

            整个内存缓冲区就是一个字节数组。它的字节索引及key/value存储结构我没有研究过。假设有朋友对它有研究,那么请大致描写叙述下它的细节吧。

     

    3.        这个内存缓冲区是有限制大小的,默认是100MB。当map task的输出结果非常多时,就可能会撑爆内存,所以须要在一定条件下将缓冲区中的数据暂时写入磁盘,然后又一次利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill。中文可译为溢写,字面意思非常直观。

    这个溢写是由单独线程来完毕,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止map的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB)。溢写线程启动,锁定这80MB的内存,运行溢写过程。Map task的输出结果还能够往剩下的20MB内存中写。互不影响。 

            当溢写线程启动后,须要对这80MB空间内的key做排序(Sort)。

    排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。

     

            在这里我们能够想想。由于map task的输出是须要发送到不同的reduce端去,而内存缓冲区没有对将发送到同样reduce端的数据做合并,那么这样的合并应该是体现是磁盘文件里的。从官方图上也能够看到写到磁盘中的溢写文件是对不同的reduce端的数值做过合并。所以溢写过程一个非常重要的细节在于。假设有非常多个key/value对须要发送到某个reduce端去,那么须要将这些key/value值拼接到一块。降低与partition相关的索引记录。 

            在针对每一个reduce端而合并数据时,有些数据可能像这样:“aaa”/1, “aaa”/1。对于WordCount样例,就是简单地统计单词出现的次数,假设在同一个map task的结果中有非常多个像“aaa”一样出现多次的key,我们就应该把它们的值合并到一块,这个过程叫reduce也叫combine。但MapReduce的术语中。reduce仅仅指reduce端运行从多个map task取数据做计算的过程。除reduce外,非正式地合并数据仅仅能算做combine了。事实上大家知道的,MapReduce中将Combiner等同于Reducer。 

            假设client设置过Combiner,那么如今就是使用Combiner的时候了。将有同样key的key/value对的value加起来,降低溢写到磁盘的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。

    那哪些场景才干使用Combiner呢?从这里分析。Combiner的输出是Reducer的输入,Combiner绝不能改变终于的计算结果。所以从我的想法来看,Combiner仅仅应该用于那种Reduce的输入key/value与输出key/value类型全然一致,且不影响终于结果的场景。比方累加,最大值等。Combiner的使用一定得谨慎,假设用好。它对job运行效率有帮助,反之会影响reduce的终于结果。 

    4.        每次溢写会在磁盘上生成一个溢写文件,假设map的输出结果真的非常大,有多次这种溢写发生,磁盘上对应的就会有多个溢写文件存在。当map task真正完毕时。内存缓冲区中的数据也所有溢写到磁盘中形成一个溢写文件。

    终于磁盘中会至少有一个这种溢写文件存在(假设map的输出结果非常少。当map运行完毕时,仅仅会产生一个溢写文件)。由于终于的文件仅仅有一个,所以须要将这些溢写文件归并到一起,这个过程就叫做Merge。Merge是如何的?如前面的样例。“aaa”从某个map task读取过来时值是5,从另外一个map 读取时值是8,由于它们有同样的key,所以得merge成group。

    什么是group。对于“aaa”就是像这种:{“aaa”, [5, 8, 2, …]},数组中的值就是从不同溢写文件里读取出来的。然后再把这些值加起来。请注意。由于merge是将多个溢写文件合并到一个文件,所以可能也有同样的key存在,在这个过程中假设client设置过Combiner。也会使用Combiner来合并同样的key。 

            至此,map端的全部工作都已结束,终于生成的这个文件也存放在TaskTracker够得着的某个本地文件夹内。每一个reduce task不断地通过RPC从JobTracker那里获取map task是否完毕的信息,假设reduce task得到通知。获知某台TaskTracker上的map task运行完毕,Shuffle的后半段过程開始启动。 

            简单地说。reduce task在运行之前的工作就是不断地拉取当前job里每一个map task的终于结果,然后对从不同地方拉取过来的数据不断地做merge,也终于形成一个文件作为reduce task的输入文件。见下图:


            如map 端的细节图,Shuffle在reduce端的过程也能用图上标明的三点来概括。

    当前reduce copy数据的前提是它要从JobTracker获得有哪些map task已执行结束,这段过程不表,有兴趣的朋友能够关注下。

    Reducer真正执行之前,全部的时间都是在拉取数据。做merge。且不断反复地在做。如前面的方式一样。以下我也分段地描写叙述reduce 端的Shuffle细节: 
    1.        Copy过程,简单地拉取数据。

    Reduce进程启动一些数据copy线程(Fetcher)。通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。由于map task早已结束,这些文件就归TaskTracker管理在本地磁盘中。 

    2.        Merge阶段。这里的merge如map端的merge动作,仅仅是数组中存放的是不同map端copy来的数值。

    Copy过来的数据会先放入内存缓冲区中。这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置,由于Shuffle阶段Reducer不执行。所以应该把绝大部分的内存都给Shuffle用。这里须要强调的是。merge有三种形式:1)内存到内存  2)内存到磁盘  3)磁盘到磁盘。默认情况下第一种形式不启用,让人比較困惑,是吧。

    当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中假设你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。另外一种merge方式一直在执行,直到没有map端的数据时才结束。然后启动第三种磁盘到磁盘的merge方式生成终于的那个文件。 

    3.        Reducer的输入文件。

    不断地merge后。最后会生成一个“终于文件”。为什么加引號?由于这个文件可能存在于磁盘上,也可能存在于内存中。对我们来说,当然希望它存放于内存中。直接作为Reducer的输入。但默认情况下。这个文件是存放于磁盘中的。至于如何才干让这个文件出如今内存中。之后的性能优化篇我再说。当Reducer的输入文件已定,整个Shuffle才终于结束。

    然后就是Reducer运行。把结果放到HDFS上。 

            上面就是整个Shuffle的过程。

    细节非常多,我非常多都略过了。仅仅试着把要点说明确。

    当然,我可能也有理解或表述上的非常多问题,不吝指点。我希望不断地完好和改动这篇文章,能让它通俗、易懂。看完就能知道Shuffle的方方面面。至于详细的实现原理。各位有兴趣就自己去探索,假设不方便的话。留言给我,我再来研究并反馈。

  • 相关阅读:
    生成大小字母以及数字混乱的6位验证码
    重写吃货联盟
    集合练习:登录注册功能
    出现并解决Navicat 报错:1130-host ... is not allowed to connect to this MySql server,MySQL
    mysql在linux的安装
    Spring4分别整合mongo2.X和3.0
    oracle数据库使用plsql(64位)时出现的问题
    spring的CXF远程服务
    spring的定时任务或者说自动任务
    jquery节点查询
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/6912730.html
Copyright © 2011-2022 走看看