Spark学习(一):概述
Spark学习(一):概述
上周六面试腾讯时被问到是否了解Spark,彼时对Spark毫无接触故答不了解,面试结束后了解到Spark与MapReduce渊源颇深,去年夏天学习MIT6.824分布式系统设计时曾深入学习过MapReduce(分布式学习:MapReduce - pinoky - 博客园 (cnblogs.com))故对Spark产生兴趣,由此开始学习
Spark诞生原因
MapReduce作为一个分布式、并行处理的计算框架,存在着诸多缺陷如下:
- 仅支持Map,Reduce两种语义操作
- 由于MapReduce操作数据涉及到大量磁盘IO,故执行效率低,时间开销大
- MapReduce主要适用于大规模离线批处理,不适合迭代计算、交互式计算、实时流处理等场景
计算框架众多、选型困难、学习成本高就成为了大数据领域的一大痛点,常见的计算框架就有如下几种:
- 批处理:MapReduce
- 流处理:Storm,Flink
- 交互式计算:Impala,Presto
- ……
业界迫切需要统一计算框架,在一个统一框架下实现批处理、流处理、交互式计算、机器学习,于是Spark应运而生
Spark特点
- Spark Core:实现了 Spark 的基本功能,包含 RDD、任务调度、内存管理、错误恢复、与存储系统交互等模块。
- Spark SQL:Spark 用来操作结构化数据的程序包。通过 Spark SQL,我们可以使用 SQL 操作数据。
- Spark Streaming:Spark 提供的对实时数据进行流式计算的组件。提供了用来操作数据流的 API。
- Spark MLlib:提供常见的机器学习(ML)功能的程序库。包括分类、回归、聚类、协同过滤等,还提供了模型评估、数据导入等额外的支持功能。
- GraphX(图计算):Spark 中用于图计算的 API,性能良好,拥有丰富的功能和运算符,能在海量数据上自如地运行复杂的图算法。
- 集群管理器:Spark 设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算。
- Structured Streaming:处理结构化流,统一了离线和实时的 API。
使用Scala语言开发的Spark提供了多种运行模式,具有高吞吐、低延时、通用易扩展、高容错等特点,同时也可兼容hadoop等框架,降低迁移成本
Spark RDD 与 编程模型
RDD,即弹性分布式数据集,Spark基于RDD进行计算,其的特点如下
- 是分布在集群中的只读对象集合
- 由多个partition组成
- 通过转换操作构造
- 失败后自动重构
- 存储在内存或磁盘中
RDD主要有两种操作:Transformation,Action
- Transformation:惰性执行,只记录转换关系,不触发计算
- 将Scala集合或Hadoop输入数据构造成一个新RDD
- 通过已有的RDD产生新的RDD
- 例如:map、filter、flatmap、union等
- Action:真正触发计算
- 通过RDD计算得到一个值或一组值
- 例如:first、collect、saveAsTextFile、foreach
以一个简单的词频统计为例 简单介绍一下Spark基于RDD的编程模型
- 在textFile阶段,首先将数据从HDFS中读出变成RDD
- 进入flatMap阶段,将RDD中的数据按某分隔符进行分隔,在本例中以“\t”为分隔符
- 随后map阶段,会将RDD中的每个单词的词频记为1,即
val rdd3 = rdd2.map((_,1))
- 再接下去进行shuffle,将相同key的数据reduce到同一个节点上去统计词频,即
reduceByKey((_+_))
- 最终再将reduce得到的RDD保存为数据文件,放入HDFS中
通过这个例子可以明显地感觉到,基于RDD的编程模型 与 单机编程是基本上一致的(不同于MapReduce,MapReduce是典型的分布式编程,需要指定map阶段各个节点的处理动作,也需要指定reduce阶段各个节点的操作)
为什么说RDD是弹性的:由于RDD的数据保存在内存中,如果说执行过程中某个partition出现错误,不需要重新回到HDFS从头开始执行,而是只需要回到上一个阶段重新计算即可,这在flatMap、map这种一对一的处理阶段尤其方便(因为这种一对一的计算往往都是在同一个节点上进行的)
RDD的宽依赖与窄依赖
窄依赖:即父RDD中的分区最多只能被一个子RDD的一个分区使用,也就是一对一的关系,如下图map、filter、union都是典型的窄依赖,在这种依赖下子RDD如果有部分partition数据丢失或损坏,只需从对应的父RDD重新计算即可恢复
宽依赖:即子RDD分区依赖父RDD的所有分区,如groupByKey、sortByKey、reduceByKey,在这种依赖下子RDD如果部分partitio数据丢失或损坏,必须从所有父RDD的partion重新计算,所以相对于窄依赖需要付出更多的代价,应该尽量避免使用
Spark的程序运行架构
Master为主节点,Driver为作业提交管理程序,Worker为从节点
Client向Master提交命令后,Driver会先向Master申请执行n个task的资源executor,然后把sparkcontext分发到worker上的executor上执行,待worker执行结束后会向Driver进行汇报,Driver发现所有task执行完成后就向Master申请资源的释放
一个作业的执行可以分为以下几个阶段:
-
生成逻辑查询计划:主要关注于 RDD状态的解析,(比如map阶段:RDD从[string]变成[string,int]的转变)
-
生成物理查询计划:主要关注底层数据的状态 ,根据DAG图是否有宽窄依赖,做stage的切分(也可以说根据是否发生shuffle进行切分)
- 同一个stage里面的转换,可以放在同一个节点上面处理
- stage数量越多,shuffle越多,性能越差
-
任务调度与任务执行:在同一个stage下、进行一对一转换的partition可以合并为一个task,以上图为例,可以划分为7个task,最终分发给executor执行