中间操作(Transformations)
map
-
用途
返回每个元素经过传入的函数
func
处理后形成的新分布式数据集 -
使用示例
map[U](f: (T) ⇒ U)(implicit arg0: ClassTag[U]): RDD[U]
scala> val distData = sc.parallelize(Array(1,2,3,4,5)) distData: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[6] at parallelize at <console>:26 scala> val newDistData = distData.map(_*10) newDistData: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[7] at map at <console>:28 scala> newDistData.foreach(println) 10 20 30 40 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
filter
-
用途
过滤出经传入
func
函数计算后返回true的数据,返回的结果对原数据没有影响,所以必须赋给一个变量存储。 -
使用示例
filter(f: (T) ⇒ Boolean): RDD[T]
scala> val distData = sc.parallelize(1 to 10) distData: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[9] at parallelize at <console>:24 scala> distData.filter(_ % 2 ==0 ).foreach(println) 2 4 6 8 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
flatMap
-
用途
flatMap函数是两个操作的集合——正是“先映射后扁平化”:
- 同map函数一样:对每一条输入进行指定的操作,然后为每一条输入返回一个对象
- 最后将所有对象合并为一个对象
-
使用示例
flatMap[U](f: (T) ⇒ TraversableOnce[U])(implicit arg0: ClassTag[U]): RDD[U]
scala> val list = Array("hello scala","hello spark","haha !") list: Array[String] = Array(hello scala, hello spark, haha !) scala> val distList = sc.parallelize(list) distList: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[13] at parallelize at <console>:26 scala> distList.flatMap(_.split(" ")).foreach(println) hello scala hello spark haha ! // 对同一份数据,使用map进行操作 scala> distList.map(_.split(" ")).foreach(println) // 返回的是三个对象 [Ljava.lang.String;@229bb432 [Ljava.lang.String;@15d247dd [Ljava.lang.String;@559c9880 scala> distList.map(_.split(" ")).foreach(_.foreach(println)) hello scala hello spark haha !
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
mapPartitions
-
用途
与map方法类似,map是对rdd中的每一个元素进行操作,而Iterator%5bU%5d,preservesPartitioning:Boolean%29%28implicitevidence%246:scala.reflect.ClassTag%5bU%5d%29:org.apache.spark.rdd.RDD%5bU%5d”>mapPartitions则是对rdd中的每个分区的迭代器进行操作。
-
使用示例
mapPartitions[U](f: (Iterator[T]) ⇒ Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]
例如,实现将每个数字变成原来的2倍的功能,map方式与mapPartitions实现方式对比如下:
-
使用map方式实现
scala> val list = sc.parallelize(1 to 9 ,3) list: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[27] at parallelize at <console>:25 scala> list.map(a => (a,a*2)).foreach(println) (1,2) (2,4) (3,6) (4,8) (5,10) (6,12) (7,14) (8,16) (9,18)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
-
使用mapPartitions方法实现
scala> val list = sc.parallelize(1 to 9 ,3) list: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[22] at parallelize at <console>:25 scala> def doubleFunc(iter:Iterator[Int]):Iterator[(Int,Int)] = { | val res = scala.collection.mutable.ListBuffer[(Int,Int)]() | while(iter.hasNext){ | val cur = iter.next | res += ((cur,cur*2)) | } | res.iterator | } doubleFunc: (iter: Iterator[Int])Iterator[(Int, Int)] scala> list.mapPartitions(doubleFunc).foreach(println) (1,2) (2,4) (3,6) <hr /> (7,14) (8,16) (9,18) <hr /> (4,8) (5,10) (6,12)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
通过对比可知,如果在map过程中需要频繁创建额外的对象(例如将rdd中的数据通过jdbc写入数据库,map需要为每个元素创建一个链接,而mapPartition为每个partition创建一个链接),可见mapPartitions效率比map高的多。
-
mapPartitionsWithIndex
-
用途
和mapPartitions类似,但是提供的函数必须能够接受两个参数,第一个参数代表分区号,第二个参数代表每个分区数据的迭代器。
-
使用示例
mapPartitionsWithIndex[U](f: (Int, Iterator[T]) ⇒ Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]
例如,实现将每个数字变成原来的2倍的功能,使用方式如下:
scala> val list = sc.parallelize(1 to 9 ,3) list: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[29] at parallelize at <console>:25 scala> def doubleFunc(index:Int, iter:Iterator[Int]):Iterator[(Int,Int,Int)] = { | val res = ListBuffer[(Int,Int,Int)]() | while(iter.hasNext){ | val cur = iter.next | res += ((index,cur,cur*2)) | } | res.iterator | } doubleFunc: (index: Int, iter: Iterator[Int])Iterator[(Int, Int, Int)] scala> list.mapPartitionsWithIndex(doubleFunc).foreach(println) (0,1,2) (0,2,4) (0,3,6) (1,4,8) (1,5,10) (1,6,12) (2,7,14) (2,8,16) (2,9,18)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
sample
-
用途
用于对数据进行采样
-
使用示例
sample(withReplacement: Boolean, fraction: Double, seed: Long = [Utils.random.nextLong]: RDD[T]
scala> val distData = sc.parallelize(1 to 10) distData: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[43] at parallelize at <console>:25 scala> distData.sample(false ,0.5).foreach(print) // 采集50%的数据,不进行替换 12349 scala> distData.sample(true ,0.5).foreach(print) // 2568 scala> distData.sample(true ,1).foreach(print) // 采集全部数据,并替换 1223447910
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
union
-
用途
两个RDD的并集
-
使用示例
union(other: RDD[T]): RDD[T]
scala> val data1 = sc.parallelize(1 to 10 by 2) data1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[50] at parallelize at <console>:25 scala> val data2 = sc.parallelize(2 to 10 by 2) data2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[51] at parallelize at <console>:25 scala> data1.union(data2).foreach(println) 1 3 5 2 6 8 10 4 7 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
intersection
-
用途
两个RDD的交集
-
使用示例
intersection(other: RDD[T]): RDD[T]
scala> val data1 = sc.parallelize(1 to 10) data1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[53] at parallelize at <console>:25 scala> val data2 = sc.parallelize(5 to 15) data2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[54] at parallelize at <console>:25 scala> data1.intersection(data2,3).foreach(println) 8 5 6 9 7 10 scala> data1.intersection(data2).foreach(println) 6 10 9 5 7 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
distinct
-
用途
返回一个原数据集中不包含重复元素的新数据集
-
使用示例
distinct(): RDD[T]
scala> val data = sc.parallelize(Array(1,3,4,2,1,3,2,2,2)) data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[67] at parallelize at <console>:25 scala> data.distinct().foreach(print) 4123
- 1
- 2
- 3
- 4
- 5
groupByKey
-
用途
将RDD中每个键的值分组为单个序列。每个组中的元素的排序不能保证,并且每次生成的RDD结果都可能会有所不同。
-
方法示例
-
groupByKey(): RDD[(K, Iterable[V])]
默认使用现有的分区/并行级别对生成的RDD进行哈希分区。
scala> val data = sc.parallelize(Array(("cwc",98),("cwc",93),("lm",43),("lm",32),("cwc",44),("lm",98))) data: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[71] at parallelize at <console>:25 scala> data.groupByKey().foreach(println) (cwc,CompactBuffer(98, 93, 44)) (lm,CompactBuffer(43, 32, 98))
- 1
- 2
- 3
- 4
- 5
- 6
-
groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
- numPartitions:输出将使用numPartition进行散列分区。
scala> data.groupByKey(2).foreach(println) (cwc,CompactBuffer(98, 93, 44)) (lm,CompactBuffer(43, 32, 98))
- 1
- 2
- 3
-
groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
- partitioner:使用给定的分区器对输出结果进行分区
-
-
使用注意
- 该操作的代价非常大,如果您正在进行分组,以便在每个键上执行聚合(如总和或平均值)操作,那么使用
PairRDDFunctions.reduceByKey
或者PairRDDFunctions.aggregateByKey
将会有更好的性能。 - 按照当前实现,groupByKey必须有足够空间在内存中保存任何键的所有键值对,如果某个键有太多的值,那么会发生OutOfMemoryError。
- 该操作的代价非常大,如果您正在进行分组,以便在每个键上执行聚合(如总和或平均值)操作,那么使用
reduceByKey
-
用途
使用给定的函数对每个键的值进行聚合操作
-
使用示例
-
reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)]
输出根据现有的分区/并行级别进行哈希分区
scala> val data = sc.parallelize(Array(("cwc",98),("cwc",93),("lm",43),("lm",32),("cwc",44),("lm",98))) data: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[71] at parallelize at <console>:25 scala> data.reduceByKey((sum,score) => sum+score).foreach(println) (lm,173) (cwc,235)
- 1
- 2
- 3
- 4
- 5
- 6
-
reduceByKey(func: (V, V) ⇒ V, numPartitions: Int): RDD[(K, V)]
- numPartitions:输出将使用numPartition进行散列分区。
-
reduceByKey(partitioner: Partitioner, func: (V, V) ⇒ V): RDD[(K, V)]
- partitioner:使用给定的分区器对输出结果进行分区
-
aggregateByKey
-
用途
使用给定的组合函数和中性“零值”来聚合每个相同键的值。
该函数返回了一个与RDD中的值类型V不同的类型U。
为了避免内存分配,这两个功能都允许修改并返回其第一个参数,而不是创建一个新的U
-
使用示例
-
aggregateByKey[U](zeroValue: U)(seqOp: (U, V) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
- seqOp:在同一分区下,利用seqOp对相同的键K对应的值V进行操作,并返回的类型为U的值(与给定的zeroValue类型相同)。
- combOp:在不同分区之间,利用combOp对相同K对应的V进行合并。
scala> val data = List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)) data: List[(Int, Int)] = List((1,3), (1,2), (1,4), (2,3), (3,6), (3,8)) scala> sc.parallelize(data).aggregateByKey(0.0)(math.max(_,_), _+_).foreach(println) (1,7.0) (2,3.0) (3,8.0) scala> sc.parallelize(data,1).aggregateByKey(0)(math.max(_,_), _+_).foreach(println) (1,4) (3,8) (2,3) // 分区:{1: [(1,3),(1,2),(1,4)], 2: [(2,3),(3,6),(3,8)] scala> sc.parallelize(data,2).aggregateByKey(0)(math.max(_,_), _+_).foreach(println) (1,4) (3,8) (2,3) // 分区:{1: [(1,3),(1,2)], 2: [(1,4),(2,3)] 3: [(3,6),(3,8)]} scala> sc.parallelize(data,3).aggregateByKey(0)(math.max(_,_), _+_).foreach(println) (1,7) (2,3) (3,8)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
-
aggregateByKey[U](zeroValue: U, numPartitions: Int)(seqOp: (U, V) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
- numPartitions:输出将使用numPartition进行散列分区。
-
sortByKey
-
用途
如果一组(K,V) 键值对中的键值实现了Ordered,那么在这组键值对上调用sortByKey,会返回一组按键值升序排序或降序排序的(K,V) 键值对。升序还是降序由布尔参数ascending决定。
-
使用示例
sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length): RDD[(K, V)]
- ascending:是否升序
- numPartitions:分区数
scala> val data = List((1,4),(2,3),(3,6),(1,5),(0,4),(4,2),(3,8)) data: List[(Int, Int)] = List((1,4), (2,3), (3,6), (1,5), (0,4), (4,2), (3,8)) scala> sc.parallelize(data).sortByKey(true,1).foreach(println) (0,4) (1,4) (1,5) (2,3) (3,6) (3,8) (4,2) scala> sc.parallelize(data).sortByKey(true,2).foreach(println) (0,4) (1,4) (1,5) (2,3) (3,6) (3,8) (4,2) scala> sc.parallelize(data).sortByKey(true,4).foreach(println) (2,3) <hr /> (0,4) (1,4) <hr /> (1,5) (4,2) <hr /> (3,6) (3,8) scala> sc.parallelize(data).sortByKey(false,4).foreach(println) (1,4) (1,5) (0,4) <hr /> (2,3) <hr /> (3,6) (3,8) <hr /> (4,2) scala> sc.parallelize(data).sortByKey(false,2).foreach(println) (2,3) (1,4) (1,5) (0,4) <hr /> (4,2) (3,6) (3,8)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
join
-
用途
当对类型(K,V)和(K,W)的数据集进行调用时,返回每个键的所有元素对数据集(K,(V,W))对。
-
使用示例
join[W](other: [RDD][(K, W)], numPartitions: Int): RDD[(K, (V, W))]
scala> val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c"),(4,"d"))) rdd1: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[170] at parallelize at <console>:25 scala> val rdd2 = sc.parallelize(Array((2,"B"),(3,"C"))) rdd2: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[171] at parallelize at <console>:25 // 内连接:左边和右边的数据只有存在才返回 scala> rdd1.join(rdd2).foreach(println) (2,(b,B)) (3,(c,C)) // 左连接:以左边的对象为主,只有存在的才会返回;如果左连接对应的数据为空,则返回None。 scala> rdd1.leftOuterJoin(rdd2).foreach(println) (2,(b,Some(B))) (3,(c,Some(C))) (1,(a,None)) (4,(d,None)) scala> rdd2.leftOuterJoin(rdd1).foreach(println) (2,(B,Some(b))) (3,(C,Some(c))) // 右连接:以右边的对象为主,只有存在的才会返回;如果右连接对应的数据为空,则返回None。 scala> rdd1.rightOuterJoin(rdd2).foreach(println) (2,(Some(b),B)) (3,(Some(c),C)) scala> rdd2.rightOuterJoin(rdd1).foreach(println) (3,(Some(C),c)) (1,(None,a)) (4,(None,d)) (2,(Some(B),b)) // 全外连接: scala> rdd1.fullOuterJoin(rdd2).foreach(println) (1,(Some(a),None)) (2,(Some(b),Some(B))) (3,(Some(c),Some(C))) (4,(Some(d),None)) scala> rdd2.fullOuterJoin(rdd1).foreach(println) (4,(None,Some(d))) (1,(None,Some(a))) (2,(Some(B),Some(b))) (3,(Some(C),Some(c)))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
cogroup
-
用途
类似于fullOuterJoin,只是返回的数据类型不同
-
使用示例
cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
scala> val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c"),(4,"d"))) rdd1: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[193] at parallelize at <console>:25 scala> val rdd2 = sc.parallelize(Array((2,"B"),(3,"C"))) rdd2: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[194] at parallelize at <console>:25 scala> rdd1.cogroup(rdd2).foreach(println) (3,(CompactBuffer(c),CompactBuffer(C))) (2,(CompactBuffer(b),CompactBuffer(B))) (1,(CompactBuffer(a),CompactBuffer())) (4,(CompactBuffer(d),CompactBuffer())) scala> rdd2.cogroup(rdd1).foreach(println) (1,(CompactBuffer(),CompactBuffer(a))) (2,(CompactBuffer(B),CompactBuffer(b))) (3,(CompactBuffer(C),CompactBuffer(c))) (4,(CompactBuffer(),CompactBuffer(d)))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
cartesian
-
用途
对两个数据集进行笛卡尔积计算
-
使用示例
cartesian[U](other: RDD[U])(implicit arg0: ClassTag[U]): RDD[(T, U)]
scala> val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c"),(4,"d"))) rdd1: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[193] at parallelize at <console>:25 scala> val rdd2 = sc.parallelize(Array((2,"B"),(3,"C"))) rdd2: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[194] at parallelize at <console>:25 scala> rdd1.cartesian(rdd2).foreach(println) ((1,a),(2,B)) ((2,b),(3,C)) ((3,c),(2,B)) ((3,c),(3,C)) ((1,a),(3,C)) ((4,d),(2,B)) ((4,d),(3,C)) ((2,b),(2,B))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
pipe
-
用途
通过shell命令管理RDD的每个分区
-
使用示例
pipe(command: String): RDD[String]
-
创建一个分区
#!/bin/bash # 文件名为echo.sh echo "Running shell script"; RESULT="";#变量两端不能直接接空格符 while read LINE; do RESULT=${RESULT}" "${LINE} done echo ${RESULT} > out123.txt
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
scala> val data = Array("my","name","is","cwc") data: Array[String] = Array(my, name, is, cwc) // 创建一个分区 scala> val rdd = sc.parallelize(data,1) rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:26 scala> val pipeRdd = rdd.pipe("./echo.sh") pipeRdd: org.apache.spark.rdd.RDD[String] = PipedRDD[1] at pipe at <console>:28 scala> pipeRdd.collect.foreach(println) Running shell script scala> pipeRdd.collect.foreach(println) Running shell script scala> val out123Rdd = sc.textFile("./out123.txt") out123Rdd: org.apache.spark.rdd.RDD[String] = ./out123.txt MapPartitionsRDD[6] at textFile at <console>:24 scala> out123Rdd.foreach(println) my name is cwc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
-
创建两个分区
#!/bin/bash echo "Running shell script"; RESULT="";#变量两端不能直接接空格符 while read LINE; do RESULT=${RESULT}" "${LINE} done # 对同一文件内容进行追加,否则后面的stdout会将前面的内容覆盖 echo ${RESULT} >> out123.txt
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
scala> val data = Array("my","name","is","cwc") data: Array[String] = Array(my, name, is, cwc) scala> val dataRdd = sc.parallelize(data,2) dataRdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[9] at parallelize at <console>:26 scala> val pipeRdd = dataRdd.pipe("./echo.sh") pipeRdd: org.apache.spark.rdd.RDD[String] = PipedRDD[10] at pipe at <console>:28 scala> pipeRdd.collect.foreach(println) // 两个分区运行了该脚本 Running shell script Running shell script scala> val out123RDD = sc.textFile("./out123.txt") out123RDD: org.apache.spark.rdd.RDD[String] = ./out123.txt MapPartitionsRDD[12] at textFile at <console>:24 scala> out123RDD.foreach(println) is cwc // 分区1输出 my name // 分区2输出
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
-
coalesce
-
用途
当把一个大型数据集过滤后,可以调用此方法将分区数减小到numPartitions,这样会更加有效的进行操作。
-
使用示例
coalesce(numPartitions: Int, shuffle: Boolean = false, partitionCoalescer: Option[PartitionCoalescer] = Option.empty)(implicit ord: Ordering[T] = null): RDD[T]
scala> val largeDatasetRdds = sc.parallelize(1 to 10000,10) largeDatasetRdds: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[25] at parallelize at <console>:24 scala> val largeDatasetAfterFilterRdds = largeDatasetRdds.filter(_%5==0) largeDatasetAfterFilterRdds: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[26] at filter at <console>:26 scala> val largeDatasetAfterFilterWithIndexRdds = largeDatasetAfterFilterRdds.mapPartitionsWithIndex((index,iter) => iter.toList.map((index,_)).iterator) largeDatasetAfterFilterWithIndexRdds: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[27] at mapPartitionsWithIndex at <console>:28 scala> largeDatasetAfterFilterWithIndexRdds.aggregateByKey(0)((counter,value) => counter+1, _+_).foreach(println) (2,200) (3,200) (4,200) (0,200) (1,200) (5,200) (6,200) (7,200) (8,200) (9,200) // 数据过滤后降低分区数 scala> val largeDatasetAfterCoalesceRdds= largeDatasetAfterFilterRdds.coalesce(5) largeDatasetAfterCoalesceRdds: org.apache.spark.rdd.RDD[Int] = CoalescedRDD[29] at coalesce at <console>:28 scala> val largeDatasetAfterCoalesceWithIndexRdds = largeDatasetAfterCoalesceRdds.mapPartitionsWithIndex((index,iter) => iter.toList.map((index,_)).iterator) largeDatasetAfterCoalesceWithIndexRdds: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[30] at mapPartitionsWithIndex at <console>:30 scala> largeDatasetAfterCoalesceWithIndexRdds.aggregateByKey(0)((counter,value) => counter+1, _+_).foreach(println) (0,400) (1,400) (2,400) (3,400) (4,400) // 对于小数据集可以是由countByKey scala> largeDatasetAfterCoalesceWithIndexRdds.countByKey() res17: scala.collection.Map[Int,Long] = Map(0 -> 400, 1 -> 400, 2 -> 400, 3 -> 400, 4 -> 400)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
repartition
-
用途
Shuffle当前RDD中的数据,并重新分配到到numPartition个分区,这期间会通过网络来传输所有数据。
-
使用示例
repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
-
使用注意
如果尝试减少此RDD中的分区数,请考虑使用coalesce,这可以避免执行shuffle。
repartitionAndSortWithinPartitions
-
用途
根据给定的分区器对RDD进行重新分区,并对每个分区的数据按键值进行排序。
-
使用示例
repartitionAndSortWithinPartitions(partitioner: Partitioner): RDD[(K, V)]
结束操作(Actions)
reduce
-
用途
通过给定的函数来聚合RDD中的元素
-
使用示例
reduce(f: (T, T) ⇒ T): T
scala> val rdd = sc.parallelize(Array(1,2,3,4,5,6)) rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[34] at parallelize at <console>:24 scala> rdd.reduce(_+_) res18: Int = 21
- 1
- 2
- 3
- 4
- 5
collect
-
用途
在驱动程序中将数据集的所有元素作为数组返回。该操作适用于经过过滤或其他操作后足够小的数据集。
-
使用示例
-
collect(): Array[T]
scala> val rdd = sc.parallelize(Array(1,2,3,4,5,6)) rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[35] at parallelize at <console>:24 scala> rdd.filter(_%2==0).collect res19: Array[Int] = Array(2, 4, 6)
- 1
- 2
- 3
- 4
- 5
-
collect[U](f: PartialFunction[T, U])(implicit arg0: ClassTag[U]): RDD[U]
- f:一个PartialFunction[A, B]类型的偏函数是一元函数,其域并不需要包括类型A*的所有值,isDefinedAt函数允许你动态地测试值是否在函数的有效域中。
scala> val data = Array(2,43,1,4,2,0,22,76) data: Array[Int] = Array(2, 43, 1, 4, 2, 0, 22, 76) scala> val dataRdd = sc.parallelize(data) dataRdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[44] at parallelize at <console>:26 // 定义偏函数 scala> val convert1To6 = new PartialFunction[Int,String] with Serializable { | val nums = Array("one", "two", "three", "four", "five") | override def isDefinedAt(i: Int): Boolean = i > 0 && i < 6 | override def apply(v1: Int): String = nums(v1 - 1) | } convert1To6: PartialFunction[Int,String] with Serializable{val nums: Array[String]} = <function1> scala> val convertedRdd = dataRdd.collect(convert1To6) 17/09/09 01:37:40 WARN ClosureCleaner: Expected a closure; got $line76.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anon$1 17/09/09 01:37:40 WARN ClosureCleaner: Expected a closure; got $line76.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$anon$1 convertedRdd: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[46] at collect at <console>:32 scala> convertedRdd.foreach(println) two one four two
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
-
count
-
用途
返回数据集中元素的个数
-
使用示例
count(): Long
scala> val data = Array(2,43,1,4,2,0,22,76) data: Array[Int] = Array(2, 43, 1, 4, 2, 0, 22, 76) scala> sc.parallelize(data).count res25: Long = 8
- 1
- 2
- 3
- 4
- 5
first
-
用途
返回数据集中的第一个元素,类似于take(1)
-
使用示例
first(): T
scala> val data = Array(2,43,1,4,2,0,22,76) data: Array[Int] = Array(2, 43, 1, 4, 2, 0, 22, 76) scala> sc.parallelize(data).first res26: Int = 2
- 1
- 2
- 3
- 4
- 5
take
-
用途
以数组形式返回数据集中前n个元素
-
使用示例
take(num: Int): Array[T]
scala> val data = Array(2,43,1,4,2,0,22,76) data: Array[Int] = Array(2, 43, 1, 4, 2, 0, 22, 76) scala> sc.parallelize(data).take(3) res27: Array[Int] = Array(2, 43, 1)
- 1
- 2
- 3
- 4
- 5
takeSample
-
用途
以数组形式返回固定数量的RDD字数据集
-
使用示例
takeSample(withReplacement: Boolean, num: Int, seed: Long = Utils.random.nextLong): Array[T]
- withReplacement:是否替换数据
- num:样本数量
- seed:种子随机数发生器
scala> val data = Array(2,43,1,4,2,0,22,76) data: Array[Int] = Array(2, 43, 1, 4, 2, 0, 22, 76) scala> val dataRdd = sc.parallelize(data) dataRdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[50] at parallelize at <console>:26 scala> dataRdd.takeSample(true,2) res28: Array[Int] = Array(4, 2) scala> dataRdd.takeSample(false,4) res29: Array[Int] = Array(43, 1, 2, 76) scala> dataRdd.takeSample(false,8) res30: Array[Int] = Array(1, 4, 2, 43, 76, 2, 22, 0) scala> dataRdd.takeSample(true,8) res31: Array[Int] = Array(76, 43, 4, 22, 4, 22, 4, 1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
takeOrdered
-
用途
以自然顺序或用户定义的比较器计算的顺序后,返回RDD中前n个元素
-
使用示例
takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
scala> sc.parallelize(Array(2,4,3,1,5,3)).takeOrdered(3) res32: Array[Int] = Array(1, 2, 3) scala> sc.parallelize(Array(1,2,3,4,5,6)).takeOrdered(3) res33: Array[Int] = Array(1, 2, 3)
- 1
- 2
- 3
- 4
- 5
saveAsTextFile
-
用途
该方法可将RDD中的元素写到指定目录的文本文件中,该目录可以使本地文件系统、HDFS或任何其他Hadoop支持的文件系统给定的目录。对于每个元素,Spark会调用它的toString方法,转化为每个文件的行。
-
使用示例
saveAsTextFile(path: String): Unit
scala> val hello = Array("hello","spark") hello: Array[String] = Array(hello, spark) // 将helloRdd存储到saveAsTextFileTest目录下的文件中,并指定3个分区 scala> sc.parallelize(hello,3).saveAsTextFile("./saveAsTextFileTest")
- 1
- 2
- 3
- 4
[root@master saveAsTextFileTest]# ls part-00000 part-00001 part-00002 _SUCCESS [root@master saveAsTextFileTest]# cat part-00000 // 有一个分区数据为空 [root@master saveAsTextFileTest]# cat part-00001 hello [root@master saveAsTextFileTest]# cat part-00002 spark
- 1
- 2
- 3
- 4
- 5
- 6
- 7
countByKey
-
用途
统计每个键的元素数量,将结果以Map类型返回。
-
使用示例
countByKey(): Map[K, Long]
scala> val scoreRdd = sc.parallelize(Array(("cwc",98),("cwc",34),("david",56))) scoreRdd: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[65] at parallelize at <console>:24 scala> scoreRdd.countByKey() res37: scala.collection.Map[String,Long] = Map(david -> 1, cwc -> 2)
- 1
- 2
- 3
- 4
- 5
-
使用注意
只有在预计生成的Map结果比较情况下才使用该方法,因此整个RDD数据都需要加载到内存中。
如果需要处理较大的数据量,考虑使用
rdd.aggregateByKey(0)((counter,value) => counter+1,_+_)
方法,并返回RDD[T, Long] 类型的数据。
foreach
-
用途
在数据集的每个元素上执行func函数,该方法通常用于副作用的操作,就像更新累加器或与外部存储系统交互
-
使用注意
修改foreach之外的变量而不是累加器,有可能导致未定义的行为结果。具体可见上文中的闭包。
Shuffle操作
Spark内部的某些操作会触发称为Shuffle的事件,Shuffle是Spark**重新分配数据的机制**。这通常涉及到跨执行器之间与机器之间的数据复制操作,从而使得Shuffle变得复杂并且花费昂贵。
背景
为了了解Shuffle期间发生了什么,我们可以考虑一下reduceByKey操作(reduceByKey使用给定的函数对每个相同键的值进行聚合操作,并返回一个新的RDD)。该操作的挑战在于,并不是每个键对应的所有值都会在同一个分区内,或者在同一个机器上,但是他们必须位于同一位置才能计算结果。
在Spark中,数据通常情况下不会跨分区分布,而是针对特定操作分布在必要的位置。在计算期间,单个任务会在单个分区上执行。因此,为了单个reduceByKey
的reduce任务能够执行,Spark需要执行一个all-to-all的操作来组织所有的数据。它必须读取所有的分区去查找所有键的所有值,然后跨分区将某个键的所有值汇集在一起,以便计算最终结果——这就称之为Shuffle。
虽然新混洗后数据的每个分区中的元素集是确定的,分区本身的顺序也是如此,但是分区内的数据集的元素却没有顺序。因此,如果需要Shuffle后得到一个有序的数据集,则可以执行以下操作:
- 利用
mapPartitions
,对分区内的元素使用sorted
排序; - 利用
repartitionAndSortWithinPartitions
对分区内元素进行排序并重新分区; - 对RDD使用
sortBy
方法进行全局排序;
可能会导致Shuffle发生的操作包括:
- repartition操作:repartition,coalesce;
- ByKey操作:groupByKey,reduceByKey,除了countByKey;
- join操作:cogroup、join;
性能影响
Shuffle过程非常耗费资源,因为它涉及到磁盘I/O、数据序列化和网络I/O。为了为Shuffle的过程组织数据,Spark会生成一组任务——包括一组用于组织数据的map任务和一组用于聚合数据的reduce任务(其中map
任务和reduce
任务的命名法来自于MapReduce,并不直接与Spark的map
和reduce
操作有关)。
在内部,单独的map任务结果都保存在内存中,直到内存无法容纳为止。接着,这些结果会根据目标分区进行排序并写入到单个文件中。在reduce端,reduce任务会从map端读取相关的排序块。
某些Shuffle操作会消耗大量的堆内存,因为records在传输前后,都会使用内存中的数据结构来存储。特别的,reduceByKey
和aggregateByKey
会在map端创建这些结构,类似*ByKey
的操作会在reduce端生成。当内存无法装下这些数据后,将会从tables中溢出到磁盘,这会导致额外的磁盘IO开销,导致垃圾收集的次数增加。
Shuffle过程也会在磁盘中生成大量临时的文件。在Spark 1.3中,这些文件会被保存到相应的RDD不再被使用并且都已被垃圾回收。临时文件的存在保证了相关操作被重新执行后不需要再次重新生成文件,并且垃圾回收只可能在很长一段时间内才会发生。这同时也意味着长时间运行的Spark作业也会消耗大量的磁盘空间(临时存储目录由参数spark.local.dir
指定)。
另外,可以通过各种配置参数来调整Shuffle的行为,具体可见Spark的配置
RDD持久化
Spark最重要的能力就是在操作中将数据集持久化到内存中。当持久化一个RDD时,包含该RDD分区的节点都会进行持久化,在未来将其重用于该数据集上的其他操作上,并且更加快速(通常超过10倍)。缓存也是迭代算法和快速交互使用的关键工具。
通常可以调用的persis()或cache()方法进行持久化操作,该操作会在第一次结束操作(action)执行时被计算,RDD则会被保存到节点的内存中。Spark的缓存是支持容错的——如果任何RDD的分区丢失,它将使用最初创建它的转换操作(transformations)自动重新计算。
持久化级别
此外,可以使用不同的存储级别来持久化RDD。例如,持久化到磁盘中、以序列化Java对象的方式持久化到内存中(节省空间)或者以多副本跨界点存储。这些需求都可以向persist()
传递StorageLevel对象的方式来实现。另外,cache()
方法默认存储级别(StorageLevel.MEMORY_ONLY)的简写。
存储级别 | 描述 |
---|---|
MEMORY_ONLY | 默认存储级别。RDD将会以Java对象的方式存储在JVM中, 如果RDD不适合内存,某些分区将不会被缓存,每次需要时都会重新计算。 |
MEMORY_AND_DISK | RDD将会以Java对象的方式存储在JVM中, 如果RDD不适合内存,请存储不适合磁盘的分区,并在需要时从那里读取。 |
MEMORY_ONLY_SER | 以序列化的方式存储RDD。这种方式更加的节省空间,尤其使用fast serializer时,但是读的时候会消耗更多的CPU。 |
MEMORY_AND_DISK_SER | 与MEMORY_ONLY_SER类似,但将不适合内存的分区溢出到磁盘,而不是每次需要重新计算它们。 |
DISK_ONLY | 将RDD分区全部存储到磁盘中 |
MEMORY_ONLY_2, MEMORY_AND_DISK_2 | 跟上面的一样,但是会在集群节点上放置存储两个副本 |
OFF_HEAP (experimental) | 类似于MEMORY_ONLY_SER,但是是将数据存储在堆外内存,这需要启用堆外内存。 |
Spark也会自动持久化(用户没有主动调用persist
)一些Shuffle过程中的中间数据,这样做是为了避免在Shuffle期间节点失败后重新计算整个输入。所以建议用户调用persist
,如果需要重用RDD的结果。
如何选择持久化级别
Spark的存储级别旨在提供内存使用和CPU效率之间的不同权衡,因此建议通过以下过程来选择一个:
- 如果你的RDDs适合默认的存储级别,则不用管。这是CPU效率最高的选项,允许RDD上的操作尽可能快地运行;
- 如果不适合默认的存储级别,那么尝试使用
MEMORY_ONLY_SER
并选择一个快速的序列化库来使对象更加节省空间,但仍然能够快速访问。 - 尽量不要溢出数据到磁盘,除非对数据集计算的消耗非常大,或者对数据集进行了大规模的过滤。否则,重新计算分区就可能与从磁盘读取分区一样快了。
- 如果想要快速故障恢复,则使用副本存储级别。所有的存储级别都通过重新计算丢失的数据来提供完整的容错能力,但是副本存储级别让你可以继续在RDD上运行任务,而不用等待重新计算丢失的分区。
移除数据
Spark会自动监控每个节点的缓存使用,并使用LRU(least-recently-used)策略删除旧的分区数据。也可以使用RDD.unpersist()
来手动移除数据。