Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行计算框架,Spark拥有Hadoop MapReduce所 具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。Spark是Scala编写,方便快速编程。
都是分布式计算框架,Spark基于内存,MR基于HDFS。Spark处理数据的能力一般是MR的十倍以上,Spark中除了基于内存计算外,还有DAG有向无环图来切分任务的执行先后顺序
概念: RDD弹性分布式数据集
RDD的五大特性
1.RDD是由一系列的partition组成
2.算子是作用在partition上面
3.RDD之间有依赖关系
4.分区器是作用在(K,V)格式的RDD
5.partition对外面提供最佳的计算位置
默认子RDD的分区数(partition)是和父RDD的分区数相同
上图中有四个机器节点,Driver和Worker是启动在节点上的进程,运行在JVM中的进程
创建SparkConf对象:可以设置Application name,可以设置运行模式及资源需求
创建SparkContext对象
基于Spark的上下文创建一个RDD,对RDD进行处理
应用程序中要有Action类算子来触发Transformation类算子执行
关闭Spark上下文对象SparkContext
object WordLines {
def main(args: Array[String]): Unit = {
//local为本地运行模式
val conf = new SparkConf().setMaster("local").setAppName("word")
val sc = SparkSession.builder().config(conf).getOrCreate().sparkContext
val count = sc.longAccumulator("longAccumulator")
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7))
//collect为Aciton算子
rdd.map(line =>{
count.add(1L)
line
}).collect()
println(count.value)
sc.stop()
}
}
需要Action算子触发才能运行
概念:Action类算子也是一类算子(函数)叫做行动算子,如foreach,collect,count等。Transformations类算子是延迟执行,Action类算子是触发执行
。一个application应用程序中有几个Action类算子执行,就有几个job运行
常用Action算子
持久化的单位是partition
。cache和persist都是懒执行的。必须有一个action类算子触发执行
。checkpoint算子不仅能将RDD持久化到磁盘,还能切断RDD之间的依赖关系cache() 默认将数据缓存在内存中,懒执行算子 需要action算子触发
persist() 手动指定持久化级别,懒执行算子 需要action算子触发
persist(StorageLevel.MEMORY_ONLY) = cache()=persist()
注意:
- 1.cache和persist都是懒执行 需要action算子触发
- 2.对于一个RDD cache 或者 persisit之后可以赋值给一个变量 下次使用之歌变量就是使用持久化rdd
- 3.如果赋值给一个变量 那么cache和persist之后不能紧跟Action算子 如 rdd = rdd.persist().collect()
checkpoint() 懒执行算子 需要action算子触发
- 1)将数据存到磁盘
- 2)切断rdd的联系
- 3)persist(StorageLevel.DISK_ONLY)程序运行完,磁盘文件就被清除,而checkpoin不会
cache persist checkpoint 持久化单位是partition
object Cache {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("cache")
val sc = new SparkContext(conf)
//指定checkpoint的磁盘文件存储位置
sc.setCheckpointDir("file:///home/checkpoint")
var rdd: RDD[String] = sc.textFile("")
rdd.checkpoint()
rdd.collect()
rdd = rdd.cache()
rdd = rdd.persist(StorageLevel.DISK_ONLY)
sc.stop()
}
}
wget https://www-us.apache.org/dist/spark/spark-2.3.2/spark-2.3.2-bin-hadoop2.7.tgz
tar -zxvf spark-2.3.2-bin-hadoop2.7.tgz
mv spark-2.3.2-bin-hadoop2.7 spark
cd spark/conf
cp slaves.template slaves
因为我们配置的是单机的,所以可以不用修改,默认就是localhost
cp spark-env.sh.template spark-env.sh
vim spark-env.sh
添加以下内容:
export SCALA_HOME=/home/twl/scala/scala-2.12.6
export JAVA_HOME=/home/twl/jdk
export HADOOP_HOME=/home/twl/app/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export SPARK_HOME=/home/twl/app/spark
export SPARK_MASTER_IP=localhost
export SPARK_EXECUTOR_MEMORY=1G
到此已经可以开始你的表演了(要启动Hadoop环境)
$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi
--master yarn
lib/spark-examples*jar 10
总结:
Yarn-client模式同样是适用于测试,因为Driver运行在本地,Driver会与yarn集群中的Executor进行大量的通信,会造成客户机网卡流量的大量增加
.
$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi
--master yarn
--deploy-mode cluster
lib/spark-examples*jar 10
总结
Yarn-Cluster主要用于生产环境中,因为Driver运行在Yarn集群中某一台nodeManager中,每次提交任务的Driver所在的机器都是随机的,不会产生某一台机器网卡流量激增的现象,缺点是任务提交后不能看到日志。只能通过yarn查看日志
1.为当前的Application申请资源
2.给NameNode发送消息启动Excutor
3.任务调度
ApplicationMaster详解
RDD之间有一系列的依赖关系,依赖关系又分为窄依赖和宽依赖
概念: 父RDD和子RDD partition之间的关系是
一对一
的。或者父RDD一个partition只对应一个子RDD的partition情况下的父RDD和子RDD partition关系是多对一
的。不会有shuffle的产生
父RDD与子RDD partition之间的关系是
一对多
。会有shuffle
的产生
Spark任务会根据RDD之间的依赖关系,形成一个
DAG有向无环图
,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,
每个stage包含一个或多个task任务
。然后将这些task以taskSet的形式提交给TaskScheduler运行
pipeline管道计算模式,pipeline只是一种计算思想,模式
每个task相当于执行了一个高阶函数,f3(f2(f1(sc.textFile("…")))),
数据是一条一条地读取
,读取一条就落地,而mapreduce是读完再落地
Mapreduce相当于是: 1+1=2 ;2+1=3
Spark是 1+1+1 = 3
数据一直在管道里面什么时候数据会落地?
- 1.对RDD进行持久化
- 2.shuffle write的时候
Stage的task
并行度
是由stage的最后一个RDD的分区数
来决定的
如何改变RDD的分区数?
例如:reduceByKey(XXX,3),GroupByKey(4)
测试验证pipeline计算模式
val conf = new SparkConf()
conf.setMaster("local").setAppName("pipeline");
val sc = new SparkContext(conf)
val rdd = sc.parallelize(Array(1,2,3,4))
val rdd1 = rdd.map { x => {
println("map--------"+x)
x
}}
val rdd2 = rdd1.filter { x => {
println("fliter********"+x)
true
} }
rdd2.collect()
sc.stop()
可以看出数据是一条一条被读取再落地
TaskScheduler不仅能重试失败的task,还会重试straggling(落后,缓慢)task(也就是执行速度比其他task慢太多的task)
如果有运行缓慢的task那么TaskScheduler会启动一个新的task来与这个运行缓慢的task执行相同的处理逻辑。两个task哪个先执行完,就以哪个task的执行结果为准。这就是Spark的推测执行机制。在Spark中推测执行默认是关闭的。推测执行可以通过spark.speculation属性来配置
注意:
- 对于ETL类型要入数据库的业务要关闭推测执行机制,这样就不会有重复的数据入库。
- 如果遇到数据倾斜的情况,开启推测执行则有可能导致一直会有task重新启动处理相同的逻辑,任务可能一直处于处理不完的状态
在Application执行之前,将所有的资源申请完毕,当资源申请成功后,才会进行任务的调度,当所有的task执行完成后,才会释放这部分资源
优点: 在Application执行之前,所有的资源都申请完毕,每一个task直接使用资源就可以了,不需要task在执行前自己去申请资源,task启动就快了,task执行快了,stage执行就快了,job就快了,application执行就快了
缺点:直到最后一个task执行完成才会释放资源,集群的资源无法充分利用
Application执行之前不需要先去申请资源,而是直接执行,让job中的每一个task在执行前自己去申请资源,task执行完成就释放资源
val conf = new SparkConf()
conf.setMaster("local").setAppName("brocast")
val sc = new SparkContext(conf)
val list = List("hello xasxt")
val broadCast = sc.broadcast(list)
val lineRDD = sc.textFile("./words.txt")
lineRDD.filter { x => broadCast.value.contains(x) }.foreach { println}
sc.stop()
val conf = new SparkConf().setMaster("local").setAppName("word")
val sc = SparkSession.builder().config(conf).getOrCreate().sparkContext
val count = sc.longAccumulator("longAccumulator")
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7))
rdd.map(line =>{
count.add(1L)
line
}).collect()
println(count.value)
sc.stop()
Hash Shuffle是Spark 1.2之前的默认Shuffle实现,并在Spark 2.0版本中被移除,因此,了解Hash Shuffle的意义更多的在于和Sort Shuffle对比,以及理解为什么Sort Shuffle能够完全取代Hash Shuffle
Shuffle就是在整个shuffle过程中,往往伴随着大量的磁盘和网络I/O。所以shuffle性能的高低也直接决定了整个程序的性能高低对数据进行重组
执行流程(Spark中时不存在map reduce,只是流程和Hadoop相似,以下出现是为了形象解释而以
)
32K
。buffer起到数据缓存的作用产生的磁盘小文件的个数: M(map task的个数)*R(reduce task的个数)
存在的问题
每一个Executor进程根据核数,决定Task的并发数量,比如executor核数是2,就是可以并发运行两个task,如果是一个则只能运行一个task
假设executor核数是1,ShuffleMapTask数量是M,那么它依然会根据ResultTask的数量R,创建R个bucket缓存,然后对key进行hash,数据进入不同的bucket中,每一个bucket对应着一个block file,用于刷新bucket缓存里的数据
产生磁盘小文件的个数:C(core的个数)*R(reduce的个数)
产生磁盘小文件的个数: 2*M(map task的个数)
bypass运行机制的触发条件如下:
shuffle reduce task的数量小于spark.shuffle.sort.bypassMergeThreshold的参数值。这个值默认是200
产生的磁盘小文件为:2*M(map task的个数)
Spark执行应用程序时,Spark集群会启动Driver和Executor两种JVM进程,Driver负责创建SparkContext上下文,提交任务,task的分发等。Executor负责task的计算任务,并将结果返回给Driver。同时需要为需要持久化的RDD提供储存。Driver端的内存管理比较简单,这里所说的Spark内存管理针对Executor端的内存管理
。
Spark内存管理分为静态内存管理和统一内存管理,Spark1.6之前使用的是静态内存管理,Spark1.6之后引入了统一内存管理;可以通过参数spark.memory.useLegacyMode 设置为true(默认为false)使用静态内存管理
存储内存、执行内存和其他内存的大小在 Spark 应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置
与静态内存管理的区别在于储存内存和执行内存共享同一块空间,可以互相借用对方的空间
reduce 中OOM如何处理?