1 概述(Overview)
总体来讲,每一个Spark驱动程序应用都由一个驱动程序组成,该驱动程序包含一个由用户编写的main方法,该方法会在集群上并行执行一些列并行计算操作。Spark最重要的一个概念是弹性分布式数据集,简称RDD(resilient distributed dataset )。RDD是一个数据容器,它将分布在集群上各个节点上的数据抽象为一个数据集,并且RDD能够进行一系列的并行计算操作。可以将RDD理解为一个分布式的List,该List的数据为分布在各个节点上的数据。RDD通过读取Hadoop文件系统中的一个文件进行创建,也可以由一个RDD经过转换得到。用户也可以将RDD缓存至内存,从而高效的处理RDD,提高计算效率。另外,RDD有良好的容错机制。
Spark另外一个重要的概念是共享变量(shared variables)。在并行计算时,可以方便的使用共享变量。在默认情况下,执行Spark任务时会在多个节点上并行执行多个task,Spark将每个变量的副本分发给各个task。在一些场景下,需要一个能够在各个task间共享的变量。Spark支持两种类型的共享变量:
广播变量(broadcast variables):将一个只读变量缓存到集群的每个节点上。例如,将一份数据的只读缓存分发到每个节点。
累加变量(accumulators):只允许add操作,用于计数、求和。
2 引入Spark(Linking with Spark)
在Spark 1.6.0上编写应用程序,支持使用Scala 2.10.X、Java 7+、Python 2.6+、R 3.1+。如果使用Java 8,支持lambda表达式(lambda expressions)。
在编写Spark应用时,需要在Maven依赖中添加Spark,Spark的Maven Central为:
groupId = org.apache.spark
artifactId = spark-core_2.10
version = 1.6.0
另外,如果Spark应用中需要访问HDFS集群,则需要在hadoop-client中添加对应版本的HDFS依赖:
groupId = org.apache.hadoop
artifactId = hadoop-client
version =
最后,需要在程序中添加Spark类。代码如下:
import org.apache.spark.SparkContext
import org.apache.spark.SparkConf
(在Spark 1.3.0之前的版本,使用Scala语言编写Spark应用程序时,需要添加import org.apache.spark.SparkContext._来启用必要的隐式转换)
3 初始化Spark(Initializing Spark)
使用Scala编写Spark程序的需要做的第一件事就是创建一个SparkContext对象(使用Java语言时创建JavaSparkContext)。SparkContext对象指定了Spark应用访问集群的方式。创建SparkContext需要先创建一个SparkConf对象,SparkConf对象包含了Spark应用的一些列信息。代码如下:
Scala
val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)
java
SparkConf conf = new SparkConf().setAppName(appName).setMaster(master);
JavaSparkContext sc = new JavaSparkContext(conf);
appName参数为应用程序在集群的UI上显示的名字。master为Spark、Mesos、YARN URL或local。使用local值时,表示在本地模式下运行程序。应用程序的执行模型也可以在使用spark-submit命令提交任务时进行指定。
3.1 使用Spark Shell(Using the Shell)
在Spark Shell下,一个特殊的SparkContext对象已经帮用户创建好,变量为sc。使用参数--master设置master参数值,使用参数--jars设置依赖包,多个jar包使用逗号分隔。可以使用--packages参数指定Maven坐标来添加依赖包,多个坐标使用逗号分隔。可以使用参数--repositories添加外部的repository。示例如下:
本地模式下,使用4个核运行Spark程序:
$ ./bin/spark-shell --master local[4]
将code.jar包添加到classpath:
$ ./bin/spark-shell --master local[4] --jars code.jar
使用Maven坐标添加一个依赖:
$ ./bin/spark-shell --master local[4] --packages "org.example:example:0.1"
详细的Spark Shell参数描述请执行命令spark-shell --help。更多的spark-submit脚本请见spark-submit script。
4 弹性分布式数据集(RDDs)
Spark最重要的一个概念就是RDD,RDD是一个有容错机制的元素容器,它可以进行并行运算操作。得到RDD的方式有两个:
通过并行化驱动程序中已有的一个集合而获得
通过外部存储系统(例如共享的文件系统、HDFS、HBase等)的数据集进行创建
4.1 并行集合(Parallelized Collections)
在驱动程序中,在一个已经存在的集合上(例如一个Scala的Seq)调用SparkContext的parallelize方法可以创建一个并行集合。集合里的元素将被复制到一个可被并行操作的分布式数据集中。下面为并行化一个保存数字1到5的集合示例:
Scala
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
Java
List data = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD distData = sc.parallelize(data);
当分布式数据集创建之后,就可以进行并行操作。例如,可以调用方法distData.reduce((a,b) => a + b)求数组内元素的和。Spark支持的分布式数据集上的操作将在后面章节中详细描述。
并行集合的一个重要的参数是表示将数据划分为几个分区(partition)的分区数。Spark将在集群上每个数据分区上启动一个task。通常情况下,你可以在集群上为每个CPU设置2-4个分区。一般情况下,Spark基于集群自动设置分区数目。也可以手动进行设置,设置该参数需要将参数值作为第二参数传给parallelize方法,例如:sc.parallelize(data, 10)。注意:在代码中,部分位置使用术语slices(而不是partition),这么做的原因是为了保持版本的向后兼容性。
4.2 外部数据库(External Datasets)
Spark可以通过Hadoop支持的外部数据源创建分布式数据集,Hadoop支持的数据源有本地文件系统、HDFS、Cassandra、HBase、Amazon S3、Spark支持的文本文件、SequenceFiles、Hadoop InputFormat。
SparkContext的testFile方法可以创建文本文件RDD。使用这个方法需要传递文本文件的URI,URI可以为本机文件路径、hdfs://、s3n://等。该方法读取文本文件的每一行至容器中。示例如下:
Scala
scala> val distFile = sc.textFile("data.txt")
distFile: RDD[String] = MappedRDD@1d4cee08
Java
JavaRDD distFile = sc.textFile("data.txt");
创建之后,distFile就可以进行数据集的通用操作。例如,使用map和reduce操作计算所有行的长度的总和:distFile.map(s => s.length).reduce((a, b) => a + b)。
使用Spark读取文件需要注意一下几点:
程序中如果使用到本地文件路径,在其它worker节点上该文件必须在同一目录,并有访问权限。在这种情况下,可以将文件复制到所有的worker节点,也可以使用网络内的共享文件系统。
Spark所有的基于文件输入的方法(包括textFile),都支持文件夹、压缩文件、通配符。例如:textFile("/my/directory")、textFile("/my/directory/*.txt")、textFile("/my/directory/*.gz")。
textFile方法提供了一个可选的第二参数,用于控制文件的分区数。默认情况下,Spark为文件的每个块创建一个分区(块使用HDFS的默认值64MB),通过设置这个第二参数可以修改这个默认值。需要注意的是,分区数不能小于块数。
除了文本文件之外,Spark还支持其它的数据格式:
SparkContext.wholeTextFiles能够读取指定目录下的许多小文本文件,返回(filename,content)对。而textFile只能读取一个文本文件,返回该文本文件的每一行。
对于SequenceFiles可以使用SparkContext的sequenceFile[K,V]方法,其中K是文件中key和value的类型。它们必须为像IntWritable和Text那样,是Hadoop的Writable接口的子类。另外,对于通用的Writable,Spark允许用户指定原生类型。例如,sequenceFile[Int,String]将自动读取IntWritable和Text。
对于其他Hadoop InputFormat,可以使用SparkContext.hadoopRDD方法,该方法接收任意类型的JobConf和输入格式类、键类型和值类型。可以像设置Hadoop job那样设置输入源。对于InputFormat还可以使用基于新版本MapReduce API(org.apache.hadoop.mapreduce)的SparkContext.newAPIHadoopRDD。(老版本接口为:SparkContext.newHadoopRDD)
RDD.saveAsObjectFile和SparkContext.objectFile能够保存包含简单的序列化Java对象的RDD。但是这个方法不如Avro高效,Avro能够方便的保存任何RDD。
4.3 RDD操作(RDD Operations)
RDD支持两种类型的操作:
transformation:从一个RDD转换为一个新的RDD。
action:基于一个数据集进行运算,并返回RDD。