1.Spark架构
分布式spark应用中的组件
在分布式环境下,Spark集群采用的是主/从结构。在一个Spark集群中,有一个节点负责中央协调,调度各个分布式工作节点。这个中央协调节点被称为驱动器(Driver)节点。与之对应的工作节点被称为执行器(executor)节点。驱动器节点可以和大量的执行器节点进行通信,它们也都作为独立的Java进行运行。驱动器节点和所有的执行器节点一起被称为一个Spark应用(application)。
Spark应用通过一个叫作集群管理器(Cluster Manager)的外部服务在集群中的机器上启。Spark自带的集群管理器被称为独立集群管理器。Spark也能运行在Hadoop YARN和Apache Mesos这两大开源集群管理器上。
驱动器(Driver)节点
Spark驱动器是执行你的程序中的main()方法的进程。它执行用户编写的用来创建SparkContext、创建RDD,以及进行RDD的转化操作和行动操作的代码。其实,当启动Spark shell时,就启动了一个Spark驱动器程序(Spark shell总是会预先加载一个叫作sc的SparkContext对象)。驱动器程序一旦终止,Spark应用也就结束了。
驱动器程序在Spark应用中有下述两个职责。
*把用户程序转为任务
Spark驱动器程序负责把用户程序转为多个物理执行的单元,这些单元也被称为任务(task)。从上层来看,所有的Spark程序都遵循同样的结构:程序从输入数据创建一系列RDD,再使用转化操作派生出新的RDD,最后使用行动操作收集或存储结果RDD中的数据。Spark程序其实是隐式的创建出一个由操作组成的逻辑上的有向无环图(Directed Acyclic,简称DAG)。当驱动器程序运行时,它会把逻辑图转为物理执行计划。
Spark会对逻辑执行计划作一些优化,比如将连续的映射转为流水线化执行,将多个操作合并到一个步骤中等。这样Spark就把逻辑计划转为一系列步骤(stage)。而每个步骤又由多个任务组成。这些任务会被打包并送到集群中。任务是Spark中最小的工作单元,用户程序通常要启动成百上千的独立任务。
*为执行器节点调度任务
有了物理执行计划之后,Spark驱动器程序必须在各执行器进程间协调任务的调度。执行器进程启动后,会向驱动器进程注册自己。因此,驱动器进程始终对应用中所有的执行器节点有完整的记录。每个执行器节点代表一个能够处理任务和存储RDD数据的进程。
Spark驱动器程序会根据当前的执行器节点集合,尝试把所有任务基于数据所在位置分配给合适的执行器进程。当任务执行时,执行器进程会把缓存数据存储起来,而驱动器进程同样会跟踪这些缓存数据的位置,并且利用这些位置信息来调度以后的任务,以尽量减少数据的网络传输。
驱动器程序会将一些Spark应用的运行时的信息通过网页呈现出来,默认在端口4040上,比如,在本地模式下,访问http://localhost:4040就可以看到这个网页了。
执行器节点
Spark执行器节点是一种工作进程,负责在Spark作业中运行任务,任务间相互独立。Spark应用启动时,执行器节点就被同时启动,并且始终伴随着整个Spark应用的生命周期而存在。如果有执行器节点发生了异常或崩溃,Spark应用也可以继续执行。执行器进程有两大作用:第一,它们负责运行组成Spark应用的任务,并将结果返回给驱动器进程;第二,它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供能内存式存储。RDD是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
集群管理器
驱动器节点和执行器节点是如何启动的呢?Spark依赖于集群管理器来启动执行器节点,而在某些特殊情况下,也依赖集群管理器来启动驱动器节点。集群管理器是Spark中的可插拔式组件。这样,除了Spark自带的独立集群管理器,Spark也可以运行在其他外部集群管理器上,比如YARN和Mesos。
Hadoop YARN会启动一个叫作资源管理器(Resource Manager)的主节点守护进程,以及一系列叫作节点管理器(Node Manager)的工作节点守护进程。而在YARN的工作节点上,Spark不仅可以运行执行器进程,还可以运行驱动器进程。
无论使用哪种集群管理器,都可以使用Spark提供的统一脚本spark-submit将应用提交到集群管理器上。通过不同的配置选项,spark-submit可以连接到相应的集群管理器上,并控制应用所使用的资源数量。在使用某些特定集群管理器时,spark-submit也可以将驱动器节点运行在集群内部
小结:在集群上运行Spark应用的详细过程:
1. 通过spark-submit脚本提交应用。
2. spark-submit脚本启动驱动器程序,调用用户定义的main()方法。
3. 驱动器程序与集群管理器通信,申请资源以启动执行器节点。
4. 集群管理器为驱动器程序启动执行器节点。
5. 驱动器进程执行用户应用中的操作。根据程序中所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程。
6. 任务在执行器程序中进行计算并保存结果。
7. 如果驱动器程序的main()方法退出,或者调用了SparkContext.stop(),驱动器程序会终止执行器进程,并且通过集群管理器释放资源。
Spark应用内与应用间调度
在现实中,许多集群是在多个用户间共享的。共享的环境会带来调度方面的挑战:如果两个用户都启动了希望使用整个集群所有资源的应用,该如何处理呢?Spark有一系列调度策略来保障资源不会被过度使用,还允许工作负载设置优先级。
在调度多用户集群时,Spark主要依赖集群管理器来在Spark应用间共享资源。当Spark应用向集群管理器申请执行器节点时,应用收到的执行器节点个数可能比它申请的更多或者更少,这取决于集群的可用性与争用。许多集群管理器支持队列,可以为队列定义不同优先级或容量限制,这样Spark就可以把作业提交到相应的队列中。请查看你所使用的集群管理器的文档获取详细信息。
Spark应用有一种特殊情况,就是那些长期运行(long lived)的应用。这意味着这些应用从不主动退出。Spark SQL中的JDBC服务器就是一个长期运行的Spark应用。当JDBC服务器启动后,它会从集群管理器获得一系列执行器节点,然后就成为用户提交SQL查询的永久入口。由于这个应用本身就是为用户调度工作的,所以它需要一种细粒度的调度机制来强制共享资源。Spark提供了一种用来配置应用内调度策略的机制。Spark内部的公平调度器(Fair Scheduler)会让长期运行的应用定义调度任务的优先级队列。(公平调度器的官方文档:http://spark.apache.org/docs/latest/job-scheduling.html )
2.RDD底层实现原理
RDD是一个分布式数据集,顾名思义,其数据应该分布存储于多台机器上。事实上,每个RDD的数据都以Block的形式存储于多台机器上,下图是Spark的RDD存储架构图。
其中每个Executor会启动一个BlockManagerSlave,并管理一部分Block;而Block的元数据由Driver节点的BlockManagerMaster保存。BlockManagerSlave生成Block后向BlockManagerMaster注册该Block,BlockManagerMaster管理RDD与Block的关系,当RDD不再需要存储的时候,将向BlockManagerSlave发送指令删除相应的Block。
RDD cache的原理
RDD的转换过程中,并不是每个RDD都会存储,如果某个RDD会被重复使用,或者计算其代价很高,那么可以通过显示调用RDD提供的cache()方法,把该RDD存储下来。那RDD的cache是如何实现的呢?
RDD中提供的cache()方法只是简单的把该RDD放到cache列表中。当RDD的iterator被调用时,通过CacheManager把RDD计算出来,并存储到BlockManager中,下次获取该RDD的数据时便可直接通过CacheManager从BlockManager读出。
RDD dependency与DAG
RDD提供了很多转换操作,每个转换操作都会生成新的RDD,这是新的RDD便依赖于原有的RDD,这种RDD之间的依赖关系最终形成了DAG(Directed Acyclic Graph)。
RDD之间的依赖关系分为两种,分别是NarrowDependency与ShuffleDependency,其中ShuffleDependency为子RDD的每个Partition都依赖于父RDD的所有Partition,而NarrowDependency则只依赖一个或部分的Partition。下图的groupBy与join操作是ShuffleDependency,map和union是NarrowDependency。
RDD partitioner与并行度
每个RDD都有Partition属性,它决定了该RDD如何分区,当然Partition的个数还将决定每个Stage的task个数。当前Spark需要应用设置stage的并行Task个数(配置项为:spark.default.parallelism),在未设置的情况下,子RDD会根据父RDD的Partition决定,如map操作下子RDD的Partition与父Partition完全一致,Union操作时子RDD的Partition个数为父Partition个数之和。
关于Spark RDD的三个重要组成部分Dependency、Partition和Partitioner详细底层介绍
Partiton:点击这个链接
Dependency:点击这个链接
Partitioner:点击这个链接