Spark
-
什么是Spark
Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行计算框架,Spark拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
Spark是Scala编写,方便快速编程。 -
总体技术栈讲解
回顾hadoop
spark 和 MapReduce 的区别
都是分布式计算框架,spark 基于内存,mapreduce基于hdfs, Spark 处理数据的能力一般是mr的十倍以上,Spark中除了基于内存计算外,还有DAG有向无环图来切分任务的执行先后顺序
MapReduce
Spark: 快除了内存计算外,还有DAG
Spark的运行模式
- local 多用于本地测试。如在 eclipse,,idea中写程序测试
- standalone: 是Spark自带的一个资源调度框架,它支持完全分布式
- Yarn: Hadoop 生态圈里的一个资源调度框架,Spark也是可以基于Yarn
要基于Yarn来进行资源调度,必须实现 ApplicationMaster接口。spark实现了这个接口,所以可以基于yarn - Mesos 资源调度框架
Spark的执行原理
以上图中有四个机器节点,Driver和Worker是启动在节点上的进程,运行在JVM中的进程。
Driver与集群节点之间有频繁的通信。
Driver负责任务(tasks)的分发和结果的回收。任务的调度。如果task的计算结果非常大就不要回收了。会造成oom。
Worker是Standalone资源调度框架里面资源管理的从节点。也是JVM进程。
Master是Standalone资源调度框架里面资源管理的主节点。也是JVM进程。
MR 和 Spark的区别
1 spark 是基于内存迭代计算,MR是基于磁盘的迭代计算
2 Spark中有DAG有向无环图
3 Mr中只有map 和 reduce,相当于spark两个算子(map ,reduceByKey),spark中有多种算子
Spark 的代码流程
- 创建SparkConf对象
可以设置Application name。
可以设置运行模式及资源需求。 - 创建SparkContext对象
- 基于Spark的上下文创建一个RDD,对RDD进行处理。
- 应用程序中要有Action类算子来触发Transformation类算子执行。
- 关闭Spark上下文对象SparkContext。
package testScalaSpark
import org.apache.spark.{SparkConf, SparkContext}
object spark_wc {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("wordCount")
conf.setMaster("local")
val sc = new SparkContext(conf)
sc.textFile(path="./data/words").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).foreach(println)
sc.stop()
}
}
SparkCore
RDD
-
概念:RDD Resilient Distributed Dateset 弹性分布式数据集
-
RDD的五大特性
- RDD是由一系列的partition组成的。(和读取的文件的block数是一样的,一一对应,有几个文件就有几个分区数)
- 函数(算子)是作用在RDD每一个partition(split)上的。(下级RDD分区个数有上级RDD分区个数决定)
- RDD之间有一系列的依赖关系。
- 分区器是作用在K,V格式的RDD上。
- RDD提供一系列最佳的计算位置。利用数据处理的本地化,计算向数据移动
-
问题:
- Spark读取hdfs中数据的方法textfile底层是调用的mr读取hdfs的方法,首先会用split切分block,每个split对应生成RDD的每个partition
- 什么是kv格式的 RDD?
RDD中的数据是一个的tuple2数据,那么这个RDD就是KV格式的RDD - 哪里体现了RDD的弹性(容错)?
1). RDD之间有依赖关系
2). RDD的partition 可多可少 - 哪里体现了RDD的分布式?
RDD的partition是分布在多个节点上的
-
Lineage 血统 : 一系列RDD组成的依赖关系
spark中 由一系列RDD 组成的一条链,一个有向无环图 -
算子的分类
- transformations 延迟算子
- Action算子
- 持久化算子
transformations(转换)算子 : 懒执行
延迟执行:需要action算子触发,transformation算子才执行
- filter
过滤符合条件的记录数,true保留,false过滤掉。 - map
将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素。
特点:输入一条,输出一条数据。 - flatMap
先map后flat。与map类似,每个输入项可以映射为0到多个输出项。 - sample
随机抽样算子,根据传进去的小数按比例进行又放回或者无放回的抽样。 - reduceByKey
将相同的Key根据相应的逻辑进行处理。 - sortByKey/sortBy
作用在K,V格式的RDD上,对key进行升序或者降序排序。
通过上图可以看出,
transformations是由RDD类型变成RDD类型
action是由RDD变成一值类型
action 算子: 触发执行
当有一个 action算子时,就会生成一个job
我们写的scala可以看成一个 sparkapplication ,一个sparkapplication 通过看有几个Action算子就有几个job
-
count
返回数据集中的元素数。会在结果计算完成后回收到Driver端。 -
take(n)
返回一个包含数据集前n个元素的集合。 -
first
first=take(1),返回数据集中的第一个元素。 -
foreach
循环遍历数据集中的每个元素,运行相应的逻辑。 -
collect
将计算结果回收到Driver端。
持久化算子
lines = sc.textFile(“hdfs://...”)
errors = lines.filter(_.startsWith(“ERROR”))
Mysql_errors = errors.filter(_.contain(“MySQL”)).count
http_errors = errors.filter(_.contain(“Http”)).count
上面这个代码。有两个action算子,在第一个调用count算子时回运行前两行。在第二个count调用时又会运行前两个行,会对文件重复io读取
所以需要进行RDD持久化,下面的算子都是懒执行的,最小持久化单位是partition
- cache 默认将数据存储在内存中
def cache(): this.type = persist()
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
- persist 可以手动执行持久化级别
查看源码
class StorageLevel private(
private var _useDisk: Boolean,
private var _useMemory: Boolean,
private var _useOffHeap: Boolean,
private var _deserialized: Boolean,
private var _replication: Int = 1)
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
- unpersist() 将数据从内存中卸载掉
rdd.unpersist()
-
checkpoint :
- 将数据直接持久化到指定目录,当lineage的逻辑非常复杂,可以尝试使用checkpoint ,checkpoint 还可以切断RRD的依赖关系
- 特殊场景使用checkpoint,对RDD使用checkpoint要慎用
- checkpoint 可以指定到目录,可以将数据持久化到指定的目录中,当application执行完成之后,这个目录中的数据不会被清除
- checkpoint的执行流程
- 当sparkjob执行完成后,Spark会从后往前回溯,找到checkpointRDD做标记
- 回溯完成之后,Spark框架会重新启动一个job,计算标记的RDD的数据,放入指定的checkpoint目录中
- 数据计算完成后,放入目录后,会切断RDD的依赖关系,当SparkApplication执行完成之后,数据目录中的数据不会被清除
- 优化:对哪个RDD进行checkpoint,最好先cache一下,这样在计算完成后再计算这个CheckpointRDD数据的时候可以直接在内存中拿到放在指定的目录中
-
区别
- 这三个持久化都是懒执行,最小持久化单位是 partiiton
- cache 和 persist之后可以直接赋值给一个值,下次直接使用这个值,就是使用的持久化的数据
- cache和 persist的数据,当application执行完成后就会自动清除
创建RDD
- textFile
var rdd = sc.textFile("./data/persistData.txt")
- parallelize
val rddtest: RDD[String] = sc.parallelize(Array[String]("a","b"))
rddtest.foreach(println)
- makeRDD
val rddtest1: RDD[String] = sc.makeRDD(Array[String]("a","b"))
rddtest1.foreach(println)
spark基于standalone-client 提交任务
提交任务命令:
./spark-submit --master spark://node001:7077 --class …jar …
./spark-submit --master spark://node001:7077 --deploy-node client --class …jar …
spark 基于standalone-cluster提交任务
命令:
./spark-submit --master spark://node001:7077 --deploy-node cluster --class …jar …
Spark 基于Yarn-client模式提交任务
命令:
./spark-submit --master yarn --class …jar …
./spark-submit --master yarn --client --class …jar …
./spark-submit --master yarn --deploy-mode client --class …jar …
Spark 基于Yarn-cluster模式提交任务
命令:
./spark-submit --master yarn --class …jar …
./spark-submit --master yarn --cluster --class …jar …
./spark-submit --master yarn --deploy-mode cluster --class …jar …
RDD 的依赖
窄依赖:父RDD与子RDD partition之间的关系是一对一,父RDD与子RDD之间的关系是多对一
宽依赖(shuffle):父RDD与子RDD partition 之间的关系是一对多
Master(standalone):资源管理的主节点(进程)
•Cluster Manager:在集群上获取资源的外部服务(例如standalone,Mesos,Yarn )
•Worker Node(standalone):资源管理的从节点(进程) 或者说管理本机资源的进程
•Application:基于Spark的⽤用户程序,包含了driver程序和运行在集群上的executor程序
•Driver Program:用来连接工作进程(Worker)的程序
•Executor:是在一个worker进程所管理的节点上为某Application启动的⼀一个进程,该进程负责运行任务,并且负责将数据存在内存或者磁盘上。每个应⽤用都有各自独⽴立的executors
•Task:被送到某个executor上的工作单元
•Job:包含很多任务(Task)的并行计算,可以看做和action对应
•Stage:⼀个Job会被拆分很多组任务,每组任务被称为Stage(就像Mapreduce分map task和reduce task一样)
按照宽依赖来递归划分stage,每个stage都有一组并行的task组成
RDD,partition是不存数据的,存的是逻辑
stage的并行度由谁决定?有stage中finalRDD的partition个数决定,
一个stage中的并行的计算逻辑不一定相同的(union)
管道中数据何时落地?1)shuffle write时落地 2) 对RDD持久化时落地
如何提高stage的并行度?shuffle 算子 :reduceByKey(xxx, numpartition), join(xxx, numPartition), distince(
spark的计算模式 Pipeline
spark处理数据的模式:pipeline管道处理模式
f3(f2(f1(textFile))) 1+1+1=3
spark比mapreduce快的原因:
spark的计算结果不会上传到hdfs
如果将mapreduce中的map端也可以按照spark的模式写,其和spark基本是一致的,但是MapReduce会将map的结果放在hdfs上,但是spark是放在本地磁盘上
根本区别是:spark中可以在action算子结束后,可以从内存中读取持久化的数据。mapreduce不行
所有spark在迭代计算(RDD复用,内存持久化)时比MapReduce更高效。在非迭代时相差不是特别大
spark 中task的发送
DAGScheduler 中根据RDD的宽窄依赖来划分stage,然后将stage以Taskset的形式交给 TaskScheduler
TaskScheduler 遍历TaskSet发送Task到 Worker中 的 execute 中去执行,重试3次仍然失败就由DAGscheduler在重新发送Taskset给TaskScheduler (重试4次),如果失败则job失败,则application失败