Spark一镜到底

414 阅读14分钟

1 大数据产生背景

为什么会有大数据这样的技术出现? 在互联网2.0诞生之前,比如门户网站时代,互联网处理的思路是垂直扩展,机器性能不行,那就换更好的CPU、加大内存、加大磁盘。面对后面互联网的潮流以及数据爆炸,垂直扩展的方式就玩不下去了。 这个时候处理的思路是,磁盘不够用,把数据分一分到不同的机器上; 算力不足,就把计算任务分到不同的机器上去执行。

image.png

大数据存储,大数据计算应运而生。

这套方案的核心思路是,既然数据是庞大的,而程序要比数据小得多,将数据输入给程序是不划算的,那么就反其道而行之,将程序分发到数据所在的地方进行计算,也就是所谓的移动计算比移动数据更划算。

大数据移动计算的总体思路

  1. 将待处理的大规模数据存储在集群的所有服务器上,主要使用HDFS,将文件分成很多块(Block),以块为单位存储在集群的服务器上。
  2. 大数据引擎根据集群里不同服务器的计算能力,在每台服务器上启动若干分布式任务执行进程,这些进程会等待给它们分配执行任务。
  3. 使用大数据计算框架支持的编程模型进行编程,比如 Hadoop 的 MapReduce 编程模型,或者 Spark 的 RDD 编程模型。应用程序编写好以后,将其打包,MapReduce 和Spark 都是在 JVM 环境中运行,所以打包出来的是一个 Java 的 JAR 包。
  4. 用 Hadoop 或者 Spark 的启动命令执行这个应用程序的 JAR 包,首先执行引擎会解析程序要处理的数据输入路径,根据输入数据量的大小,将数据分成若干片(Split),每一个数据片都分配给一个任务执行进程去处理。
  5. 任务执行进程收到分配的任务后,检查自己是否有任务对应的程序包,如果没有就去下载程序包,下载以后通过反射的方式加载程序。走到这里,最重要的一步,也就是移动计算就完成了。
  6. 加载程序后,任务执行进程根据分配的数据片的文件地址和数据在文件内的偏移量读取数据,并把数据输入给应用程序相应的方法去执行,从而实现在分布式服务器集群中移动计算程序,对大规模数据进行并行处理的计算目标。

2 HDFS

image.png

HDFS 遵循主/从架构,由单个 NameNode(NN) 和多个 DataNode(DN) 组成:

  • NameNode : 负责执行有关 文件系统命名空间 的操作,例如打开,关闭、重命名文件和目录等。它同时还负责集群元数据的存储,记录着文件中各个数据块的位置信息。
  • DataNode:负责处理来自文件系统客户端的读写请求,执行数据块的创建,删除等操作。

NameNode负责整个分布式文件系统的元数据(MetaData)管理,也就是文件路径名、数据块的 ID 以及存储位置等信息,相当于操作系统中文件分配表(FAT)的角色。

数据复制,冗余备份图示

image.png

HDFS的高可用设计

  1. 数据存储故障容错 数据本身容错,软件损坏 磁盘介质在存储过程中受环境或者老化影响,其存储的数据可能会出现错乱。HDFS 的应对措施是,对于存储在 DataNode 上的数据块,计算并存储校验和(CheckSum)。在读取数据的时候,重新计算读取出来的数据的校验和,如果校验不正确就抛出异常,应用程序捕获异常后就到其他 DataNode 上读取备份数据。

  2. 磁盘故障容错 物理损坏 如果 DataNode 监测到本机的某块磁盘损坏,就将该块磁盘上存储的所有 BlockID 报告给NameNode,NameNode 检查这些数据块还在哪些 DataNode 上有备份,通知相应的DataNode 服务器将对应的数据块复制到其他服务器上,以保证数据块的备份数满足要求。

  3. DataNode故障容错 DataNode 会通过心跳和 NameNode 保持通信,如果 DataNode 超时未发送心跳,NameNode 就会认为这个 DataNode 已经宕机失效,立即查找这个 DataNode 上存储的数据块有哪些,以及这些数据块还存储在哪些服务器上,随后通知这些服务器再复制一份数据块到其他服务器上,保证 HDFS 存储的数据块备份数符合用户设置的数目,即使再出现服务器宕机,也不会丢失数据。

  4. NameNode 故障容错 NameNode 是整个 HDFS 的核心,记录着 HDFS 文件分配表信息,所有的文件路径和数据块存储信息都保存在 NameNode,如果 NameNode 故障,整个 HDFS 系统集群都无法使用;如果 NameNode 上记录的数据丢失,整个集群所有 DataNode 存储的数据也就没用了。

image.png

集群部署两台 NameNode 服务器,一台作为主服务器提供服务,一台作为从服务器进行热备,两台服务器通过 ZooKeeper 选举,主要是通过争夺 znode 锁资源,决定谁是主服务器。而 DataNode 则会向两个 NameNode 同时发送心跳数据,但是只有主NameNode 才能向 DataNode 返回控制信息。

hdfs 写数据 image.png image.png hdfs 读数据 image.png HDFS的容错和高可用 image.png HDFS 读写故障的处理 image.png image.png image.png

3 MapReduce

MapReduce 既是一个编程模型,又是一个计算框架。可能是一个编程模型,也就是一个程序。也可以是一个计算框架,调度执行大数据分布式计算。

image.png

image.png

MapReduce作业启动和运行机制

我们以 Hadoop 1 为例,MapReduce 运行过程涉及三类关键进程。

  1. 大数据应用进程。这类进程是启动 MapReduce 程序的主入口,主要是指定 Map 和Reduce 类、输入输出文件路径等,并提交作业给 Hadoop 集群,也就是下面提到的JobTracker 进程。这是由用户启动的 MapReduce 程序进程,比如我们上期提到的WordCount 程序。
  2. JobTracker 进程。这类进程根据要处理的输入数据量,命令下面提到的 TaskTracker 进程启动相应数量的 Map 和 Reduce 进程任务,并管理整个作业生命周期的任务调度和监控。这是 Hadoop 集群的常驻进程,需要注意的是,JobTracker 进程在整个 Hadoop 集群全局唯一。
  3. TaskTracker 进程。这个进程负责启动和管理 Map 进程以及 Reduce 进程。因为需要每个数据块都有对应的 map 函数,TaskTracker 进程通常和 HDFS 的 DataNode 进程启动在同一个服务器。也就是说,Hadoop 集群中绝大多数服务器同时运行 DataNode 进程和TaskTracker 进程。

MapReduce 的主服务器就是 JobTracker,从服务器就是 TaskTracker。还记得我们讲 HDFS 也是主从架构吗,HDFS 的主服务器是 NameNode,从服务器是DataNode。后面会讲到的 Yarn、Spark 等也都是这样的架构,这种一主多从的服务器架构也是绝大多数大数据系统的架构方案。

  1. 应用进程 JobClient 将用户作业 JAR 包存储在 HDFS 中,将来这些 JAR 包会分发给Hadoop 集群中的服务器执行 MapReduce 计算。
  2. 应用程序提交 job 作业给 JobTracker。
  3. JobTracker 根据作业调度策略创建 JobInProcess 树,每个作业都会有一个自己的JobInProcess 树。
  4. JobInProcess 根据输入数据分片数目(通常情况就是数据块的数目)和设置的 Reduce数目创建相应数量的 TaskInProcess。
  5. TaskTracker 进程和 JobTracker 进程进行定时通信。
  6. 如果 TaskTracker 有空闲的计算资源(有空闲 CPU 核心),JobTracker 就会给它分配任务。分配任务的时候会根据 TaskTracker 的服务器名字匹配在同一台机器上的数据块计算任务给它,使启动的计算任务正好处理本机上的数据,以实现我们一开始就提到的“移动计算比移动数据更划算”。
  7. TaskTracker 收到任务后根据任务类型(是 Map 还是 Reduce)和任务参数(作业 JAR包路径、输入数据文件路径、要处理的数据在文件中的起始位置和偏移量、数据块多个备份的 DataNode 主机名等),启动相应的 Map 或者 Reduce 进程。
  8. Map 或者 Reduce 进程启动后,检查本地是否有要执行任务的 JAR 包文件,如果没有,就去 HDFS 上下载,然后加载 Map 或者 Reduce 代码开始执行。
  9. 如果是 Map 进程,从 HDFS 读取数据(通常要读取的数据块正好存储在本机);如果是 Reduce 进程,将结果数据写出到 HDFS。

什么是数据倾斜

MapReduce的计算过程当map完成之后,会将处理的数据根据key进行分区(依赖Partitioner实现)。

** Use {@link Object#hashCode()} to partition. */
public int getPartition(K2 key, V2 value, int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}

只需要记住这一点:分布式计算需要将不同服务器上的相关数据合并到一起进行下一步计算,这就是 shuffle。

image.png 数据倾斜就是shuffle的过程出现有些节点task计算的处理的数据是其他task的很多倍,而整个stage的运行速度也由运行最慢的那个task所决定。

数据倾斜处理的思路参照 tech.meituan.com/2016/05/12/…

资源调度器Yarn

image.png

我们以一个 MapReduce 程序为例,来看一下 Yarn 的整个工作流程。

  1. 我们向 Yarn 提交应用程序,包括 MapReduce ApplicationMaster、我们的MapReduce 程序,以及 MapReduce Application 启动命令。
  2. ResourceManager 进程和 NodeManager 进程通信,根据集群资源,为用户程序分配第一个容器,并将 MapReduce ApplicationMaster 分发到这个容器上面,并在容器里面启动 MapReduce ApplicationMaster。
  3. MapReduce ApplicationMaster 启动后立即向 ResourceManager 进程注册,并为自己的应用程序申请容器资源。
  4. MapReduce ApplicationMaster 申请到需要的容器后,立即和相应的 NodeManager进程通信,将用户 MapReduce 程序分发到 NodeManager 进程所在服务器,并在容器中运行,运行的就是 Map 或者 Reduce 任务。
  5. Map 或者 Reduce 任务在运行期和 MapReduce ApplicationMaster 通信,汇报自己的运行状态,如果运行结束,MapReduce ApplicationMaster 向 ResourceManager 进程注销并释放所有的容器资源。

image.png

详细流程

  1. 作业提交 client 调用 job.waitForCompletion 方法,向整个集群提交 MapReduce 作业 (第 1 步) 。新的作业 ID(应用 ID) 由资源管理器分配 (第 2 步)。作业的 client 核实作业的输出, 计算输入的 split, 将作业的资源 (包括 Jar 包,配置文件, split 信息) 拷贝给 HDFS(第 3 步)。 最后, 通过调用资源管理器的 submitApplication() 来提交作业 (第 4 步)。

  2. 作业初始化 当资源管理器收到 submitApplciation() 的请求时, 就将该请求发给调度器 (scheduler), 调度器分配 container, 然后资源管理器在该 container 内启动应用管理器进程, 由节点管理器监控 (第 5 步)。

MapReduce 作业的应用管理器是一个主类为 MRAppMaster 的 Java 应用,其通过创造一些 bookkeeping 对象来监控作业的进度, 得到任务的进度和完成报告 (第 6 步)。然后其通过分布式文件系统得到由客户端计算好的输入 split(第 7 步),然后为每个输入 split 创建一个 map 任务, 根据 mapreduce.job.reduces 创建 reduce 任务对象。

  1. 任务分配 如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务。

如果不是小作业, 那么应用管理器向资源管理器请求 container 来运行所有的 map 和 reduce 任务 (第 8 步)。这些请求是通过心跳来传输的, 包括每个 map 任务的数据位置,比如存放输入 split 的主机名和机架 (rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入 split 的节点相同机架的节点。

  1. 任务运行 当一个任务由资源管理器的调度器分配给一个 container 后,应用管理器通过联系节点管理器来启动 container(第 9 步)。任务由一个主类为 YarnChild 的 Java 应用执行, 在运行任务之前首先本地化任务需要的资源,比如作业配置,JAR 文件, 以及分布式缓存的所有文件 (第 10 步。 最后, 运行 map 或 reduce 任务 (第 11 步)。

YarnChild 运行在一个专用的 JVM 中, 但是 YARN 不支持 JVM 重用。

  1. 进度和状态更新 YARN 中的任务将其进度和状态 (包括 counter) 返回给应用管理器, 客户端每秒 (通 mapreduce.client.progressmonitor.pollinterval 设置) 向应用管理器请求进度更新, 展示给用户。

  2. 作业完成 除了向应用管理器请求作业进度外, 客户端每 5 分钟都会通过调用 waitForCompletion() 来检查作业是否完成,时间间隔可以通过 mapreduce.client.completion.pollinterval 来设置。作业完成之后, 应用管理器和 container 会清理工作状态, OutputCommiter 的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。

spark为什么快?

RDD 的编程模型更简单,DAG 切分的多阶段计算过程更快速,使用内存存储中间计算结果更高效。

image.png

  1. MapReduce 简单粗暴地根据 shuffle 将大数据计算分成 Map 和 Reduce 两个阶段,而Spark将Map和Reducel当作一个阶段持续计算,形成一个更加优雅、高效地计算模型。尽管其本质依然是Map和Reduce,但是减少了对HDFS的访问,减少了作业调度的执行次数,因此执行更快。
  2. spark的持久化会优先放内存,从内存中进行数据读写天然快磁盘数量级

image.png

  • 与Hadoop的MapReduce相比,Spark基于内存的运算要快100倍以上,基于硬盘的运算也要快10倍以上
  • Spark实现了高效的DAG执行引擎,可以通过基于内存来高效处理数据流。计算的中间结果是存在于内存中的。

4 Spark

  • 存储的弹性:内存与磁盘的自动切换
  • 容错的弹性:数据丢失可以自动恢复
  • 计算的弹性:计算出错重试机制
  • 分片的弹性:根据需要重新分片

1)自动进行内存和磁盘数据存储的切换
Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换
2)基于血统的高效容错机制
在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。
3)Task如果失败会自动进行特定次数的重试
RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。
4)Stage如果失败会自动进行特定次数的重试
如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。
5)Checkpoint和Persist可主动或被动触发
RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。
6)数据调度弹性
Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。
7)数据分片的高度弹性
可以根据业务的特征,动态调整数据分片的个数,提升整体的应用执行效率。