1、什么是Spark?
Spark是一个用来实现快速而通用的集群计算的平台。
1.1、特点
能在内存中进行计算。
1.2、Spark Core
包含任务调度、内存管理、错误恢复、与存储系统交互等模块。
1.3、Spark SQL
通过Spark SQL,我们可以使用SQL或者Hive版本的SQL(HQL)来查询数据,支持多种数据源,比如Hive表、Parquet、JSON等。
1.4、Spark Streaming
对实时数据进行流式计算的组件,例如服务器日志、消息队列等数据流。
1.5、集群管理器
Spark设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算,Spark支持在各种集群管理(cluster manager)上运行,例如Hadoop YARN、Apache Mesos、Spark自带的独立调度器。
1.6、核心概念
驱动器程序来发起集群伤的各种并行操作,驱动器程序通过一个SparkContext对象来访问Spark,这个对象代表对计算集群的一个连接,驱动器程序一般要管理多个执行器(executor)节点。
1.6、代码示例
2、RDD
弹性分布式数据集,表示分布在多个计算节点上可以并行操作的元素集合,是Spark主要的编程抽象。
每个RDD都被分为多个分区,这些分区运行在集群的不同节点。
2.1、创建RDD
Spark提供两种创建RDD的方式:读取外部数据集,在驱动器程序中对一个集合进行并行化。
2.2、RDD操作
RDD支持两种类型操作:转化操作(transformation)和行动操作(action)。
转化操作:由一个RDD生成一个新的RDD,Spark会使用谱系图累记录这些不同的RDD直接的依赖关系。
行动操作:由于行动操作需要生成实际的输出,他们会强制执行那些求值必须用到的RDD的转化操作。
区别:
1:Spark计算RDD的方式不同,虽然可以在任何时候定义新的RDD,但Spark只会惰性计算这些RDD。只有第一次在一个行动操作中用到时,才会真正计算。
2:转化操作返回的是RDD,而行动操作返回的是其他的数据类型。
惰性求值:和转化操作一样的是,读取数据的操作也有可能是会多次执行。虽然转化操作是惰性求值的,但还是可以随时通过运行一个行动操作来强制Spark执行RDD的转化操作,这事一种对你所写的程序进行部分测试的简单方法。
2.3、向Spark传递函数
Spark的大部分转化操作和一部分行动操作,都需要依赖用户传递的函数来计算。
2.4、不同RDD类型间转换
有些函数只能用于特定类型的RDD,比如mean()和variance()。Java中有两个专门的类JavaDoubleRDD和javaPairRDD,来处理特殊类型的RDD。
2.5、持久化(缓存)
当我们让Spark持久化存储一个RDD时,计算出RDD的节点会分别保存他们所求出的分区数据。默认情况下persist()会把数据以序列化的形式缓存在JVM的对空间中。
最后,RDD还有一个方法叫作unpersist(),调用该方法可以手动把持久化的RDD从缓存中移除。
3、键值对操作
键值对RDD通常用来进行聚合计算。要先通过初始ETL(抽取、转化、装载)操作来将数据转化为键值对形式。
控制键值对RDD在各节点上分布情况的高级特性:分区。分布式数据集选择正确的分区方式和为本地数据集选择合适的数据结构很相似——在这两种情况下,数据的分布都会及其明显地影响程序的性能表现。
3.1、创建PairRDD
Java没有自带的二元组类型,因此Spark的Java API让用户使用scala.Tuple2类来创建二元组。new Tuple2(elem1,elem2)创建,._1()和._2()方法访问其元素。
使用Java从内存数据集创建pairRDD需要使用SparkContext.parallelizePairs()。
3.2、PairRDD转化操作
Pair RDD也还是RDD(元素为Tuple2对象),同样支持RDD所支持的函数。
3.2.1聚合操作
并行度调优:
1:每个RDD都有固定数目的分区,分区数决定了在RDD上执行操作时的并行度。
2:在执行聚合或分组操作时,可以要求Spark使用给定的分区数,Spark始终尝试根据集群的大小推断出一个有意义的默认值。
3:本节的大多数操作福都能接收第二个参数,用来制定分组结果或聚合结果的RDD分区数。
3.2.2分组操作
groupByKey()、cogroup()
3.2.3连接
连接的方式多种多样:右外连接、左外连接、交叉连接、内连接。
3.2.4数据排序
当把数据排好序后,后续对数据进行collect()或save()等操作都会得到有序的数据。
sortByKey()函数接受一个叫做ascending的参数,表示我们是否想要让结果按生序排序(默认值为true)
3.3、PairRDD的行动操作
所有基础RDD支持的传统行动操作也都在pair RDD上可用。
3.4、数据分区
对数据集在节点间的分区进行控制。和单节点的程序需要为记录集合选择合适的数据结构一样,Spark程序可以通过控制RDD分区方式来减少通信开销。
对userData表使用partitionBy()转化操作,将这张表转为哈希分区。可以通过向partitionBy传递一个spark.HashPartitioner对象来实现该操作。partitionBy()是一个转化操作,因此它的返回值总是一个新的RDD,但他不会改变原来的RDD。RDD一旦创建就无法修改。因此应该对partitionBy()的结果进行持久化,并保存为userData。
如果没有将partitionBy()转化操作的结果持久化,那么后面每次用到这个RDD时都会重复地对数据进行分区操作。不进行持久化会导致整个RDD谱系图重新求值。那样的话,partitionBy()带来的好处就会被抵消,导致重复对数据进行分区以及跨节点的混洗,和没有制定分区方式时放生的情况十分相似。
3.3.1获取RDD分区方式
可以使用RDD的partitioner()方法来获取RDD的分区方式。返回一个scala.Option对象,这是Scala中用来存放可能存在的对象的容器类。可以调用isDefined方法来检查其中是否指,调用get方法获取其中的值。值为spark.Partitioner对象。
3.3.2从分区中获益的操作
Spark的许多操作都引入了将数据根据键跨节点进行混洗的过程,所有这些操作都会从数据分区中获益。cogroup()、groupWhit()、join()、leftOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combineByKey()、lookup()。
reduceByKey()这样只作用于单个RDD的操作,运行在为分区的RDD上的时候会导致每个键的所有对应值都在每台及其上进行本地计算,只需要把本地最终归约出的结果值从各工作节点传回主节点,所有原本的网络开销就不算大。
3.3.3影响分区方式的操作
由于传给map()的函数理论上可以改变元素的键,因此结果就不会有固定的分区方式。
对于二元操作,输出数据的分区方式取决于父RDD的分区方式。默认情况下,结果会采用哈希分区,分区的数量和操作的的并行度一样。不过,如果其中的一个父RDD已经设置过分区方式,那么结果就会采用那种分区方式;如果两个父RDD都设置过分区方式,结果RDD会采用第一个父RDD的分区方式。
PageRank使用中从RDD分区中获益的更复杂算法。
为了最大化分区相关优化的潜在作用,你应该在无需改变元素的键时尽量使用mapValues()或flatMapValues()。
3.3.4自定义分区方式
Spark提供HashPartitioner与RangePartitioner。
Spark还是允许你通过提供一个自定义的Partitioner对象来控制RDD的分区方式。
要实现自定义的分区器,你需要继承org.apache.spark.Partitioner类并实现三个方式。
numPartitions:Int:返回创建出来的分区数;
getPartition(key:Any):Int:返回给定键的分区编号(0到numPartitions-1);
equals():这个方法的实现非常重要,Spark需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样Spark才可以判断两个RDD的分区方式是否相同;
有一个问题需要注意:当你的算法依赖与Java的hashCode()方法时,这个方法有可能会返回负数。你需要十分谨慎,确保getPartition()永远返回一个非负数。
使用自定义的Partitioner是很容易的:只要把它传给partitionBy()方法即可。
4、数据读取与保存
Spark支持很多种输入输出源,Spark可以通过Hadoop MapReduce所使用的InputFormat和OutputFormat接口访问数据。
三种常见数据源:
1:文件格式与文件系统:文本文件、JSON、SequenceFile、protocol buffer(PB)
2:Spark SQL中的结构化数据源:JSON、Apache Hive
3:数据库与键值存储:Cassandra、Hbase、ES、JDBC源
4.1、文件格式
Spark会根据文件扩展名选择对应的处理方式。这一过程是封装好的,对用户透明。
4.1.1、文本文件
将一个文本文件读取为RDD时,输入的每一行都会成为RDD的一个元素。也可以将多个完成的文本文件一次性读取为一个pair RDD,其中键是文件名,值是文件内容。
读取文本文件:RDD = SparkContext.textFile()、pairRDD = SparkContext.wholeTextFile()
Spark支持读取给定目录中的所有文件,以及在输入路径中使用通配字符(part-*.txt)
保存文本文件:RDD.saveAsTextFile(outputFile)
saveAsTextFile()方法接收一个路径,并将RDD中的内容都输入到路径对应的文件中。
4.1.2、JSON
JSON是一种使用较广的半结构化数据格式。
读取JSON:将数据作为文本文件读取,然后对JSON数据进行解析,这种方法假设文件中的每一行都是一个JSON,如果跨行,读入整个文件,对文件解析,可以使用mapPartitions()重用解析器。
tips:如果选择跳过格式不正确的数据,应该尝试使用累加器来跟踪错误的个数。
4.1.3、逗号分隔值与制表符分隔值
每行都有固定数目的字段,每条记录都没有相关联的字段名,只能得到对应的序号,常规做法是使用第一行中每列的值作为字段名。
逗号分隔值文件:CSV,字段间用逗号分隔。
制表符分隔值文件:TSV,字段间用制表符分隔。
tips:Hadoop InputFormat中的CSVInputFormat也可以用于在Java中读取CSV数据。不过它不支持包含换行符的记录。
4.1.4、SequenceFile
SequenceFile是由没有相对关系结构的键值对文件组成的重用Hadoop格式。SequenceFile文件有同步标记,Spark可以用它来定位到文件中的某个点,然后再与记录的边界对齐。折可以让Spark使用多个节点高效地并行读取SequenceFile文件。
由于Hadoop使用了一套自定义的序列化框架,因此SequenceFile是有实现Hadoop的Writable结偶的元素组成,可以通过重载org.apache.hadoop.io.Writable中的readfields和write来实现自己的Writable类。
tips:整型和长整型通常存储为定长的形式。存储数字12占据的空间和存储数字2**30占据的一样。如果你有大量的小数据,你应该使用可变长的类型VIntWritable和VLongWritable,他们可以在存储较小数值时使用更少位。模版类型也必须使用Writable类型。
读取SequenceFile:SparkContext.sequenceFile(path,keyClass,valueClass)
保存SequenceFile:RDD.saveAsSequenceFile(outputFile)
4.1.5、对象文件
对象文件看起来就像是对SequenceFile的简单封装,它允许存储只包含值的RDD。和SequenceFile不一样的是,对象文件是使用Java序列化写出的。
tips:如果你修改了你的类——比如增减了几个字段——已经生成的对象文件就不再可读了。对象文件使用Java序列化,它对兼容同一个类的不同版本有一定程度的支持,但是需要程序员去实现。Java序列化有可能相当慢。
4.1.6、非文件系统数据源
许多像Hbase和MongoDB这样的键值对存储都提供了用来直接读取Hadoop输入格式的接口。hadoopDataset()这一组函数只接收一个Configuration对象,这个对象用来设置访问数据源必须的Hadoop属性。
Protocol buffer(PB)比起XML,PB能在同样的空间内存储大约3到10倍的数据,同时编解码速度大约为XML的20至100倍。PB采用一致化编码。
4.2、文件压缩
在大数据工作中,我们经常需要对数据进行压缩以节省存储空间和网络传输开销。
对于像Spark这样的分布式系统,我们通常会尝试从多个不同及其上一起读入数据。要实现这种情况,每个工作节点都必须能够找到一条新记录的开端。有些压缩格式会使折变得不可能,而必须要单个节点来读入所有数据,这就很容易产生性能瓶颈。
尽管Spark的textFile()方法可以处理压缩过的输入,但即使输入数据被以可分割读取的方式压缩,Spark也不会打开splittable。因此,如果你要读取单个压缩过的输入,最好不要考虑使用Spark的封装,而是使用newAPIHadoopFile或者hadoopFile,并制定正确的压缩编解码器。
有些输入格式(例如SequenceFile)允许我们只压缩键值对数据中的值,这在查询时很有用。
4.3、文件系统
本地文件系统:Spark支持从本地文件系统中读取文件,不过它要求文件在集群中所有节点的相同路径下都可以找到。
HDFS:Hadoop分布式文件系统是一种广泛使用的文件系统,有弹性的应对节点失败,同时提供高吞吐量。Spark和HDFS可以部署在同一批机器上,这样Spark可以利用数据分布来尽量避免一些网络开销。
4.4、Spark SQL中的结构化数据
结构化数据指的是有结构信息的数据——也就是所有的数据记录都具有一致字段结构的集合。Spark SQL支持多种结构化数据源作为输入,而且由于Spark SQL知道数据的结构信息,它还可以从这些数据源中只读取所需字段。
我们把一条SQL查询给Spark SQL,让它对一个数据源执行查询,然后得到有Row对象组成的RDD,每个Row对象表示一条记录。在Java中,Row对象的访问是基于下标的。每个Row都有一个get()方法(例如getInt()、getString())。
4.4.1、Apache Hive
Apache Hive是Hadoop上的一种常见的结构化数据源。Hive可以在HDFS内或者在其他存储系统上存储多种格式的表。这些格式从普通文本到列式存储式,应有尽有。Spark SQL可以读取Hive支持的任何表。
要把Spark SQL连接到已有的Hive上,你需要提供Hive的配置文件。你需要将hive-site.xml文件复制到Spark的./conf/目录下。创建出HiveContext对象,也就是Spark SQL的入口。
4.4.2、JSON
要读取JSON数据,首先需要和使用Hive一样创建一个HiveContext,然后使用HiveContext.jsonFile方法来从整个文件中获取由Row对象组成的RDD。也可以将RDD注册为一张表(RDD.registerTempTable(“表名”)),然后从中选出特定的字段。
4.5、数据库
通过数据库提供的Hadoop连接器或者自定义的Spark连接器,Spark可以访问一些常用的数据库系统。
4.5.1、Java数据库连接
Spark可以从任何支持Java数据库连接(JDBC)的关系型数据库中读取数据,包括MySql。
JdbcRDD接收参数:
首先:要提供一个用于对数据库创建连接的函数。这个函数让每个节点在连接必要的配置后创建自己读取数据的连接。
接下来:要提供一个可以读取一定范围内数据的查询,以及查询中的条键值。
最后:这个函数的最后一个参数是一个可以将输出结果从java.sql.ResultSet转为对操作数据有用的格式的函数。
4.5.2、Hbase
由于org.apache.hadoop.hbase.mapreduce.TableInputFormat类的实现,Spark可以通过Hadoop输入格式访问HBase。这个输入格式会返回键值对数据。其中键的类型为org.apache.hadoop.hbase.io.ImmutableBytesWritable,而值的类型为org.apache.hadoop.hbase.client.Result。Result类包含多种根据列获取值的方法。
4.5.3、Elasticsearch
Spark可以使用Elasticsearch-Hadoop从Elasticsearch中读取数据。Elasticsearch是基于Lucene的搜索系统。
5、Spark SQL
Spark SQL:Spark用来操作结构化和把结构化数据的接口。当数据符合这样的条件时,Spark SQL就会使得针对这些数据的读取和查询变得更加简单高效。
结构化数据:任何有结构信息的数据。
结构信息:每条记录共用的已知的字段集合。
1:Spark SQL可以从各种结构化数据源(JSON、Hive、Parquet等)中读取数据。
2:Spark支持在Spark程序内使用SQL语句进行数据查询,也支持从外部工具中通过标准数据库连接器(JDBC/ODBC)连接Spark SQL进行查询。
3:在Spark程序内使用Spark SQL时,Spark SQL支持SQL与常规的Java代码高度整合,包括连接RDD与SQL表,公开的自定义SQL函数接口等。
Spark SQL提供了一种特殊的RDD:SchemaRDD(1.3后,被DataFrame所取代),存放Row对象的RDD,每个Row对象代表一行记录,SchemaRDD还包含记录的结构信息(即数据字段)。
5.1、连接Spark SQL
Spark SQL编译时可以包含Hive支持,也可以不包含。
支持:spark-hive_2.10;入口:HiveContext
不支持:spark-sql_2.10;入口:SQLContext
若要把Spark SQL连接到一个部署好的Hive上,你必须把hive-site.xml复制发哦Spark的配置文件目录中。即使没有部署好HIve,Spark SQL也可以运行。Spark SQL会在当前的工作目录中创建出自己的Hive元数据仓库,叫做metastore_db。
5.2、在应用中使用Spark SQL
SchemaRDD:读取数据和执行查询都会返回,和传统数据库中的表的概念类似。它仍然是RDD,可以把它注册为临时表(registerTempTable()),这样就可以是用sql进行查询。
tips:临时表是当前使用的HiveContext或SQLContext中的临时变量,在你的应用退出时这些临时表就不存在了。
缓存:由于我们知道每个列的类型信息,Spark可以更加高效的存储数据。当缓存数据表时,Spark SQL使用一种列式存储格式在内存中表示数据。这些缓存下来的表只会在驱动器程序的生命周期里保存在内存中(进程)。和缓存RDD时的动机一样,如果想在同样的数据上多次运行任务或查询时,就应该把这些数据表缓存起来。
方法:hiveCtx.cacheTable("tableName")
tips:RDD上原有的cache()方法也会引发一次对cacheTable()方法的调用。
也可以使用HQL/SQL语句来缓存表。(CACHE TABLE tableName或UNCACHE TABLE tableName)。
5.3、读取和存储数据
Spark SQL支持很多种结构化数据源,可以跳过复杂的读取过程,轻松从各种数据源中读取到Row对象。
JSON:其中的记录遵循同样的结构信息,那么Spark SQL就可以通过扫描文件推测出结构信息,并且让你可以使用名字访问对应字段。
方法:hiveCtx.jsonFile();
查询结构信息方法:SchemaRDD.printSchema();
5.4、JDBC/ODBC服务器
Spark SQL也提供JDBC连接支持,JDBC服务器作为一个独立的Spark驱动器程序运行,可以在多用户之间共享。
tips:当启动JDBC服务器时,JDBC服务器会在后台运行并且将所有输出重定向到一个日志文件中,如果使用JDBC服务器进行查询时遇到问题,可以查看日志报错信息。
Beeline客户端:可以使用标准的HQL命令。创建数据表(CREATE TABLE)、数据读取(LOAD DATA)、列举数据表(SHOW TABLES)、查看表结构(DESCRIBE tableName)、缓存表数据(CACHE TABLE或UNCACHE TABLE tableName)、执行计划(EXPLAIN sql)。
5.5、用户自定义函数
UDF:用户自定义函数。Spark SQL不仅有自己的UDF接口,也支持已有的Apache Hive UDF。
Spark SQL UDF:在Java中,需要扩展对应的UDF类。UDF能够支持各种数据类型,返回类型也可以与调用时的参数类型完全不一样。
Java中的对应类型可以在:org.apache.spark.sql.api.java.DataType中找到。
5.6、Spark SQL性能
Spark SQL使有条件的聚合操作变得非常容易,比如对多个列进行求值。
Spark SQL可以利用其对类型的了解来高效地表示数据。当缓存数据时,Spark SQL使用内存式的列式存储。这不仅仅节约了缓存的空间,而且尽可能地减少了后续查询中对针对某几个字段查询时的数据读取。
6、在集群上运行Spark
Spark的一大好处就是可以通过增加及其数量并使用集群模式运行,来扩展程序的计算能力。可以在小数据集上利用本地模式快速开发并验证应用,然后无需修改代码就可以在大规模集群上运行。Spark可以在各种各样的集群管理(Hadoop YARN、Apache Mesos、独立集群管理器)上运行。
6.1、运行时架构
在分布式环境下,Spark集群采用的是主/从结构。
驱动器(Driver)节点:这个工作节点负责中央协调,调度各个分布式工作节点。
执行器(executor)节点:是一种工作进程,负责在Spark作业中运行任务。
Spark应用(application):Driver + executor(n),通过一个叫做集群管理器(Cluster Manager)的外部服务在集群中的机器上启动。
Driver可以和大量的executor进行通信,它们也都作为独立的Java进程运行。
6.1.1、驱动器节点
Spark驱动器是执行你的程序中的main()方法的进程。它执行用户编写的用来创建SparkContext、创建RDD,以及进行RDD的妆化操作和行动操作的代码。驱动器程序一旦种植,Spark应用也就结束了。
驱动器程序在Spark应用中有下述两个职责:
1)把用户程序转为任务:
Spark驱动器程序负责把用户程序转化为多个物理执行的单元,这些单元也被称为任务(task)。从上层来看,所有的Spark程序都遵循同样的结构:程序从输入数据创建一系列RDD,再使用转化操作派生出新的RDD,最后使用行动操作收集或存储结构RDD中的数据。Spark程序其实是隐式地创建出了一个由操作组成的逻辑上的有向无环图(DAG)。当驱动器程序运行时,它会把这个逻辑图转为物理执行计划。Spark会对逻辑执行计划作一些优化,这样Spark就把逻辑计划转为一系列步骤(stage)。而每个步骤又由多个任务组成。这些任务会被打包并送到集群中。任务是Spark中最小的工作单元,用户程序通常要启动成百上千的独立任务。
2)为执行器节点调度任务
执行器进程启动后,会向驱动器进程注册自己。驱动器进程始终对应用中所有的执行器节点有完整的记录。每个执行器节点代表一个能够处理任务和存储RDD数据的进程。Spark驱动器程序会根据当前的执行器节点集合,尝试把所有任务基于数据所在位置分配给合适的执行器进程。驱动器程序会将一些Spark应用的运行时的信息通过网页界面呈现出来,默认端口4040上。
6.1.2、执行器节点
Spark执行器节点是一种工作进程,负责在Spark作业中运行任务,任务间相互独立,Spark应用启动时,执行器节点就被同时启动,并且始终伴随着整个Spark应用的生命周期而存在。如果有执行器节点发生了异常或崩溃,Spark应用也可以继续执行。执行器进程有两大作用:
1)它们负责运行组成Spark应用的任务,并将结果返回给驱动器进程;
2)它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在执行器进程的,因此任务可以在运行时充分利用缓存数据加速运算。
tips:本地模式中的驱动器程序和执行器程序:执行器程序通常都运行在专用的进程中。
6.1.3、集群管理器
驱动器节点和执行器节点是如何启动的?
答:Spark依赖与集群管理器来启动执行器节点,而在某些特殊情况下,也依赖集群管理器来启动驱动器节点。
集群管理器:是Spark中的可插拔式组件。除了自带的独立集群管理,Spark也可以运行在其他外部集群管理器上。
tips:Spark文档中始终使用驱动器节点和执行器节点的概念来描述执行Spark应用的进程。而主节点(master)和工作节点(worker)的概念则被用来分别表述集群管理器中的中心化的部分和分布式的部分。
6.1.4、启动一个程序
不论你使用的是哪一种集群管理器,你都可以使用Spark提供的统一脚本spark-submit将你的应用提交到那种集群管理器上。通过不同的配置选项,spark-submit可以连接到相应的集群管理器上,并控制应用使用的资源数量。
6.1.5、小结
1)用户通过spark-submit脚本提交应用。
2)spark-submit脚本启动驱动器程序,调用用户定义的main()方法。
3)驱动器程序与集群管理通信,申请资源以启动执行器节点。
4)集群管理为驱动器程序启动执行器节点。
5)驱动器进程执行用户应用中的操作。根据程序中所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程。
6)任务在执行器程序中进行计算并保存结果。
7)乳沟驱动器程序的main()方法突出,或者调用了SparkContext.stop(),驱动器程序会终止执行器进程,并且通过集群管理器释放资源。
6.2、使用spark-submit部署应用
bin/spark-submit myApp.jar
如果在调用spark-submit时处理脚本或JAR包的名字之外没有别的参数,那么这个Spark程序只会在本地执行。
bin/spark-submit [options]
[app options]:是传给你的应用的选项。如果你的程序要处理传给main()方法的参数,它只会得到[app options]对用的标记,不会得到spark-submit 的标记。
tips:当你提交应用时,绝不要把Spark本身放在提交的依赖中。spark-submit会自动取保Spark在你的程序的运行路径中。
6.3、Spark应用内与应用间调度
在调度多用户集群时,Spark主要依赖集群管理器来在Spark应用间共享资源。当Spark应用向集群管理器申请执行器节点时,应用收到的执行器节点个数可能比它申请的更多或者更少,这取决于集群的可用性与争用。
Spark应用有一种特殊情况,就是那些长期运行(long lived)的应用。这以为这这些应用从不主动推出。Spark SQL中的JDBC服务器就是一个长期运行的Spark应用。当JDBC服务器启动后,它会从集群管理器获得一系列执行器节点,然后就成为用户提交SQL查询的永久入口。由于这个应用本身就是为多用户调度工作的,所以它需要一种细粒度的调度机制来强制共享资源。Spark提供了一种用来配置应用内调度策略的机制。Spark内部的公平调度(Fair Scheduler)会让长期运行的应用定义调度任务的优先级队列。
6.4、集群调度器
独立集群调度器:这种集群管理器由一个主节点和几个工作节点组成,各自都分配一定量的内存和CPU核心。当提交应用时,你可以配置星期进程使用的内存量,以及所有执行器进程使用的CPU核心总数。
tips:阻碍应用运行的一个常见陷阱是为执行器进程申请的内存(spark-submit的--executor-memory标记传递的值)超过了集群所能提供的内存总量。这种情况下,独立集群管理器始终无法为应用分配执行器节点。
独立集群管理器支持两种部署模式:在这两种模式中,应用的驱动器程序运行在不同的地方。
1)客户端模式(默认情况):驱动器程序会运行在你执行spark-submit的机器上,是spark-submit命令的一部分。
2)集群模式:驱动器程序会作为某个工作节点上的一个独立的进程运行在独立集群管理内部。它也会连接主节点来申请执行器节点。在这种模式下,spark-submit是“一劳永逸”型。
高度可用性:如果你想让集群的主节点也拥有高度可用性,Spark还支持使用ZooKeeper来维护多个备用的主节点,并在一个主节点失败时切换到新的主节点上。
6.5、选择合适的集群管理器
1)如果是从零开始,可以选择独立集群管理器,安装起来最简单,只是使用Spark的话,独立集群管理器提供与其他集群管理器完全一样的全部功能。
2)如果你要在使用Spark的同时使用其他应用,或是要用到更丰富的资源调度功能(队列),那么YARN和Mesos都能满足你的需求,两者中,大多数Hadoop发行版来数,一般YARN已经预装好了
3)Mesos相对于YARN和独立模式的一大有点在于其细粒度共享的选项,该选项可以将类似Spark shell这样的交互式应用中的不同命令分配到不同的CPU还是那个。