大数据开发面试的总结-第四篇

1、hdfs读写文件的机制

(1) HDFS集群角色:NameNode、DataNode

HDFS集群分为两大角色:NameNode、DataNode
NameNode负责管理整个文件系统的元数据
DataNode 负责管理用户的文件数据块
文件会按照固定的大小(blocksize)切成若干块后分布式存储在若干台datanode上
每一个文件块可以有多个副本,并存放在不同的datanode上
Datanode会定期向Namenode汇报自身所保存的文件block信息,而namenode则会负责保持文件的副本数量
HDFS的内部工作机制对客户端保持透明,客户端请求访问HDFS都是通过向namenode申请来进行

(2) HDFS写数据流程

1)概述
客户端要向HDFS写数据,首先要跟namenode通信以确认可以写文件并获得接收文件block的datanode,然后,客户端按顺序将文件逐个block传递给相应datanode,并由接收到block的datanode负责向其他datanode复制block的副本
2) 详细步骤图
在这里插入图片描述
3) 详细步骤解析
1、根namenode通信请求上传文件,namenode检查目标文件是否已存在,父目录是否存在
2、namenode返回是否可以上传
3、client请求第一个 block该传输到哪些datanode服务器上
4、namenode返回3个datanode服务器ABC
5、client请求3台dn中的一台A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会继续调用B,然后B调用C,将真个pipeline建立完成,逐级返回客户端
6、client开始往A上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,A收到一个packet就会传给B,B传给C;A每传一个packet会放入一个应答队列等待应答
7、当一个block传输完成之后,client再次请求namenode上传第二个block的服务器。

(3)HDFS读数据流程

1) 概述
客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件
2) 详细步骤图
在这里插入图片描述

3) 详细步骤解析
1、跟namenode通信查询元数据,找到文件块所在的datanode服务器
2、挑选一台datanode(就近原则,然后随机)服务器,请求建立socket流
3、datanode开始发送数据(从磁盘里面读取数据放入流,以packet为单位来做校验)
4、客户端以packet为单位接收,先在本地缓存,然后写入目标文件

2、spark和MapReduce的关系

目前的大数据处理可以分为以下三个类型:
复杂的批量数据处理(batch data processing),通常的时间跨度在数十分钟到数小时之间;
基于历史数据的交互式查询(interactive query),通常的时间跨度在数十秒到数分钟之间;
基于实时数据流的数据处理(streaming data processing),通常的时间跨度在数百毫秒到数秒之间。
大数据处理势必需要依赖集群环境,而集群环境有三大挑战,分别是并行化、单点失败处理、资源共享,分别可以采用以并行化的方式重写应用程序、对单点失败的处理方式、动态地进行计算资源的分配等解决方案来面对挑战。

(1)MapReduce

MapReduce 是为 Apache Hadoop 量身订做的,它非常适用于 Hadoop 的使用场景,即大规模日志处理系统、批量数据提取加载工具 (ETL 工具) 等类似操作。但是伴随着 Hadoop 地盘的不断扩张,Hadoop 的开发者们发现 MapReduce 在很多场景下并不是最佳选择,于是 Hadoop 开始把资源管理放入到自己独立的组件 YARN 里面。此外,类似于 Impala 这样的项目也开始逐渐进入到我们的架构中,Impala 提供 SQL 语义,能查询存储在 Hadoop 的 HDFS 和 HBase 中的 PB 级大数据。之前也有类似的项目,例如 Hive。Hive 系统虽然也提供了 SQL 语义,但由于 Hive 底层执行使用的是 MapReduce 引擎,仍然是一个批处理过程,难以满足查询的交互性。相比之下,Impala 的最大特点也是最大卖点就是它的效率。

第一代 Hadoop MapReduce 是一个在计算机集群上分布式处理海量数据集的软件框架,包括一个 JobTracker 和一定数量的 TaskTracker。运行流程图如图 1 所示。
在这里插入图片描述
图 1. MapReduce 运行流程图
在最上层有 4 个独立的实体,即客户端、jobtracker、tasktracker 和分布式文件系统。客户端提交 MapReduce 作业;jobtracker 协调作业的运行;jobtracker 是一个 Java 应用程序,它的主类是 JobTracker;tasktracker 运行作业划分后的任务,tasktracker 也是一个 Java 应用程序,它的主类是 TaskTracker。Hadoop 运行 MapReduce 作业的步骤主要包括提交作业、初始化作业、分配任务、执行任务、更新进度和状态、完成作业等 6 个步骤。

(2)Spark 简介

Spark 生态系统的目标就是将批处理、交互式处理、流式处理融合到一个软件框架内。Spark 是一个基于内存计算的开源的集群计算系统,目的是让数据分析更加快速。Spark 非常小巧玲珑,由加州伯克利大学 AMP 实验室的 Matei 为主的小团队所开发。使用的语言是 Scala,项目的 core 部分的代码只有 63 个 Scala 文件,非常短小精悍。Spark 启用了内存分布数据集,除了能够提供交互式查询外,它还可以优化迭代工作负载。Spark 提供了基于内存的计算集群,在分析数据时将数据导入内存以实现快速查询,速度比基于磁盘的系统,如 Hadoop 快很多。Spark 最初是为了处理迭代算法,如机器学习、图挖掘算法等,以及交互式数据挖掘算法而开发的。在这两种场景下,Spark 的运行速度可以达到 Hadoop 的几百倍。

Spark 允许应用在内存中保存工作集以便高效地重复利用,它支持多种数据处理应用,同时也保持了 MapReduce 的重要特性,如高容错性、数据本地化、大规模数据处理等。此外,提出了弹性分布式数据集 (Resilient Distributed Datasets) 的概念:
RDD 表现为一个 Scala 对象,可由一个文件创建而来;
分布在一个集群内的,不可变的对象切分集;
通过并行处理(map、filter、groupby、join)固定数据(BaseRDD)创建模型,生成 Transformed RDD;
故障时可使用 RDD 血统信息重建;
可高速缓存,以便再利用。
图 2 所示是一个日志挖掘的示例代码,首先将日志数据中的 error 信息导入内存,然后进行交互搜索。
在这里插入图片描述
图 2. RDD 代码示例
在导入数据时,模型以 block 形式存在于 worker 上,由 driver 向 worker 分发任务,处理完后 work 向 driver 反馈结果。也可在 work 上对数据模型建立高速缓存 cache,对 cache 的处理过程与 block 类似,也是一个分发、反馈的过程。

Spark 的 RDD 概念能够取得和专有系统同样的性能,还能提供包括容错处理、滞后节点处理等这些专有系统缺乏的特性。如下:

迭代算法:这是目前专有系统实现的非常普遍的一种应用场景,比如迭代计算可以用于图处理和机器学习。RDD 能够很好地实现这些模型,包括 Pregel、HaLoop 和 GraphLab 等模型。

关系型查询:对于 MapReduce 来说非常重要的需求就是运行 SQL 查询,包括长期运行、数小时的批处理作业和交互式的查询。然而对于 MapReduce 而言,对比并行数据库进行交互式查询,有其内在的缺点,比如由于其容错的模型而导致速度很慢。利用 RDD 模型,可以通过实现许多通用的数据库引擎特性,从而获得很好的性能。

MapReduce 批处理:RDD 提供的接口是 MapReduce 的超集,所以 RDD 能够有效地运行利用 MapReduce 实现的应用程序,另外 RDD 还适合更加抽象的基于 DAG 的应用程序。

流式处理:目前的流式系统也只提供了有限的容错处理,需要消耗系统非常大的拷贝代码或者非常长的容错时间。特别是在目前的系统中,基本都是基于连续计算的模型,常住的有状态的操作会处理到达的每一条记录。为了恢复失败的节点,它们需要为每一个操作复制两份操作,或者将上游的数据进行代价较大的操作重放,利用 RDD 实现离散数据流,可以克服上述问题。离散数据流将流式计算当作一系列的短小而确定的批处理操作,而不是常驻的有状态的操作,将两个离散流之间的状态保存在 RDD 中。离散流模型能够允许通过 RDD 的继承关系图进行并行性的恢复而不需要进行数据拷贝。

(3)Spark 内部进程术语解释

Application:基于 Spark 的用户程序,包含了 driver 程序和集群上的 executor;
Driver Program:运行 main 函数并且新建 SparkContext 的程序;
Cluster Manager:在集群上获取资源的外部服务 (例如:standalone,Mesos,Yarn);
Worker Node:集群中任何可以运行应用代码的节点;
Executor:是在一个 worker node 上为某应用启动的一个进程,该进程负责运行任务,并且负责将数据存在内存或者磁盘上。每个应用都有各自独立的 executors;
Task:被送到某个 executor 上的工作单元;
Job:包含很多任务的并行计算,可以与 Spark 的 action 对应;
Stage:一个 Job 会被拆分很多组任务,每组任务被称为 Stage(就像 Mapreduce 分 map 任务和 reduce 任务一样)。

(4)MapReduce对比Spark的实现方式

场景:计算一个文本文件里每一行的字符数量。在 Hadoop MapReduce 里,我们需要为 Mapper 方法准备一个键值对,key 用作行的行数,value 的值是这一行的字符数量。

清单 1. MapReduce 方式 Map 函数

public class LineLengthCountMapper
 extends Mapper<LongWritable,Text,IntWritable,IntWritable> {
 @Override
 protected void map(LongWritable lineNumber, Text line, Context context)
 throws IOException, InterruptedException {
 context.write(new IntWritable(line.getLength()), new IntWritable(1));
 }
}

清单 1 所示代码,由于 Mappers 和 Reducers 只处理键值对,所以对于类 LineLengthCountMapper 而言,输入是 TextInputFormat 对象,它的 key 由行数提供,value 就是该行所有字符。换成 Spark 之后的代码如清单 2 所示。

清单 2. Spark 方式 Map 函数

lines.map(line => (line.length, 1))

在 Spark 里,输入是弹性分布式数据集 (Resilient Distributed Dataset),Spark 不需要 key-value 键值对,代之的是 Scala 元祖 (tuple),它是通过 (line.length, 1) 这样的 (a,b) 语法创建的。以上代码中 map() 操作是一个 RDD,(line.length, 1) 元祖。当一个 RDD 包含元祖时,它依赖于其他方法,例如 reduceByKey(),该方法对于重新生成 MapReduce 特性具有重要意义。

清单 3 所示代码是 Hadoop MapReduce 统计每一行的字符数,然后以 Reduce 方式输出。

清单 3. MapReduce 方式 Reduce 函数

public class LineLengthReducer
 extends Reducer<IntWritable,IntWritable,IntWritable,IntWritable> {
 @Override
 protected void reduce(IntWritable length, Iterable<IntWritable> counts, Context context)
 throws IOException, InterruptedException {
 int sum = 0;
 for (IntWritable count : counts) {
 sum += count.get();
 }
 context.write(length, new IntWritable(sum));
 }
}

Spark 里面的对应代码如清单 12 所示。

清单 4. Spark 方式 Reduce 函数

val lengthCounts = lines.map(line => (line.length, 1)).reduceByKey(_ + _)

Spark 的 RDD API 有一个 reduce() 方法,它会 reduce 所有的 key-value 键值对到一个独立的 value。

我们现在需要统计大写字母开头的单词数量,对于文本的每一行而言,一个 Mapper 可能需要统计很多个键值对,代码如清单 5 所示。

清单 5. MapReduce 方式计算字符数量

public class CountUppercaseMapper
 extends Mapper<LongWritable,Text,Text,IntWritable> {
 @Override
 protected void map(LongWritable lineNumber, Text line, Context context)
 throws IOException, InterruptedException {
 for (String word : line.toString().split(" ")) {
 if (Character.isUpperCase(word.charAt(0))) {
 context.write(new Text(word), new IntWritable(1));
 }
 }
 }
}

在 Spark 里面,对应的代码如清单 6所示。

清单 6. Spark 方式计算字符数量

lines.flatMap(
_.split(" ").filter(word => Character.isUpperCase(word(0))).map(word => (word,1))
)

MapReduce 依赖的 Map 方法这里并不适用,因为每一个输入必须对应一个输出,这样的话,每一行可能占用到很多的输出。相反的,Spark 里面的 Map 方法比较简单。Spark 里面的方法是,首先对每一行数据进行汇总后存入一个输出结果物数组,这个数组可能是空的,也可能包含了很多的值,最终这个数组会作为一个 RDD 作为输出物。这就是 flatMap() 方法的功能,它对每一行文本里的单词转换成函数内部的元组后进行了过滤。

在 Spark 里面,reduceByKey() 方法可以被用来统计每篇文章里面出现的字母数量。如果我们想统计每一篇文章里面出现的大写字母数量,在 MapReduce 里程序可以如清单 7所示。

清单 7. MapReduce 方式

public class CountUppercaseReducer
 extends Reducer<Text,IntWritable,Text,IntWritable> {
 @Override
 protected void reduce(Text word, Iterable<IntWritable> counts, Context context)
 throws IOException, InterruptedException {
 int sum = 0;
 for (IntWritable count : counts) {
 sum += count.get();
 }
 context.write(new Text(word.toString().toUpperCase()), new IntWritable(sum));
 }
}

在 Spark 里,代码如清单 8所示。

清单 8. Spark 方式
1
groupByKey().map { case (word,ones) => (word.toUpperCase, ones.sum) }
groupByKey() 方法负责收集一个 key 的所有值,不应用于一个 reduce 方法。本例中,key 被转换成大写字母,值被直接相加算出总和。但这里需要注意,如果一个 key 与很多个 value 相关联,可能会出现 Out Of Memory 错误。

Spark 提供了一个简单的方法可以转换 key 对应的值,这个方法把 reduce 方法过程移交给了 Spark,可以避免出现 OOM 异常。

> reduceByKey(_ + _).map { case (word,total) => (word.toUpperCase,total)
> }

setup() 方法在 MapReduce 里面主要的作用是在 map 方法开始前对输入进行处理,常用的场景是连接数据库,可以在 cleanup() 方法中释放在 setup() 方法里面占用的资源。

清单 9. MapReduce 方式

> public class SetupCleanupMapper extends
> Mapper<LongWritable,Text,Text,IntWritable> {  private Connection
> dbConnection;  @Override  protected void setup(Context context) { 
> dbConnection = ...;  }  ...  @Override  protected void cleanup(Context
> context) {  dbConnection.close();  } }

spark中不存在这样的方式,不需要这样处理。

3、当hdfs写文件的时候,如果某一块数据写入失败,会怎样处理?

如果第一个备份就写入失败:
如果在数据写入期间datanode发生故障,则执行以下操作(对写入的数据的客户端是透明的)。首先关闭管道,然后将确认队列(ack queue)中的数据包都添加回数据队列(data queue)的最前端,以确保故障节点下游的datanode不会漏掉任何一个数据包。为存储在正常的datanode上的数据块指定一个新的标识,并将该标识传递给namenode,以便故障的datanode在恢复后可以删除存储的部分数据块。从管线(pipeline)中删除故障数据节点,并把使用剩下的正常的datanode构建一个新的管线(pipeline)。余下的数据块写入新的管线中。namenode注意到块副本量不足时,会在另一个节点上创建一个新的复本,后续的数据块继续正常接受处理。这里接受正常处理的意思,我的理解是:因为namenode 发现副本数小于我们配置的数目,从新找一个datanode,然后把副本数不足的数据块都复制到新的datanode上,处理完这些数据块后,后面的新数据块就可以又按照管线(pipeline)的方式去处理了。

可以看出,故障的datanode会删除,namenode会用正常的datanode写入数据,后面再通过异步复制的方式恢复故障的datanode数据。

在一个块被写入期间可能会有多个datanode同时发生故障,但非常少见。只要写入了dfs.replication.min的副本书(默认为一),写入操作就会成功,并且这个块可以在集群中异步复制,直到达到其目标复本数(默认三个复本),原因见下方:

4、向datanode写入文件时,ACK 是否三个备份都写成功之后才确认成功操作?

不是的,只要成功写入的节点数量达到dfs.replication.min(默认为1),那么就任务是写成功的
正常情况下:
① 在进行写操作的时候(以默认备份3份为例),DataNode_1接受数据后,首先将数据写入buffer,再将数据写入DatNode_2,写入成功后将 buffer 中的数据写入本地磁盘,并等待ACK信息
② 重复上一个步骤,DataNode入本地磁盘后,等待ACK信息
③ 如果ACK都成功返回后,发送给Client,本次写入成功

如果一个节点或多个节点写入失败:
① 只要成功写入的节点数量达到dfs.replication.min(默认为1),那么就任务是写成功的。然后NameNode会通过异步的方式将block复制到其他节点,使数据副本达到dfs.replication参数配置的个数

5、yarn的工作机制和调度策略

(1)YARN 基本架构

YARN是Hadoop 2.0中的资源管理系统,它的基本设计思想是将MRv1中的JobTracker拆分成了两个独立的服务:一个全局的资源管理器ResourceManager和每个应用程序特有的ApplicationMaster。

其中ResourceManager负责整个系统的资源管理和分配,而ApplicationMaster负责单个应用程序的管理。

(2)YARN基本组成结构

YARN总体上仍然是Master/Slave结构,在整个资源管理框架中,ResourceManager为Master,NodeManager为Slave,ResourceManager负责对各个NodeManager上的资源进行统一管理和调度。当用户提交一个应用程序时,需要提供一个用以跟踪和管理这个程序的ApplicationMaster,它负责向ResourceManager申请资源,并要求NodeManger启动可以占用一定资源的任务。由于不同的ApplicationMaster被分布到不同的节点上,因此它们之间不会相互影响。在本小节中,我们将对YARN的基本组成结构进行介绍。

下图描述了YARN的基本组成结构,YARN主要由ResourceManager、NodeManager、ApplicationMaster(图中给出了MapReduce和MPI两种计算框架的ApplicationMaster,分别为MR AppMstr和MPI AppMstr)和Container等几个组件构成。
在这里插入图片描述
1.ResourceManager(RM)

RM是一个全局的资源管理器,负责整个系统的资源管理和分配。它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Applications Manager,ASM)。

(1)调度器

调度器根据容量、队列等限制条件(如每个队列分配一定的资源,最多执行一定数量的作业等),将系统中的资源分配给各个正在运行的应用程序。

需要注意的是,该调度器是一个“纯调度器”,它不再从事任何与具体应用程序相关的工作,比如不负责监控或者跟踪应用的执行状态等,也不负责重新启动因应用执行失败或者硬件故障而产生的失败任务,这些均交由应用程序相关的ApplicationMaster完成。调度器仅根据各个应用程序的资源需求进行资源分配,而资源分配单位用一个抽象概念“资源容器”(Resource Container,简称Container)表示,Container是一个动态资源分配单位,它将内存、CPU、磁盘、网络等资源封装在一起,从而限定每个任务使用的资源量。此外,该调度器是一个可插拔的组件,用户可根据自己的需要设计新的调度器,YARN提供了多种直接可用的调度器,比如Fair Scheduler和Capacity Scheduler等。

(2) 应用程序管理器

应用程序管理器负责管理整个系统中所有应用程序,包括应用程序提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重新启动它等。

  1. ApplicationMaster(AM)

用户提交的每个应用程序均包含1个AM,主要功能包括:

与RM调度器协商以获取资源(用Container表示);

将得到的任务进一步分配给内部的任务;

与NM通信以启动/停止任务;

监控所有任务运行状态,并在任务运行失败时重新为任务申请资源以重启任务。

当前YARN自带了两个AM实现,一个是用于演示AM编写方法的实例程序distributedshell,它可以申请一定数目的Container以并行运行一个Shell命令或者Shell脚本;另一个是运行MapReduce应用程序的AM—MRAppMaster,我们将在第8章对其进行介绍。此外,一些其他的计算框架对应的AM正在开发中,比如Open MPI、Spark等。

  1. NodeManager(NM)

NM是每个节点上的资源和任务管理器,一方面,它会定时地向RM汇报本节点上的资源使用情况和各个Container的运行状态;另一方面,它接收并处理来自AM的Container启动/停止等各种请求。
4. Container

Container是YARN中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等,当AM向RM申请资源时,RM为AM返回的资源便是用Container表示的。YARN会为每个任务分配一个Container,且该任务只能使用该Container中描述的资源。

需要注意的是,Container不同于MRv1中的slot,它是一个动态资源划分单位,是根据应用程序的需求动态生成的。截至本书完成时,YARN仅支持CPU和内存两种资源,且使用了轻量级资源隔离机制Cgroups进行资源隔离。

(4)YARN工作流程

当用户向YARN中提交一个应用程序后,YARN将分两个阶段运行该应用程序:

第一个阶段是启动ApplicationMaster;

第二个阶段是由ApplicationMaster创建应用程序,为它申请资源,并监控它的整个运行过程,直到运行完成。

如下图所示,YARN的工作流程分为以下几个步骤:

 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200617175410220.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzI2NTY2MTM3,size_16,color_FFFFFF,t_70)

步骤1 用户向YARN中提交应用程序,其中包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。

步骤2 ResourceManager为该应用程序分配第一个Container,并与对应的Node-Manager通信,要求它在这个Container中启动应用程序的ApplicationMaster。

步骤3 ApplicationMaster首先向ResourceManager注册,这样用户可以直接通过ResourceManager查看应用程序的运行状态,然后它将为各个任务申请资源,并监控它的运行状态,直到运行结束,即重复步骤4~7。

步骤4 ApplicationMaster采用轮询的方式通过RPC协议向ResourceManager申请和领取资源。

步骤5 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它启动任务。

步骤6 NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本中,并通过运行该脚本启动任务。

步骤7 各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。

 在应用程序运行过程中,用户可随时通过RPC向ApplicationMaster查询应用程序的当前运行状态。

步骤8 应用程序运行完成后,ApplicationMaster向ResourceManager注销并关闭自己。

(5)多角度理解YARN

在这里插入图片描述
可将YARN看做一个云操作系统,它负责为应用程序启动ApplicationMaster(相当于主线程),然后再由ApplicationMaster负责数据切分、任务分配、启动和监控等工作,而由ApplicationMaster启动的各个Task(相当于子线程)仅负责自己的计算任务。当所有任务计算完成后,ApplicationMaster认为应用程序运行完成,然后退出。

6、参考

HDFS读写数据的原理:https://blog.csdn.net/qq_16633405/article/details/78907070

如何将 MapReduce 转化为 Spark:
https://www.ibm.com/developerworks/cn/opensource/os-cn-mapreduce-spark/index.html

HDFS 从客户端写入到 DataNode 时,ACK 是否三个备份都写成功之后再确认成功操作?:
https://blog.csdn.net/HeatDeath/article/details/79012340

Hadoop Yarn 框架原理及运作机制:https://blog.csdn.net/liuwenbo0920/article/details/43304243

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章