三种创建方式
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8))
val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8))
运行在集群中的Spark API 强依赖于 driver 程序中给RDD传入的函数。官方推荐以下两种方式:
object MyFunctions {
def func1(s: String): String = { ... }
}
myRdd.map(MyFunctions.func1)
除了使用静态方法以外,编码中也会出现传递一个实例方法的引用,但是这样会导致整个实例对象会被序列化发送到集群:
//等同于rdd.map(x => this.func1(x))
class MyClass {
def func1(s: String): String = { ... }
def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
}
如果抛序列化的异常,使类继承scala.Serializable即可。
访问类实例属性也有类似情况。
//等同于rdd.map(x => this.field + x)
class MyClass {
val field = "Hello"
def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) }
}
//为了避免这种情况可以这么做
def doStuff(rdd: RDD[String]): RDD[String] = {
val field_ = this.field
rdd.map(x => field_ + x)
}
理解Spark集群中执行的变量、方法的作用域和生命周期是个难点。
RDD 操作其作用域范围以外的变量常常会带来迷惑。
以下例子,就算运行在local模式同一JVM中,结果也跟想象不同:
var counter = 0
var rdd = sc.parallelize(data)
// Wrong: Don't do this!!
rdd.foreach(x => counter += x)
println("Counter value: " + counter)
Job运行过程中,Spark将RDD操作分割成一个个Task,序列化后分发到Executor上执行。Executor执行的内容称作闭包。这个闭包中的变量、方法必须对Executor可见,比如上例foreach()中涉及的部分。我们知道java对象经过序列化和反序列化以后,旧对象跟新对象是不一样的,所以Executor上的新闭包是一个副本,修改也是作用在副本上。所以上例打印的是0。
类似的情况,比如使用 rdd.foreach(println) or rdd.map(println)打印RDD的数据。在local模式单机上跑,Exexutor、Driver都在一个JVM中,可以在控制台上看到打印,但是在集群模式下,它是在Exexutor上打印,而不是在Driver端打印。要想在Driver上打印,需要使用collect()把整个RDD的数据抓取过来再打印,如果数据量大可能OOM,安全的做法是使用take(),比如rdd.take(100).foreach(println)
从操作对象上,分为value 类型和 key-value类型
value类型
map(func)
意义:将每一个输入元素经过func函数转换映射成新元素
scala> var source = sc.parallelize(1 to 10)
scala> val mapadd = source.map(_ * 2)
scala> mapadd.collect()
res8: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
filter(func)
意义:过滤func函数计算后返回值为true的元素
scala> var sourceFilter = sc.parallelize(Array("laozhang","laoli","woqu","daye"))
scala> val filter = sourceFilter.filter(_.contains("lao"))
scala> filter.collect()
res5: Array[String] = Array(laozhang, laoli)
flatMap(func)
意义:将每一个输入元素经过func函数转换映射成新的0或多个元素(func返回一个Seq )
scala> val sourceFlat = sc.parallelize(1 to 5)
scala> val flatMap = sourceFlat.flatMap(1 to _)
scala> flatMap.collect()
res22: Array[Int] = Array(1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5)
mapPartitions(func)
意义:以每一个分区为单位经func函数处理(func类型Iterator[T] => Iterator[U])
跟map的区别:
map每次处理一条数据。mapPartition每次处理一个分区,每个分区处理完以前,数据不能回收,可能导致OOM,但是效率比map高。
scala> val rdd = sc.parallelize(Array(1,2,3,4))
scala> val mapRdd = rdd.mapPartitions(x=>x.map(_*2))
scala> mapRdd .collect()
res15: Array[Int] = Array(2, 4, 6, 8)
mapPartitionsWithIndex(func)
意义:以每一个分区为单位经func函数处理,多了一个分区号(func类型(Int, Interator[T]) => Iterator[U])
scala> val rdd = sc.parallelize(Array(1,2,3),2)
scala> val indexRdd = rdd.mapPartitionsWithIndex((index,itr)=>(itr.map((index,_))))
scala> indexRdd.collect()
res15: Array[(Int, Int)] = Array((0,1), (1,2), (1,3))
glom(func)
意义:把每一个分区的数据整个作为一个数组的元素
scala> val rdd = sc.parallelize(1 to 16,4)
scala> rdd.glom().collect()
res17: Array[Array[Int]] = Array(Array(1, 2, 3, 4), Array(5, 6, 7, 8), Array(9, 10, 11, 12), Array(13, 14, 15, 16))
groupBy(func)
意义:按func函数的返回值进行分组
scala> val rdd = sc.parallelize(1 to 4)
scala> val group = rdd.groupBy(_%2)
scala> group.collect
res9: Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4)), (1,CompactBuffer(1, 3)))
sample(withReplacement,fraction,seed)
意义:随机抽样出fraction比例数量的数据,withReplacement表示抽出的数据是否放回,true有放回,false无放回,seed随机生成器种子
scala> val rdd = sc.parallelize(1 to 10)
scala> var sample1 = rdd.sample(true,0.4,2)
scala> sample1.collect()
res10: Array[Int] = Array(1, 5, 5, 6, 7, 7, 8, 9)
scala> var sample2 = rdd.sample(false,0.8,12)
scala> sample2.collect()
res11: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
distinct([numTasks])
意义:去重。可选参数numTasks指并行度
scala> val dataRdd = sc.parallelize(List(1,1,1,5,9,9,2,1))
scala> val disRDD = dataRdd.distinct()
scala> disRDD.collect
res0: Array[Int] = Array(1, 2, 9, 5)
coalesce(numPartitions)
意义:减少分区数。可以指定是否shuffle
scala> val rdd = sc.parallelize(1 to 16,4)
scala> rdd.getNumPartitions
res1: Int = 4
scala> val coalesceRDD = rdd.coalesce(2)
scala> coalesceRDD.getNumPartitions
res2: Int = 2
repartition(numPartitions)
意义:重分区。是coalesce的封装,一定执行shuffle
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}
scala> val rdd = sc.parallelize(1 to 16,4)
scala> rdd.getNumPartitions
res3: Int = 4
scala> val reRdd = rdd.repartition(2)
scala> reRdd.getNumPartitions
res4: Int = 2
sortBy(func,[ascending], [numTasks])
意义:以func函数返回值大小来排序,默认正序
scala> val rdd = sc.parallelize(List(2,3,1,4))
//按自身大小排序
scala> rdd.sortBy(x => x).collect()
res5: Array[Int] = Array(1, 2, 3, 4)
//按余数大小排序
scala> rdd.sortBy(x => x%2).collect()
res6: Array[Int] = Array(2, 4, 1, 3)
pipe(command, [envVars])
意义:对每个分区执行脚本(Perl 或 bash)
举例脚本:
#!/bin/sh
while read line; do
echo "---->"${line}
done
scala> val rdd = sc.parallelize(List("hello","world","lao","zhang"),1)
scala> rdd.pipe("/home/hadoop/spark/pipe.sh").collect()
res19: Array[String] = Array(---->hello, ---->world, ---->lao, ---->zhang)
union(otherDataset)
意义:两个RDD做并集
scala> val rdd1 = sc.parallelize(1 to 5)
scala> val rdd2 = sc.parallelize(5 to 8)
scala> val rdd3 = rdd1.union(rdd2)
scala> rdd3.collect
res7: Array[Int] = Array(1, 2, 3, 4, 5, 5, 6, 7, 8)
subtract (otherDataset)
意义:两个RDD做差集
scala> val rdd = sc.parallelize(1 to 6)
scala> val rdd1 = sc.parallelize(4 to 10)
scala> rdd1.subtract(rdd).collect
res0: Array[Int] = Array(7, 8, 9, 10)
intersection (otherDataset)
意义:两个RDD做交集
scala> val rdd1 = sc.parallelize(1 to 7)
scala> val rdd2 = sc.parallelize(5 to 10)
scala> rdd1.intersection(rdd2).collect
res1: Array[Int] = Array(6, 7, 5)
cartesian(otherDataset)
意义:两个RDD做笛卡尔积。是个重操作
scala> val rdd1 = sc.parallelize(1 to 3)
scala> val rdd2 = sc.parallelize(2 to 5)
scala> rdd1.cartesian(rdd2).collect()
res2: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (1,5), (2,2), (2,3), (2,4), (2,5), (3,2), (3,3), (3,4), (3,5))
zip(otherDataset)
意义:将两个RDD拉链。要求两个RDD分区数、元素个数都必须一致,否则抛异常
scala> val rdd1 = sc.parallelize(Array(1,2,3),3)
scala> val rdd2 = sc.parallelize(Array("one","two","three"),3)
scala> rdd1.zip(rdd2).collect
res3: Array[(Int, String)] = Array((1,one), (2,two), (3,three))
key-value类型
groupByKey([numPartitions])
意义:按每个key分组,返回包含value的Iterable
若仅仅是做一个分组求和或平均的聚合操作,reduceByKey 或 aggregateByKey 比 groupByKey 有更好的性能。比如reduceByKey 在shuffle之前会按key进行merge,类似MapReduce的combine,数据量大减
scala> val words = Array("one", "two", "two", "three", "three", "three")
scala> val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
scala> val group = wordPairsRDD.groupByKey()
scala> group.collect()
res5: Array[(String, Iterable[Int])] = Array((three,CompactBuffer(1, 1, 1)), (two,CompactBuffer(1, 1)), (one,CompactBuffer(1)))
scala> group.map(t => (t._1, t._2.sum)).collect
res6: Array[(String, Int)] = Array((three,3), (two,2), (one,1))
reduceByKey(func, [numPartitions])
意义:按每个key分组来聚合操作,func类型必须是(V,V) => V
scala> val rdd = sc.parallelize(List(("a",1),("b",2),("b",3),("a",4)))
scala> val reduce = rdd.reduceByKey((x,y) => x+y).collect
reduce: Array[(String, Int)] = Array((a,5), (b,5))
aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions])
zeroValue:对每一个分区中的每一个key操作的初始值
seqOp:在每一个分区中用初始值按key迭代value进行函数运算
combOp:合并每个分区中的结果
意义:先在每个分区中按key进行分组,每一个key组使用zeroValue与每一个value执行combOp函数运算,得到一个新的key-value对。此时,每个分区生成1或多个k-v对。然后在合并阶段,将每个分区生成的k-v对按照key再分组,每个key分组使用combOp函数运算每一个value,得到合并后新的k-v对。
scala> val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
scala> val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)
scala> agg.collect()
res7: Array[(String, Int)] = Array((b,3), (a,3), (c,12))
foldByKey(zeroValue)(func, [numPartitions])
意义:是aggregateByKey简化,seqop和combop变成相同的func
scala> val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
scala> val agg = rdd.foldByKey(0)(_+_)
scala> rdd.foldByKey(0)(_+_).collect
res8: Array[(Int, Int)] = Array((3,14), (1,9), (2,3))
combineByKey(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
createCombiner:使用每一种key的第一个kv对和初始值创建Combinner形成新的ck-cv对。其中ck是第一个kv的v,cv是初始值
mergeValue:在分区中,使用Combinner形成新的ck-cv与剩下kv对的v进行运算,形成新的(k,(ck-cv))
mergeCombiners:分区间进行按k进行shuffle执行mergeCombiners合并,进而形成新的(k,(ck-cv))
意义:是aggregateByKey简化,seqop和combop变成相同的func
scala> val rdd = sc.parallelize(Array(("a", 80), ("b", 90), ("a", 95), ("b", 95), ("a", 90), ("b", 100)),2)
scala> val combine = rdd.combineByKey((_,1),(acc:(Int,Int),v)=>(acc._1+v,acc._2+1),(acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2))
scala> combine.collect
res9: Array[(String, (Int, Int))] = Array((b,(285,3)), (a,(265,3)))
sortByKey([ascending], [numPartitions])
意义:按key进行排序(k,v)。要求k必须实现Ordered接口
scala> val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
scala> rdd.sortByKey(true).collect()
res10: Array[(Int, String)] = Array((1,dd), (2,bb), (3,aa), (6,cc))
mapValues(func)
意义:仅对k-v中的v操作func运算
scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
scala> rdd.mapValues("<"+_+">").collect()
res11: Array[(Int, String)] = Array((1,<a>), (2,<b>), (3,<c>))
__ join(otherDataset, [numTasks])__
意义:按key连接两个RDD的数据。连接后的v是个tuple
scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
scala> val rdd1 = sc.parallelize(Array((1,"e"),(2,"f"),(3,"g")))
scala> rdd.join(rdd1).collect()
res12: Array[(Int, (String, String))] = Array((1,(a,e)), (2,(b,f)), (3,(c,g)))
cogroup(otherDataset, [numTasks])
意义:按key分别聚合两个RDD的数据。联合分组后(key,(Iterable,Iterable))
scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
scala> val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))
scala> rdd.cogroup(rdd1).collect()
res13: Array[(Int, (Iterable[String], Iterable[Int]))] = Array((1,(CompactBuffer(a),CompactBuffer(4))), (2,(CompactBuffer(b),CompactBuffer(5))), (3,(CompactBuffer(c),CompactBuffer(6))))
reduce(func)
意义:先聚合分区内数据,再聚合分区间数据
scala> val rdd = sc.makeRDD(Array(("a",1),("b",3),("c",3),("d",5)))
scala> rdd.reduce((x,y)=>(x._1 + y._1,x._2 + y._2))
res15: (String, Int) = (bcad,12)
collect()
意义:一般使用在Driver中收集所有数据
count()
意义:统计元素个数
first()
意义:取第一个元素
take(n)
意义:取前n个元素
takeOrdered(n)
意义:取排序后的前n个元素
aggregate(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
意义:分区内先将每个元素使用seqOp和初始值进行聚合,然后再使用combOp进行分区间进行运算。返回值不要求与RDD数据类型一致
scala> var rdd1 = sc.makeRDD(1 to 10,2)
scala> rdd1.aggregate(0)(_+_,_+_)
res17: Int = 55
fold(zeroValue)(func)
意义:aggregate的简化,seqOp和combOp一样同为func。
scala> var rdd = sc.makeRDD(1 to 10,2)
scala> rdd.fold(0)(_+_)
res17: Int = 55
saveAsTextFile(path)
意义:以文本形式保存到HDFS或者其他支持的文件系统
saveAsTextFile(path)
意义:以文本形式保存到HDFS或者其他支持的文件系统
__saveAsSequenceFile(path) __
意义:以Hadoop sequencefile格式的形式保存到HDFS或者其他支持的文件系统
saveAsObjectFile((path)
意义:以序列化的形式保存元素到HDFS或者其他支持的文件系统
countByKey()
意义:按照key统计元素的个数
foreach(func)
意义:对每一个元素迭代执行func
getNumPartitions
意义:获取当前RDD分区数
toDebugString
意义:查看当前RDD的血统(即依赖关系)
dependencies
意义:查看当前RDD的依赖类型(宽窄依赖)