Spark中的编程模型
:运行Application的main()函数并创建SparkContext。通常SparkContext代表driver program
Executor
:
在集群上获得资源的外部服务(例如 Spark Standalon,Mesos、Yarn)
Worker Node
:被送到executor上执行的工作单元。
Job
:每个Job会被拆分成很多组Task,每组任务被称为stage,也可称TaskSet。该术语可以经常在日志中看打。
RDD
2. Spark应用框架
DAGScheduler作业调度模块是基于Stage的高层调度模块(参考:),DAG 全称 Directed Acyclic Graph,有向无环图。简单的来说,就是一个由顶点和有方向性的边构成的图中,从任意一个顶点出发,没有任何一条路径会将其带回到出发的顶点。它为每个 Spark Job计算具有依赖关系的多个Stage任务阶段(通常根据Shuffle来划分Stage,如groupByKey, reduceByKey等涉及到shuffle的transformation就会产生新的stage),然后将每个Stage划分为具体的一组任务,以 TaskSets的形式提交给底层的任务调度模块来具体执行。其中,不同stage之前的RDD为宽依赖关系。 TaskScheduler任务调度模块负责具体启动任务,监控和汇报任务运行情况。
创建SparkContext一般要经过下面几个步骤:
- import org.apache.spark.SparkContext._ new SparkConf().setAppName(appName).setMaster(master_url) new SparkContext(conf) 在完成应用的设计和编写后,使用spark-submit来提交应用的jar包。spark-submit的命令行参考如下:
- ./bin/spark-submit
- --class <main-class>
- --master <master-url>
- --deploy-mode <deploy-mode>
- ... # other options
- <application-jar>
- [application-arguments]
使用一个Worker线程本地化运行SPARK(完全不并行)
local[*]使用逻辑CPU个数数量的线程来本地化运行Spark
local[K]使用K个Worker线程本地化运行Spark(理想情况下,K应该根据运行机器的CPU核数设定)
spark://HOST:PORT连接到指定的Spark
standalone master。默认端口是7077.
yarn-client以客户端模式连接YARN集群。集群的位置可以在HADOOP_CONF_DIR
环境变量中找到。
yarn-cluster
连接到指定的Mesos集群。默认接口是5050.
而spark-shell会在启动的时候自动构建SparkContext,名称为sc。
Spark所有的操作都围绕弹性分布式数据集(RDD)进行,这是一个有容错机制并可以被并行操作的元素集合,具有只读、分区、容错、高效、无需物化、可以缓存、RDD依赖等特征。
Hadoop数据集(Hadoop Datasets) :在一个文件的每条记录上运行函数。只要文件系统是HDFS,或者hadoop支持的任意存储系统即可。
这两种类型的RDD都可以通过相同的方式进行操作,从而获得子RDD等一系列拓展,形成lineage血统关系图。
根据能启动的executor的数量来进行切分多个slice,每一个slice启动一个Task来进行处理。
指定了partition的数量
使用textFile()方法可以将本地文件或HDFS文件转换成RDD
支持整个文件目录读取,文件可以是文本或者压缩文件(如gzip等,自动执行解压缩并加载数据)。如textFile(”file:///dfs/data”)
支持通配符读取,例如:
- val rdd1 = sc.textFile("file:///root/access_log/access_log*.filter");
- val rdd2=rdd1.map(_.split("t")).filter(_.length==6)
- rdd2.count()
- ......
- 14/08/20 14:44:48 INFO HadoopRDD: Input split: file:/root/access_log/access_log.20080611.decode.filter:134217728+20705903
- ......
b).
使用sequenceFile[K,V]()方法可以将SequenceFile转换成RDD。SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat
File)。
d). 使用SparkContext.hadoopRDD方法可以将其他任何Hadoop输入类型转化成RDD使用方法。一般来说,HadoopRDD中每一个HDFS
block都成为一个RDD分区。
此外,通过Transformation可以将HadoopRDD等转换成FilterRDD(依赖一个父RDD产生)和JoinedRDD(依赖所有父RDD)等。
b).
使用sequenceFile[K,V]()方法可以将SequenceFile转换成RDD。SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat
File)。
d). 使用SparkContext.hadoopRDD方法可以将其他任何Hadoop输入类型转化成RDD使用方法。一般来说,HadoopRDD中每一个HDFS
block都成为一个RDD分区。
此外,通过Transformation可以将HadoopRDD等转换成FilterRDD(依赖一个父RDD产生)和JoinedRDD(依赖所有父RDD)等。
RDD支持两类操作:
转换(transformation)
在RDD上运行计算后,返回结果给驱动程序或写入文件系统。
例如,map就是一种transformation,它将数据集每一个元素都传递给函数,并返回一个新的分布数据集表示结果。
reduce则是一种action,通过一些函数将所有的元素叠加起来,并将最终结果返回给Driver程序。
(1). map(func)
1 to 100)
- val num2 = num.map(_*2)
- val num3 = num2.filter(_ % 3 == 0)
- ......
- num3.collect
- //res: Array[Int] = Array(6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198)
- num3.toDebugString
- //res5: String =
- //FilteredRDD[20] at filter at <console>:16 (48 partitions)
- // MappedRDD[19] at map at <console>:14 (48 partitions)
- // ParallelCollectionRDD[18] at parallelize at <console>:12 (48 partitions)
Similar to map, but each input item can be mapped to 0 or more output
items (so func should return a Seq rather than a single item).
类似于map,但是每一个输入元素可以被映射为0或多个输出元素(因此func应该返回一个序列,而不是单一元素)
1,2),List(3,4),List(3,6,8)))
- kv.flatMap(x=>x.map(_+1)).collect
- //res0: Array[Int] = Array(2, 3, 4, 5, 4, 7, 9)
- //Word Count
- sc.textFile('hdfs://hdp01:9000/home/debugo/*.txt').flatMap(_.split(' ')).map((_,1)).reduceByKey(_+_)
(4). mapPartitions(func)
Similar
to map, but runs separately on each partition (block) of the RDD, so
func must be of type Iterator<T> => Iterator<U> when
running
on an RDD of type T.
类
似于map,但独立地在RDD的每一个分块上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] =>
Iterator[U]。mapPartitions将会被每一个数据集分区调用一次。各个数据集分区的全部内容将作为顺序的数据流传入函数func的参
数中,func必须返回另一个Iterator[T]。被合并的结果自动转换成为新的RDD。下面的测试中,元组(3,4)和(6,7)将由于我们选择的
分区策略和方法而消失。
The
combined result iterators are automatically converted into a new RDD.
Please note, that the tuples (3,4) and (6,7) are missing
from the following result due to the partitioning we chose
- val nums = sc . parallelize (1 to 9 , 3)
- def myfunc[T] ( iter : Iterator [T] ) : Iterator [( T , T ) ] = {
- var res = List [(T , T) ]()
- var pre = iter.next
- while ( iter.hasNext )
- {
- val cur = iter . next ;
- res .::= ( pre , cur )
- pre = cur ;
- }
- res . iterator
- }
- //myfunc: [T](iter: Iterator[T])Iterator[(T, T)]
- nums.mapPartitions(myfunc).collect
- //res12: Array[(Int, Int)] = Array((2,3), (1,2), (5,6), (4,5), (8,9), (7,8))
Similar to mapPartitions, but also provides func with an integer value
representing the index of the partition, so func must be of type (Int,
Iterator<T=>) ==> Iterator<U=> when running on an RDD of
type T.
类似于mapPartitions, 其函数原型是:
def mapPartitionsWithIndex [ U : ClassTag ]( f : ( Int , Iterator [ T ])
=> Iterator [ U ] , preservesPartitioning : Boolean = false ) : RDD [
U ],
mapPartitionsWithIndex的func接受两个参数,第一个参数是分区的索引,第二个是一个数据集分区的迭代器。而输出的是一个包含经过该函数转换的迭代器。下面测试中,将分区索引和分区数据一起输出。
- val x = sc . parallelize ( List (1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10) , 3)
- def myfunc ( index : Int , iter : Iterator [ Int ]) : Iterator [ String ] = {
- iter . toList . map ( x => index + "-" + x ) . iterator
- }
- //myfunc: (index: Int, iter: Iterator[Int])Iterator[String]
- x . mapPartitionsWithIndex ( myfunc ) . collect()
- res: Array[String] = Array(0-1, 0-2, 0-3, 1-4, 1-5, 1-6, 2-7, 2-8, 2-9, 2-10)
Sample a fraction fraction of the data, with or without replacement, using a given random number generator seed.
根据fraction指定的比例,对数据进行采样,可以选择是否用随机数进行替换,seed用于指定随机数生成器种子。
- val a = sc . parallelize (1 to 10000 , 3)
- a . sample ( false , 0.1 , 0) . count
- res0 : Long = 960
- a . sample ( true , 0.7 , scala.util.Random.nextInt(10000)) . count
- res1: Long = 7073
Return a new dataset that contains the union of the elements in the source dataset and the argument.
返回一个新的数据集,新数据集是由源数据集和参数数据集联合而成。
Return a new RDD that contains the intersection of elements in the source dataset and the argument.
Return a new dataset that contains the distinct elements of the source dataset.
返回一个包含源数据集中所有不重复元素的新数据集
- val kv1=sc.parallelize(List(("A",1),("B",2),("C",3),("A",4),("B",5)))
- val kv2=sc.parallelize(List(("A",4),("A", 2),("C",3),("A",4),("B",5)))
- kv2.distinct.collect
- res0: Array[(String, Int)] = Array((A,4), (C,3), (B,5), (A,2))
- kv1.union(kv2).collect
- res1: Array[(String, Int)] = Array((A,1), (B,2), (C,3), (A,4), (B,5), (A,4), (A,2), (C,3), (A,4), (B,5))
- kv1.union(kv2).collect.distinct
- res2: Array[(String, Int)] = Array((A,1), (B,2), (C,3), (A,4), (B,5), (A,2))
- kv1.intersection(kv2).collect
- res43: Array[(String, Int)] = Array((A,4), (C,3), (B,5))
When called on a dataset of (K, V) pairs, returns a dataset of (K, Iterable) pairs.
Note: If you are grouping in order to perform an aggregation (such as a
sum or average) over each key, using reduceByKey or combineByKey will
yield much better performance.
Note: By default, the level of parallelism in the output depends on the
number of partitions of the parent RDD. You can pass an optional
numTasks argument to set a different number of tasks.
窄依赖(narrow
dependencies)
子RDD的每个分区依赖于常数个父分区(与数据规模无关)
输入输出一对一的算子,且结果RDD的分区结构不变。主要是map/flatmap
输入输出一对一的算子,但结果RDD的分区结构发生了变化,如union/coalesce
从输入中选择部分元素的算子,如filter、distinct、substract、sample
reduce是一个action,和reduceByKey完全不同。
When called on a dataset of (K, V) pairs, returns a dataset of (K, V)
pairs where the values for each key are aggregated using the given
reduce function func, which must be of type (V,V) => V. Like
ingroupByKey, the number of reduce tasks is configurable through
an optional second argument.
在一个(K,V)对的数据集上调用时,返回一个(K,V)对的数据集,使用指定的reduce函数,将相同key的值聚合到一起。类似groupByKey,reduce任务个数是可以通过第二个可选参数来配置的
(12).sortByKey([ascending], [numTasks])
- val kv1=sc.parallelize(List(("A",1),("B",2),("C",3),("A",4),("B",5)))
- res0: Array[(String, Int)] = Array((A,1), (A,4), (B,2), (B,5), (C,3))
- kv1.sortByKey().collect //注意sortByKey的小括号不能省
- res1: Array[(String, Int)] = Array((A,1), (A,4), (B,2), (B,5), (C,3))
- kv1.groupByKey().collect
- res1: Array[(String, Iterable[Int])] = Array((A,ArrayBuffer(1, 4)), (B,ArrayBuffer(2, 5)), (C,ArrayBuffer(3)))
- kv1.reduceByKey(_+_).collect
- res2: Array[(String, Int)] = Array((A,5), (B,7), (C,3))
When
called on datasets of type (K, V) and (K, W), returns a dataset of (K,
(V, W)) pairs with all pairs of elements for each key.
Outer joins are also supported through leftOuterJoin and
rightOuterJoin.
在类型为(K,V)和(K,W)类型的数据集上调用时,返回一个相同key对应的所有元素对在一起的(K, (V, W))数据集
(14).cogroup(otherDataset, [numTasks])
"A",1),("B",2),("C",3),("A",4),("B",5)))
- val kv3=sc.parallelize(List(("A",10),("B",20),("D",30)))
- kv1.join(kv3).collect
- res16: Array[(String, (Int, Int))] = Array((A,(1,10)), (A,(4,10)), (B,(2,20)), (B,(5,20)))
- kv1.cogroup(kv3).collect
- res0: Array[(String, (Iterable[Int], Iterable[Int]))] = Array((A,(ArrayBuffer(1, 4),ArrayBuffer(10))), (B,(ArrayBuffer(2, 5),ArrayBuffer(20))), (C,(ArrayBuffer(3),ArrayBuffer())), (D,(ArrayBuffer(),ArrayBuffer(30))))
When called on datasets of types T and U, returns a dataset of (T, U) pairs (all pairs of elements).
笛卡尔积,在类型为 T 和 U 类型的数据集上调用时,返回一个 (T, U)对数据集(两两的元素对)
Pipe each partition of the RDD through a shell command, e.g. a Perl or
bash script. RDD elements are written to the process’s stdin and lines
output to its stdout are returned as an RDD of strings.
通过POSIX 管道来将每个RDD分区的数据传入一个shell命令(例如Perl或bash脚本)。RDD元素会写入到进程的标准输入,其标准输出会作为RDD字符串返回。
Decrease the number of partitions in the RDD to numPartitions. Useful
for running operations more efficiently after filtering down a large
dataset.
将RDD分区的数量降低为numPartitions,对于经过过滤后的大数据集的在线处理更加有效。
Reshuffle the data in the RDD randomly to create either more or fewer
partitions and balance it across them. This always shuffles all data
over the network.
随机重新shuffle RDD中的数据,并创建numPartitions个分区。这个操作总会通过网络来shuffle全部数据。
Aggregate the elements of the dataset using a function func (which takes
two arguments and returns one). The function should be commutative and
associative so that it can be computed correctly in parallel.
通过函数func(接受两个参数,返回一个参数)聚集数据集中的所有元素。这个功能必须可交换且可关联的,从而可以正确的被并行执行。
Return all the elements of the dataset as an array at the driver
program. This is usually useful after a filter or other operation that
returns a sufficiently small subset of the data.
在驱动程序中,以数组的形式,返回数据集的所有元素。这通常会在使用filter或者其它操作并返回一个足够小的数据子集后再使用会比较有用。
Return the number of elements in the dataset.
返回数据集的元素的个数。
Return the first element of the dataset (similar to take(1)).
返回数据集的第一个元素(类似于take(1))
Return an array with the first n elements of the dataset. Note that this
is currently not executed in parallel. Instead, the driver program
computes all the elements.
返回一个由数据集的前n个元素组成的数组。注意,这个操作目前并非并行执行,而是由驱动程序计算所有的元素
Only available on RDDs of type (K, V). Returns a hashmap of (K, Int) pairs with the count of each key.
对(K,V)类型的RDD有效,返回一个(K,Int)对的Map,表示每一个key对应的元素个数
Run a function func on each element of the dataset. This is usually done
for side effects such as updating an accumulator variable (see below)
or interacting with external storage systems.
在数据集的每一个元素上,运行函数func进行更新。这通常用于边缘效果,例如更新一个累加器,或者和外部存储系统进行交互,例如HBase.
- val num=sc.parallelize(1 to 10)
- num.reduce (_ + _)
- res1: Int = 55
- num.take(5)
- res2: Array[Int] = Array(1, 2, 3, 4, 5)
- num.first
- res3: Int = 1
- num.count
- res4: Long = 10
- num.take(5).foreach(println)
- 1
- 2
- 3
- 4
- 5
- val kv1=sc.parallelize(List(("A",1),("B",2),("C",3),("A",4),("B",5),("A",7),("B",7)))
- val kv1_count=kv1.countByKey()
- kv1_count: scala.collection.Map[String,Long] = Map(A -> 3, C -> 1, B -> 3)
(26). takeSample(withReplacement,num, seed)
(27). takeOrdered(n, [ordering])
(29). saveAsSequenceFile(path)
RDD API
- object StorageLevel {
- val NONE = new StorageLevel(false, false, false, false)
- val DISK_ONLY = new StorageLevel(true, false, false, false)
- val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
- val MEMORY_ONLY = new StorageLevel(false, true, false, true)
- val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
- val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
- val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
- val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
- val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
- val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
- val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
- val OFF_HEAP = new StorageLevel(false, false, true, false) // Tachyon
- }
- class StorageLevel private(
- private var useDisk_ : Boolean,
- private var useMemory_ : Boolean,
- private var useOffHeap_ : Boolean,
- private var deserialized_ : Boolean,
- private var replication_ : Int = 1
- )
· 如果不行,试着使用MEMORY_ONLY_SER并且选择一个快速序列化的库使得对象在有比较高的空间使用率的情况下,依然可以较快被访问。
· 如果你想有快速故障恢复能力,使用复制存储级别(例如:用Spark来响应web应用的请求)。所有的存储级别都有通过重新计算丢失数据恢复错误的容错机制,但是复制存储级别可以让你在RDD上持续的运行任务,而不需要等待丢失的分区被重新计算。
· 6. RDD的共享变量
– 广播变量缓存到各个节点的内存中,而不是每个 Task
– 广播变量被创建后,能在集群中运行的任何函数调用
– 广播变量是只读的,不能在被广播后修改
– 对于大数据集的广播, Spark 尝试使用高效的广播算法来降低通信成本
使用方法:
0)
- sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum + = x)
- accum.value
- val num=sc.parallelize(1 to 100)
浙公网安备 33010602011771号