最近在看一些大数据的东西,发现对其中的shuffle过程很模糊,于是决定学习一下,深入之后又发现对整个mapreduce的数据完成处理过程也同样模糊。
所以本文将从以下几个角度来展开:
- mapreduce以及hadoop框架的一些认识
- mapreduce的核心思想是什么
- mapreduce数据处理过程推演
- mapreduce的shuffle是如何实现的
Hadoop三剑客
Hadoop是一个由Apache开发的大数据处理框架,它包括了HDFS(Hadoop分布式文件系统)、YARN(Yet Another Resource Negotiator,资源管理器)以及MapReduce计算框架。
HDFS是Hadoop的存储组件,YARN则是用于资源管理和调度的组件,而MapReduce是Hadoop用于分布式计算的框架。
在Hadoop中,数据通常存储在HDFS中,通过MapReduce框架进行分布式计算,YARN负责管理计算资源,并协调MapReduce等计算框架的运行。
MapReduce、Hadoop、HDFS和YARN之间是相互依存、协同工作的关系,它们共同构成了一个完整的大数据处理系统。
MapReduce核心思想
分而治之
MapReduce的主要思想是将大规模数据处理任务分解成多个小任务,并在分布式计算集群上并行执行,从而实现高效的数据处理和分析。
MapReduce数据处理任务分为两个主要阶段:Map阶段和Reduce阶段。
在Map阶段中,MapReduce将输入数据分割成若干个小块,然后在分布式计算集群上同时执行多个Map任务,每个任务都对一个小块的数据进行处理,并将处理结果输出为一系列键值对,Map任务的输出结果会被临时存储在本地磁盘或内存中,以供Reduce任务使用。
在Reduce阶段中,它主要负责对Map任务的输出结果进行整合和汇总,生成最终的输出结果。Reduce任务会将所有Map任务输出的键值对按照键进行排序,并将相同键的值合并在一起,Reduce任务的输出结果通常会写入到文件系统中
计算向数据靠拢
在传统的分布式计算中,数据通常需要从存储介质中读取到计算节点进行处理,这会造成大量的数据传输和网络延迟,导致计算效率较低。
"计算向数据靠拢"是MapReduce的一个设计思想,旨在最大化利用数据本地性(data locality)来提高作业的性能。
在MapReduce中,数据通常分布在集群的不同节点上,而处理数据的任务(Map任务和Reduce任务)尽量在数据所在的节点上执行,减少数据的网络传输和节点间的通信开销,提高作业的性能。
MapReduce过程推演
我们尝试从头来推演整个MapReduce任务的大致执行过程:
- 怎么写跨语言的MapReduce作业?
- JobClient做了些什么?
- JobClient和Yarn是如何交互的?
- Hadoop2.x中AppMaster、NodeManager是如何协作的?
- 输入文件分割是怎么实现的?
Hadoop Streaming
如果使用非 Java 编程语言来实现 MapReduce 任务,或者希望更灵活地定制 Map 和 Reduce 函数的实现方式,可以考虑使用 Hadoop Streaming。
Hadoop Streaming 是 Hadoop 提供的一个工具,可以让用户通过标准输入和标准输出来实现自定义 Map 和 Reduce 函数的功能。使用 Hadoop Streaming 可以使用任何语言来实现 Map 和 Reduce 函数,而不仅仅局限于 Java。
当使用 Hadoop Streaming 时,客户端会将 Map 和 Reduce 函数打包成可执行文件,然后提交给 Hadoop 集群来执行。这些可执行文件可以用任何编程语言编写,例如 Python、Perl、Ruby、C++ 等。
JobClient
在提交任务之前,客户端需要将任务的输入数据和输出路径等信息设置好,以便Hadoop集群能够正确地执行任务并将结果输出到指定的路径。
MapReduce的客户端JobClient通常会将任务打包成JAR包,然后将该JAR包提交给Hadoop集群来执行任务。
JAR包中包含了MapReduce任务所需的输入数据、输出路径、 Map 和 Reduce 的数量,以及每个任务需要的内存和 CPU 资源等参数,这样可以保证任务在集群中的任何节点上都能够正常运行。
hadoop 1.x和2.x
在Hadoop 1.x版本中,JobTracker和TaskTracker是Hadoop集群中的两个重要组件,其中JobTracker负责整个集群中所有MapReduce任务的协调和管理,而TaskTracker负责具体的任务执行。
在Hadoop 2.x版本中引入了YARN框架,将JobTracker的功能拆分成两部分,一部分是ResourceManager,负责集群资源的管理和分配,另一部分是ApplicationMaster,负责具体任务的管理和协调。
在Hadoop 2.x版本及以后的版本中,ApplicationMaster扮演的角色类似于JobTracker。
AppMaster
在 Hadoop YARN 中,ApplicationMaster是一个关键的组件,它负责在集群中管理和监控一个特定的应用程序。
在 MapReduce 中,每个 MapReduce 作业都有一个对应的 ApplicationMaster 实例,该实例负责协调整个作业的执行过程,包括分配任务、监控任务的进度和状态、处理任务失败等。
当客户端提交 MapReduce 作业时,YARN 资源管理器会为该作业启动一个 ApplicationMaster 实例。
ApplicationMaster 将向资源管理器请求分配资源,并与各个 NodeManager 协商任务的执行。它还负责将 MapReduce 作业的逻辑划分为多个 Map 和 Reduce 任务,并将任务分配给相应的 NodeManager 执行。
在任务执行期间,ApplicationMaster 将持续监控任务的进度和状态,并在任务出现错误或失败时进行相应的处理。
NodeManager
AppMaster和NodeManager不同,它们是YARN框架中的两个不同的组件,分别扮演着不同的角色。
AppMaster是一个应用程序级别的组件,它运行在分布式集群中的一个节点上,负责协调和管理应用程序的生命周期。它向ResourceManager请求资源,然后将这些资源分配给它的任务(如Map和Reduce任务)。在任务运行期间,AppMaster监视任务的进度并与ResourceManager通信,以确保应用程序在分布式集群上有效地运行。
NodeManager是一个节点级别的组件,它运行在每个节点上,负责管理该节点上的容器和资源。在应用程序启动时,AppMaster向ResourceManager请求节点资源,并指示NodeManager在该节点上启动容器来执行应用程序的任务。
NodeManager负责启动容器并为它们分配资源,同时监视它们的进度,并向ResourceManager报告资源使用情况。
HDFS输入文件逻辑分割
客户端程序中的Job对象会设置输入文件的路径和InputFormat类,在提交作业之前,Job会调用InputFormat的getSplits()方法来获取输入文件的切片信息。
InputFormat 是针对输入文件进行逻辑分割,将一个或多个输入文件划分为一组输入切片InputSplit,以便于 MapReduce 作业的并行处理。
对于大多数常见的数据类型,Hadoop 提供了一些内置的 InputFormat 实现,如下:
- TextInputFormat:按行读取文本文件,并将每行作为一个输入记录。
- KeyValueTextInputFormat:按行读取键值对形式的文本文件,并将每个键值对作为一个输入记录。
- SequenceFileInputFormat:读取 Hadoop 序列文件(SequenceFile),并将其中的每个 key-value 对作为一个输入记录。
- CombineFileInputFormat:支持读取多个小文件或者多个小数据块,并将它们合并成一个或多个输入切片,以便于提高作业的并行度和执行效率。
输入文件的切片由InputFormat的getSplits()方法生成,这个方法会计算输入文件的切片,并返回一个切片数组。每个切片都包含了一个起始偏移量和一个长度,这些信息告诉了Map任务它需要处理的输入数据的范围,ResourceManager会根据Split对象数组来计算作业需要的资源,并分配相应的资源来运行作业。
当Map任务启动时,它会使用InputFormat提供的InputSplit信息来创建一个RecordReader实例,用于读取该Map任务需要处理的输入数据。
RecordReader从HDFS或其他存储系统中读取数据,将数据划分成适当大小的记录,然后将它们转换为键值对(key-value pairs),再将它们传递给Mapper进行处理。
shuffle简介
先看一个完整的图,接下来会进行展开:
什么是shuffle
在 MapReduce 中,Map 和 Reduce 任务都是并行执行的,Map 任务负责对输入数据进行处理,将其转换为键值对的形式,而 Reduce 任务负责对 Map 任务的输出结果进行聚合和计算。
在 MapReduce 中,Shuffle 过程的主要作用是将 Map 任务的输出结果传递给 Reduce 任务,并为 Reduce 任务提供输入数据,它是 MapReduce 中非常重要的一个步骤,可以提高 MapReduce 作业效率。
Shuffle 过程的作用包括以下几点:
- 合并相同 Key 的 Value:Map 任务输出的键值对可能会包含相同的 Key,Shuffle 过程会将相同 Key 的 Value 合并在一起,减少 Reduce 任务需要处理的数据量。
- 按照 Key 进行排序:Shuffle 过程会将 Map 任务的输出结果按照 Key 进行排序,这样 Reduce 任务可以顺序地处理键值对序列,避免在处理数据时需要进行额外的排序操作。
- 划分数据并传输:Shuffle 过程会将 Map 任务的输出结果按照 Key 划分成多个分区,并将每个分区的数据传输到对应的 Reduce 任务中。这样,Reduce 任务可以从不同的 Map 任务中获取到数据,从而实现更好的并行化处理。
Map端的Shuffle
- Map任务在执行过程中会将输出的键值对写入本地磁盘上的环形缓冲区中,按照Partitioner函数的规则将键值对映射到不同的区域Partition中。
- 当环形缓冲区满或者Map任务执行完毕时,Map任务将环形缓冲区中的数据进行排序,并按照Partition和排序结果写入到本地磁盘上的Spill文件中。
- 当所有Map任务都执行完毕后,MapReduce会按照Partition对所有Spill文件进行归并排序,形成一个有序的大文件。
环形缓冲区的作用
环形缓冲区是一种特殊的缓冲区,它将数据存储在一个固定大小的、循环的缓冲区中,当缓冲区满时,新的数据将覆盖最老的数据,于链表和数组等数据结构,环形缓冲区具有以下优势:
- 高效的读写性能:由于环形缓冲区具有固定的大小,可以预先分配一定的空间,因此读写操作非常高效。而且由于缓冲区是循环的,可以通过简单的指针操作来实现数据的读写操作,而不需要复杂的数据移动操作。
- 空间利用率高:由于环形缓冲区可以循环利用空间,因此空间利用率非常高。相比于链表和数组等数据结构,它不需要额外的空间来维护节点或索引等信息,可以更好地利用内存空间。
- 易于实现和管理:由于环形缓冲区的结构非常简单,因此易于实现和管理。可以通过一些简单的技术来解决缓冲区溢出等问题,从而保证 Shuffle 过程的正确性。
分区&排序&溢写
在 Map 阶段中,Map 任务会将输出数据写入环形缓冲区,而这些数据会被分为多个分区,并且每个分区内的数据是按照键(Key)进行排序的。
分区的划分是由 Partitioner 类完成的,默认情况下,Partitioner 类会根据键值对的键(Key)来计算分区编号(Partition ID),从而将数据分配到对应的分区中。
不同的键(Key)可能会被分到同一个分区中,而相同的键(Key)则一定会被分到同一个分区中,这是为了保证相同键(Key)的数据能够被发送到同一个 Reduce 任务中进行处理。
Map 任务向环形缓冲区中写入数据时,先将数据插入到分区内,然后对该分区内的所有数据进行快速排序。
当环形缓冲区中的数据量达到一定阈值(MapReduce 1.x 中默认为环形缓冲区大小的 0.8 倍),或者某个分区内存放的数据大小达到一定阈值(MapReduce 1.x 中默认为 100MB),就会触发溢写操作,将数据按照分区写到磁盘上的临时文件中。
Reduce端的Shuffle
- map的shuffle阶段会生成包含不同分区的大文件
- reduce任务可能会多个不同的分区,但是同一个分区的数据一定在同一个reduce中
- reduce任务收集来自多个map输出的同一个分区的数据,在内部再针对同一分区的多个文件做归并成一个大文件
小结
很多时候明确问题比知道答案更重要,多想为什么、多在脑海里去推演过程、最终才能自洽吸收外界知识,化为自己的经验。