Spark原理基础笔记

Spark原理基础笔记_第1张图片
spark core承上启下.png

Spark的概念

https://www.cnblogs.com/wzj4858/p/8204411.html

核心基本概念

  • RDD(Resilient Distributed Dataset)弹性分布式数据集
    为了减少网络及磁盘 IO 开销,需要设计出一种新的容错方式,于是诞生了新的数据结构 RDD。Spark API 的所有操作都是基于 RDD 的;弹性表明数据丢失时,可以进行重建;分布式表名每个RDD被分为多个分区,运行在不同节点上。RDD 是一种只读 的数据块,只读表示当你对一个 RDD 进行Operation,那么会产生一个新的 RDD。RDD可以包含Python/Java/Scala中任意类型对象,甚至用户自定义对象。

    Spark 1.5版以后,新增了数据结构 Spark-DataFrame,仿造的 R 和 python 的类 SQL 结构-DataFrame, 底层为 RDD, 能够让数据从业人员更好的操作 RDD。

RDD的创建方式主要有两种:

  1. 并行化(Parallelizing)一个已经存在与驱动程序(Driver Program)中的集合如set、list;
  2. 读取外部存储系统上的一个数据集,比如HDFS、Hive、HBase,或者任何提供了Hadoop InputFormat的数据源.也可以从本地读取 txt、csv 等数据集
# 创建RDD
lines = sc.textFile("readme.md")
pythonLines = lines.filter(lambda line: "Python" in line) # 转换transformation操作
pythonLines.first() # 行动action操作
  • DAG(Directed Acyclic Graph)有向无环图
    RDD 里面的数据并不是真实的数据,而是一些元数据信息,记录了该 RDD 是通过哪些 Transformation 得到的,在计算机中使用 lineage 来表示这种血缘结构,lineage 形成一个有向无环图 DAG, 整个计算过程中,将不需要将中间结果落地到 HDFS 进行容错,加入某个节点出错,则只需要通过 lineage 关系重新计算即可

集群角色概念

  • Driver
    驱动。运行Application的main()函数并且创建SparkContext;
  • Operation
    作用于RDD的各种操作分为Transformation(转换)和Action(动作)。转换操作是"Lazy"(惰性)的,只有Action才会触发真正的计算。
  • Executor
    运行在Worker Node上的一个进程,负责运行Task,并将数据存在内存或者磁盘上,每个Application都有各自独立的1-n个Executors;
  • Cluster Manager
    集群(资源)管理模式(程序)(Local、Standalone、Mesos/Yarn等集群管理系统);

执行阶段概念

  • Application
    每个Spark作业其实都是一个 Application,每个 Application 对应多个 Jobs。
  • Job
    作业,一个 action 操作(比如 collect)触发一个 job。Job由任意多阶段stages有向无环图DAG构成,每个阶段类似map或reduce。阶段被Spark运行环境分解为多个任务task,任务并行运行在分布于集群中的RDD分区上,类似MR中的任务。Spark作业始终运行在应用application上下文SparkContext中,它提供RDD分组与共享变量。
  • Stage
    每个 job 拆成多个 stage(发生 shuffle 的时候会拆分出一个 stage,比如reduceByKey 处会发生 shuffle)。
  • Task
    A unit of work within a stage, corresponding to one RDD partition。即 stage 下的一个任务执行单元,一般来说,一个 rdd 有多少个 partition,就会有多少个 task,因为每一个 task 只是处理一个 partition 上的数据。

SparkConf与SparkContext

使用Spark Shell的时候,本身是预配置了sc,即SparkConf和SparkContext的

% spark-shell
Spark context available as sc.
scala >

但是在生产任务编写中是需要设置这些配置的。

# python
from pyspark import SparkConf, SparkContext
conf = SparkConf.setMaster("local").setAppName("My App")
sc = SparkContext(conf = conf)
sc.stop() # 关闭Spark

创建SparkContext只需要两个参数:

  • 集群URL,这里使用本地
  • 应用名:方便在集群管理器找到自己的应用

spark-submit与spark-shell

spark-shell是对spark-submit的封装,是交互式的客户端。
它很大程度上基于Scala REPL(Scala 交互式shell,即Scala解释器),并继承了Scala REPL(读取-求值-打印-循环)(Read-Evaluate-Print-Loop)的所有功能。其对多种语言提供了接口(位于spark 的 bin 目录下):

  1. Spark-shell: 提供Scala的交互,启动了 Spark 的 scala 解释器.
  2. PySpark: 提供Python的交互,启动了 Spark 的 python 解释器.
  3. sparkR: 提供R的交互,启动了 Spark 的 R 解释器.
  4. Spark-submit/Spark2-submit: 提供任务提交,包括Scala/Java的包,

spark-shell调用顺序: SparkSbumit -> repl.Main -> SparkILoop
https://www.bbsmax.com/A/VGzlW79Vdb/

Spark集群管理器

https://www.jianshu.com/p/b4d3db386925

# WordCount程序
object Wordcount {
  def main(args:Array[String]): Unit ={
   val conf = new SparkConf().setAppName("WordCount")
   val sc = new SparkContext(conf)
   val inputpath = args(0)
   val lines = sc.textFile(inputpath)
   val words = lines.flatMap(line=>line.split(" "))
   words.count()
   val wordcounts = words.map(word=>(word,1)).reduceByKey(_+_)
   wordcounts.saveAsTextFile(args(1))
 } 
}

Local

本机启动n个线程,通常用于测试程序正确性

Standalone

Spark自带资源管理,可独立部署。

# client(默认)
bin/spark-submit \
--class com.spark.Wordcount  \
--master spark://master:7077  \
--executor-memory 1G  \
wordcount.jar \
hdfs://master:8020/user/README.txt \
hdfs://master:8020/user/wordcount1
# cluster
添加
--deploy-mode cluster \

Spark on Yarn

https://blog.csdn.net/sundujing/article/details/51417800

  • Cluster
    driver运行在yarn集群下的某台机器上(ApplicationMaster进程中)
# cluster
bin/spark-submit \
--class com.spark.Wordcount \
--master spark://master:7077 \
--executor-memory 1G \
--deploy-mode cluster \
wordcount.jar \
hdfs://master:8020/user/README.txt \
hdfs://master:8020/user/wordcount2
  • Client
    driver运行在提交机机器上(启动后的executor并不会向AM进程注册,而是向driver注册)好处:方便在终端看到运行过程,利于调试
Spark原理基础笔记_第2张图片
image.png
  • 小结:两种主要集群管理器角色的对应关系
    Yarn的结构:
        ResourceManager: 负责集群资源的管理
        NodeManager:负责当前机器的资源管理
    Standalone的结构:
        Master: 负责集群资源管理
        Worker: 负责当前机器的资源管理

任务提交过程

https://zhuanlan.zhihu.com/p/34436165

  • spark-submit提交一个Spark作业
    这个作业就会启动一个对应的Driver进程。根据使用的部署模式(deploy-mode)不同,Driver进程在不同位置启动。
  • Driver向集群管理器申请资源——Executor进程。
    集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个都占有一定数量的内存和CPU core


    Spark原理基础笔记_第3张图片
    图源https://www.cnblogs.com/charlotte77/p/5468968.html
  • 开始调度执行
    Driver进程将Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批Task,将这些Task分配到各个Executor进程中执行。
    Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个Task处理的数据不同而已。
    一个stage的所有Task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。
    下一个stage的Task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据得到结果。
      Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个Task可能都会从上一个stage的Task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。
      当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个Task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。
      因此Executor的内存主要分为三块:第一块是让Task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让Task通过shuffle过程拉取了上一个stage的Task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。
      Task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个Task,都是以每个Task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的Task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些Task线程。

Shuffle与Stage,宽依赖和窄依赖

shuffle 是划分 DAG 中 stage 的标识,同时影响 Spark 执行速度的关键步骤
 
RDD 的 Transformation 函数中,又分为窄依赖(narrow dependency)和宽依赖(wide dependency)的操作.窄依赖跟宽依赖的区别是是否发生 shuffle(洗牌) 操作.

宽依赖会发生 shuffle 操作. 子 RDD 的各个分片会依赖于父RDD 的多个分片,所以会造成父 RDD 的各个分片在集群中重新分片.

窄依赖是子 RDD的各个分片(partition)不依赖于其他分片,能够独立计算得到结果.

// Map: "cat" -> c, cat
val rdd1 = rdd.Map(x => (x.charAt(0), x))
// groupby same key and count
val rdd2 = rdd1.groupBy(x => x._1).
                Map(x => (x._1, x._2.toList.length))

第一个 Map 操作将 RDD 里的各个元素进行映射, RDD 的各个数据元素之间不存在依赖,可以在集群的各个内存中独立计算,也就是并行化,第二个 groupby 之后的 Map 操作,为了计算相同 key 下的元素个数,需要把相同 key 的元素聚集到同一个 partition 下,所以造成了数据在内存中的重新分布,即 shuffle 操作.shuffle 操作是 spark 中最耗时的操作,应尽量避免不必要的 shuffle.

宽依赖主要有两个过程: shuffle write 和 shuffle fetch. 类似 Hadoop 的 Map 和 Reduce 阶段.shuffle write 将 ShuffleMapTask 任务产生的中间结果缓存到内存中, shuffle fetch 获得 ShuffleMapTask 缓存的中间结果进行 ShuffleReduceTask 计算,这个过程容易造成OutOfMemory.

并行度

https://blog.csdn.net/leen0304/article/details/78674073

常见问题(待理解)

Apache Spark设计与实现
https://www.kancloud.cn/kancloud/spark-internals/45243

  • MR、Spark为何启动了很多任务,如何限制为20个?

  • spark为什么就算不在内存跑也比mr快

  • 如何优化spark任务

  • 用户代理设置?
    https://www.jianshu.com/p/a037c1855718

  • SparkPI

  • 什么情况下spark比MR慢

其他需要了解的内容

https://zhuanlan.zhihu.com/p/58521037

你可能感兴趣的:(Spark原理基础笔记)