Spark-基本框架和基本原理

本文详细介绍了Apache Spark的执行模式,包括local、Yarn、Standalone和Mesos,以及cluster和client模式的区别。重点讲解了Spark作业的运行流程,如DAG、Stage、TaskSet和Task的定义与作用,强调了数据本地化和计算优化策略。此外,还对比了Spark与MapReduce的优化点,并提供了一个简单的Spark样例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

框架

在这里插入图片描述

执行模式

Spark 的执行模式有 local、Yarn、Standalone、Mesos 四类。后面三个分别有 cluster 和 client 二种。client 和 cluster 的区别就是指 Driver 是在程序提交客户端还是在集群的 AM 上。 比如常见的 Yarn-cluster 模式如下图所示:
在这里插入图片描述
驱动(Driver): 在驱动程序中,通过SparkContext主导应用的执行,SparkContext可以连接不同类型的Cluster Manager(Standalone、YARN、Mesos),连接后,获得集群节点上的Executor
Executor: 一个Worker节点默认一个Executor,某一个Application应用程序运行在worker节点上的一个进程。每个应用获取自己的Executor, 每个Executor进程都占有一定数量的内存和CPU core
Job: 包含多个Task组成的并行计算,往往由Spark Action触发生成,一个Application包含多个Job。 当你需要执行一个 rdd 的 action 的时候,会生成一个 job。
Stage: 每个Job会被拆分成多组Task,作为一个TaskSet, 一个stage是一个taskset。为什么需要划分Stage,数据本地化,移动计算,而不是移动数据,保证一个Stage内不会发生数据移动
Task: 被送到Executor上的工作单元。task数由分区数决定,当Spark读取这些文件作为输入时,会根据具体数据格式对应的InputFormat进行解析,一般是将若干个Block合并成一个输入分片,称为InputSplit,注意InputSplit不能跨越文件。随后将为这些输入分片生成具体的Task。InputSplit与Task是一一对应的关系。

Spark比MR优化的地方

1、RDD提供了很多转换和动作,实现很多操作,比如join,sort,而mr只提供map和reduce
2、分区相同的转换会放在一个Task 中以流水线方式执行,只有不同的分区转换才shuffle,计算移动,数据不移动。而mr的reduceTask必须等所有map执行完,才能执行。
3、通过内存缓冲数据,大大提高迭代式计算性能,减少多次计算之间中间结果写到 Hdfs 的开销;而mr的reduce和下一个map直接数据结果只能存储在hdfs上面
4、一个Job包含多个RDD转换操作,只需要在调度的时候生成多个stage,而mr只有两个阶段。

Spark的运行原理

DAG:有向无环图

Directed Acycle graph,反应RDD之间的依赖关系
在这里插入图片描述

窄依赖

父RDD每一个分区最多被一个子RDD的分区所用,也及时父RDD和子RDD的关系是1对1
在这里插入图片描述

宽依赖

父RDD的每个分区都可能被多个子RDD分区所使用,子RDD分区通常对应所有的父RDD分区
在这里插入图片描述
常见的窄依赖有:map、filter、union、mapPartitions、mapValues
常见的宽依赖有groupByKey、partitionBy、reduceByKey、join

TaskSet:任务集

由一组关联的,但相互之间没有Shuffle依赖关系的任务所组成的任务集
在这里插入图片描述

Stage:调度阶段

一个任务集对应的调度阶段;每个Job会被拆分很多组Task,每组任务被称为Stage,也可称TaskSet,一个作业分为多个阶段;Stage分成两种类型ShuffleMapStage、ResultStage。
在这里插入图片描述

计算流程

在这里插入图片描述

我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。而Driver进程要做的第一件事情,就是向集群管理器申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。
  在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。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线程。

Spark 样例

pom.xml

<properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <spark.version>2.1.0</spark.version>
      <scala.version>2.11.12</scala.version>
  </properties>

  <dependencies>
    <!-- Spark-core -->
    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-core_2.11</artifactId>
      <version>${spark.version}</version>
    </dependency>
    <!-- SparkSQL -->
    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.11</artifactId>
      <version>${spark.version}</version>
    </dependency>
    <!-- SparkSQL  ON  Hive-->
    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-hive_2.11</artifactId>
      <version>${spark.version}</version>
    </dependency>
    <!--mysql依赖的jar包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <!-- Scala 包-->
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>${scala.version}</version>
    </dependency>
  </dependencies>   

读取文本文件,取前10个打印输出

object example{

  def main(args: Array[String]): Unit = {
    var input = "./data/log3"
    val spark: SparkSession = SparkSession.builder()
      .appName("spark_job")
      .getOrCreate()
    val logRDD: RDD[String] = spark.read.textFile(input).rdd
    logRDD.take(10).foreach(println(_));
    }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值