广播变量
广播变量允许程序员保留一个只读的变量,缓存在每一台机器上,而非每个任务保存一份拷贝。他们可以这样被使用,例如,以一种高效的方式给每个结点一个大的输入数据集。Spark会尝试使用一种高效的广播算法来传播广播变量,从而减少通信的代价。
SparkContext.broadcast(v)方法从变量v创建的。广播变量是一个v的封装器,它的值可以通过调用value方法获得。如下模块展示了这个:
scala > val broadcastVar = sc . broadcast ( Array ( 1 , 2 , 3 ) )
broadcastVar : spark . Broadcast [ Array [ Int ] ] = spark . Broadcast ( b5c40191 - a864 - 4c7d - b9bf - d87e1a4e787c )
scala > broadcastVar . value
res0 : Array [ Int ] = Array ( 1 , 2 , 3 )
在广播变量被创建后,它应该在集群运行的任何函数中,代替v值被调用,从而v值不需要被再次传递到这些结点上。另外,对象v不能在广播后修改,这样可以保证所有结点的收到的都是一模一样的广播值。
累加器
累加器是一种只能通过关联操作进行“加”操作的变量,因此可以高效被并行支持。它们可以用来实现计数器(如MapReduce中)和求和器。Spark原生就支持Int和Double类型的累加器,开发者可以自己添加新的支持类型。
一个累加器可以通过调用SparkContext.accumulator(v)方法从一个初始值v中创建。运行在集群上的任务,可以通过使用+=来给它加值。然而,他们不能读取这个值。只有驱动程序可以使用value的方法来读取累加器的值。
如下的解释器模块,展示了如何利用累加器,将一个数组里面的所有元素相加:
scala > val accum = sc . accumulator ( 0 )
accum : spark . Accumulator [ Int ] = 0
scala > sc . parallelize ( Array ( 1 , 2 , 3 , 4 ) ) . foreach ( x = > accum + = x )
. . .
10 / 09 / 29 18 : 41 : 08 INFO SparkContext : Tasks finished in 0.317106 s
scala > accum . value
res2 : Int = 10
并行集合(Parallelized Collections)
SparkContext的parallelize方法,在一个已经存在的Scala集合上创建的(一个Seq对象)。集合的对象将会被拷贝,创建出一个可以被并行操作的分布式数据集。例如,下面的解释器输出,演示了如何从一个数组创建一个并行集合:
scala > val data = Array ( 1 , 2 , 3 , 4 , 5 )
data : Array [ Int ] = Array ( 1 , 2 , 3 , 4 , 5 )
scala > val distData = sc . parallelize ( data )
distData : spark . RDD [ Int ] = spark . ParallelCollection @ 10d13e3e
一旦分布式数据集(distData)被创建好,它们将可以被并行操作。例如,我们可以调用distData.reduce(_ +_)来将数组的元素相加。我们会在后续的分布式数据集运算中进一步描述。
并行集合的一个重要参数是slices,表示数据集切分的份数。Spark将会在集群上为每一份数据起一个任务。典型地,你可以在集群的每个CPU上分布2-4个slices. 一般来说,Spark会尝试根据集群的状况,来自动设定slices的数目。然而,你也可以通过传递给parallelize的第二个参数来进行手动设置。(例如:sc.parallelize(data, 10)).
Hadoop数据集(Hadoop Datasets)
Spark可以从存储在HDFS,或者Hadoop支持的其它文件系统(包括本地文件,Amazon S3, Hypertable, Hbase等等)上的文件创建分布式数据集。Spark可以支持TextFile,SequenceFiles以及其它任何Hadoop输入格式。(Python接口目前还不支持SequenceFile,很快会支持吧)
Text file的RDDs可以通过SparkContext’s textFile的方式创建,该方法接受一个文件的URI地址(或者机器上的一个本地路径,或者一个hdfs://, sdn://,kfs://,其它URI). 下面是一个调用例子:
scala > val distFile = sc . textFile ( "data.txt" )
distFile : spark . RDD [ String ] = spark . HadoopRDD @ 1d4cee08
distFile可以被进行数据集操作。例如,我们可以通过使用如下的map和reduce操作:distFile.map(_.size).reduce(_ + _ )将所有数据行的长度相加。
textFile方法也可以通过输入一个可选的第二参数,来控制文件的分片数目。默认情况下,Spark为每一块文件创建一个分片(HDFS默认的块大小为64MB),但是你也可以通过传入一个更大的值,来指定一个更高的片值。注意,你不能指定一个比块数更小的片值(和Map数不能小于Block数一样,但是可以比它多)
对于SequenceFiles,可以使用SparkContext的sequenceFile[K, V]方法创建,其中K和V是文件中的key和values的类型。像IntWritable和Text一样,它们必须是Hadoop的Writable interface的子类。另外,对于几种通用Writable类型,Spark允许你指定原生类型来替代。例如:sequencFile[Int, String]将会自动读取IntWritable和Texts。
SparkContext.hadoopRDD方法,它可以接收任意类型的JobConf和输入格式类,键类型和值类型。按照像Hadoop作业一样的方法,来设置输入源就可以了。
RDD 的操作
转换(transformation)从现有的数据集创建一个新的数据集;而动作(actions)在数据集上运行计算后,返回一个值给驱动程序。 例如,map就是一种转换,它将数据集每一个元素都传递给函数,并返回一个新的分布数据集表示结果。另一方面,reduce是一种动作,通过一些函数将所有的元素叠加起来,并将最终结果返回给Driver程序。(不过还有一个并行的reduceByKey,能返回一个分布式数据集)
map创建的一个新数据集,并在reduce中使用,最终只返回reduce的结果给driver,而不是整个大的新数据集。
persist(或者cache)方法,持久化一个RDD在内存中。在这种情况下,Spark将会在集群中,保存相关元素,下次你查询这个RDD时,它将能更快速访问。在磁盘上持久化数据集,或在集群间复制数据集也是支持的,这些选项将在本文档的下一节进行描述。
下面的表格列出了目前所支持的转换和动作(详情请参见 RDD API doc):
转换(transformation)
转换 | 含义 |
map(func) | func函数转换后组成 |
filter(func) | func函数计算后返回值为true的输入元素组成 |
flatMap(func) | func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithSplit(func) | func带有一个整数参数表示分块的索引值。因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement,fraction, seed) | fraction指定的比例,对数据进行采样,可以选择是否用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 返回一个新的数据集,新数据集是由源数据集和参数数据集联合而成 |
distinct([numTasks])) | 返回一个包含源数据集中所有不重复元素的新数据集 |
groupByKey([numTasks]) | 在一个(K,V)对的数据集上调用,返回一个(K,Seq[V])对的数据集 注意:默认情况下,只有8个并行任务来做操作,但是你可以传入一个可选的numTasks参数来改变它 |
reduceByKey(func, [numTasks]) | reduce函数,将相同key的值聚合到一起。类似groupByKey,reduce任务个数是可以通过第二个可选参数来配置的 |
sortByKey([ascending], [numTasks]) | ascending布尔参数决定 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)类型的数据集上调用时,返回一个相同key对应的所有元素对在一起的(K, (V, W))数据集 |
cogroup(otherDataset, [numTasks]) | groupwith |
cartesian(otherDataset) | 笛卡尔积,在类型为 T 和 U 类型的数据集上调用时,返回一个 (T, U)对数据集(两两的元素对) |
完整的转换列表可以在RDD API doc中获得。
动作(actions)
动作 | 含义 |
reduce(func) | func(接受两个参数,返回一个参数)聚集数据集中的所有元素。这个功能必须可交换且可关联的,从而可以正确的被并行执行。 |
collect() | 在驱动程序中,以数组的形式,返回数据集的所有元素。这通常会在使用filter或者其它操作并返回一个足够小的数据子集后再使用会比较有用。 |
count() | 返回数据集的元素的个数。 |
first() | 返回数据集的第一个元素(类似于take(1)) |
take(n) | n个元素组成的数组。注意,这个操作目前并非并行执行,而是由驱动程序计算所有的元素 |
takeSample(withReplacement,num, seed) | num个元素组成,可以选择是否用随机数替换不足的部分,Seed用于指定的随机数生成器种子 |
saveAsTextFile(path) | toString方法,将它转换为文件中的文本行 |
saveAsSequenceFile(path) | 将数据集的元素,以Hadoop sequencefile的格式,保存到指定的目录下,本地系统,HDFS或者任何其它hadoop支持的文件系统。这个只限于由key-value对组成,并实现了Hadoop的Writable接口,或者隐式的可以转换为Writable的RDD。(Spark包括了基本类型的转换,例如Int,Double,String,等等) |
countByKey() | 对(K,V)类型的RDD有效,返回一个(K,Int)对的Map,表示每一个key对应的元素个数 |
foreach(func) | func进行更新。这通常用于边缘效果,例如更新一个累加器,或者和外部存储系统进行交互,例如HBase |
完整的转换列表可以在RDD API doc中获得。