为了账号安全,请及时绑定邮箱和手机立即绑定

Spark 基础(下篇)

标签:
Spark

上篇介绍了spark的突出特点以及基本框架,下面给大家介绍下spark的基本数据结构、spark任务调度的详细流程以及spark中stage的划分。

5. spark的基本数据类型

RDD、DataFrame和DataSet可以说是spark独有的三种基本的数据类型。Spark的核心概念是RDD (resilientdistributed dataset),指的是一个只读的,可分区的分布式数据集,这个数据集的全部或部分可以缓存在内存中,在多次计算间重用。DataFrame是一个以RDD为基础的,但却是一种类似二维数据表的一种分布式数据集。与RDD不同的是,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这样,spark就可以使用sql操作dataframe,像操作数据库中的表一样。目前,spark sql支持大多数的sql数据库的操作。Dataset可以认为是DataFrame的一个特例,主要区别是Dataset每一个record存储的是一个强类型值而不是一个Row。后面版本DataFrame会继承DataSet,DataFrame和DataSet可以相互转化,df.as[ElementType]这样可以把DataFrame转化为DataSet,ds.toDF()这样可以把DataSet转化为DataFrame。创建Dataframe的代码如下所示:

val df = spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdoutdf.show()// +----+-------+// | age|   name|// +----+-------+// |null|Michael|// |  30|   Andy|// |  19| Justin|// +----+-------+

创建Dataset的代码如下所示:

// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,// you can use custom classes that implement the Product interfacecase class Person(name: String, age: Long)// Encoders are created for case classesval caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()// +----+---+// |name|age|// +----+---+// |Andy| 32|// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by nameval path = "examples/src/main/resources/people.json"val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()// +----+-------+// | age|   name|// +----+-------+// |null|Michael|// |  30|   Andy|// |  19| Justin|// +----+-------+

6. spark scheduler(spark任务调度)

(1) 在使用spark-summit提交spark程序后,根据提交时指定(deploy-mode)的位置,创建driver进程,driver进程根据sparkconf中的配置,初始化sparkcontext。Sparkcontext的启动后,创建DAG Scheduler(将DAG图分解成stage)和Task Scheduler(提交和监控task)两个调度模块。
  (2) driver进程根据配置参数向resource manager(资源管理器)申请资源(主要是用来执行的executor),resource manager接到到了Application的注册请求之后,会使用自己的资源调度算法,在spark集群的worker上,通知worker为application启动多个Executor。
  (3) executor创建后,会向resource manager进行资源及状态反馈,以便resource manager对executor进行状态监控,如监控到有失败的executor,则会立即重新创建。
  (4) Executor会向taskScheduler反向注册,以便获取taskScheduler分配的task。
  (5) Driver完成SparkContext初始化,继续执行application程序,当执行到Action时,就会创建Job。并且由DAGScheduler将Job划分多个Stage,每个Stage 由TaskSet 组成,并将TaskSet提交给taskScheduler,taskScheduler把TaskSet中的task依次提交给Executor, Executor在接收到task之后,会使用taskRunner(封装task的线程池)来封装task,然后,从Executor的线程池中取出一个线程来执行task。
   就这样Spark的每个Stage被作为TaskSet提交给Executor执行,每个Task对应一个RDD的partition,执行我们的定义的算子和函数。直到所有操作执行完为止。如下图所示:

webp

图4. Spark 任务调度流程

7. Spark作业调度中stage划分

Spark在接收到提交的作业后,DAGScheduler会根据RDD之间的依赖关系将作业划分成多个stage,DAGSchedule在将划分的stage提交给TASKSchedule,TASKSchedule将每个stage分成多个task,交给executor执行。task的个数等于stage末端的RDD的分区个数。因此对了解stage的划分尤为重要。
  在spark中,RDD之间的依赖关系有两种:一种是窄依赖,一种是宽依赖。窄依赖的描述是:父RDD的分区最多只会被子RDD的一个分区使用。宽依赖是:父RDD的一个分区会被子RDD的多个分区使用。如下图所示:

webp

图5. RDD的两种依赖关系


  上图中,以一竖线作为分界,左边是窄依赖,右边是宽依赖。
  Stage的划分不仅根据RDD的依赖关系,还有一个原则是将依赖链断开,每个stage内部可以并行运行,整个作业按照stage顺序依次执行,最终完成整个Job。

实际划分时,DAGScheduler就是根据DAG图,从图的末端逆向遍历整个依赖链,一般是以一次shuffle为边界来划分的。一般划分stage是从程序执行流程的最后往前划分,遇到宽依赖就断开,遇到窄依赖就将将其加入当前stage中。一个典型的RDD Graph如下图所示:其中实线框是RDD,RDD内的实心矩形是各个分区,实线箭头表示父子分区间依赖关系,虚线框表示stage。针对下图流程首先根据最后一步join(宽依赖)操作来作为划分stage的边界,再往左走,A和B之间有个group by也为宽依赖,也可作为stage划分的边界,所以我们将下图划分为三个stage。

webp

图6. Spark中的Stage划分示例



作者:ZPPenny
链接:https://www.jianshu.com/p/fe54ec82d360


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消