文章目录
RDD转换算子
功能的补充和封装,将旧的RDD包装成新的RDD
RDD根据数据处理方式的不同将算子整体上分为value类型、双value类型和Key-Value类型
value类型
map
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
val rdd = sc.parallelize(Array(1,2,3,4))
val mapRDD = rdd.map(_*2)
mapRDD.collect.foreach(println)
//2
//4
//6
//8
mapPartitions
可以以分区为单位进行数据转换操作,但是会将整个分区的数据加载到内存中进行引用,如果处理完的数据时不会被释放的,存在对象的引用。
val rdd = sc.makeRDD(Array(1,2,3,4),2)
val mp = rdd.mapPartitions(
iter => {
println("------")
iter.map(_*2)
}
)
mp.collect.foreach(println)
//------
//------
//2
//4
//6
//8
区别:
map算子是分区内一个数据一个数据的执行,mapPartitions算子是以分区为单位进行批处理操作
map算子主要的目的是将数据源中的数据进行转换和改变,但是不会减少或增多数据。mapPartitions算子需要传递一个迭代器,返回一个迭代器,没有要求元素的个数保持不变,所以可以增加或减少数据。
map算子类似于串行操作,所以性能比较低,mapPartitions算子类似于批处理,所以性能比较高,但是会长时间占用内存,导致内存可能不够用,出现内存溢出的错误,所以在内存有限的情况下,不推荐使用。
mapPartitionsWithIndex
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理
val rdd = sc.makeRDD(Array(1,2,3,4),2)
val mp = rdd.mapPartitionsWithIndex(
(index,iter) => {
if(index ==1){
iter
}else{
Nil.iterator
}
}
)
mp.collect.foreach(println)
//3
//4
flatMap
将处理的数据进行扁平化后再进行映射处理
val rdd = sc.makeRDD(Array(Array(1,2),Array(3,4)))
rdd.flatMap(x=>x).collect.foreach(println)
//1
//2
//3
//4
glom
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
val rdd = sc.makeRDD(Array(1,2,3,4),2)
val value = rdd.glom()
value.collect.foreach(x=>println(x.mkString(",")))
1,2
3,4
分区内取最大值,两个分区之间最大值相加
val rdd = sc.makeRDD(Array(1,2,3,4),2)
val value = rdd.glom()
val maxValue = value.map(array=>array.max)
println(maxValue.collect.sum)
//6
groupBy
将数据根据指定的规则进行分组,分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。
val rdd = sc.makeRDD(Array(1,2,3,4),2)
val group = rdd.groupBy(x=>x%2)
group.collect.foreach(println)
(0,CompactBuffer(2, 4))
(1,CompactBuffer(1, 3))
根据首写字母进行分组
val rdd = sc.makeRDD(Array("Hello","Spark","Scala","Hadoop"),2)
val group = rdd.groupBy(_.charAt(0))
group.collect.foreach(println)
(H,CompactBuffer(Hello, Hadoop))
(S,CompactBuffer(Spark, Scala))
分组和分区没有必然的关系
filter
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃,当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜
val rdd = sc.makeRDD(Array(1,2,3,4))
val fi = rdd.filter(x=>x%2==0)
fi.collect.foreach(println)
//2
//4
sample
根据指定的规则从数据集中抽取数据
val rdd = sc.makeRDD(Array(1,2,3,4,5,6,7,8,9,10))
//第一个参数,抽取数据后是否将数据放回
//第二个参数,数据源中每条数据被抽取的概率
//基准值的概念
//第三个参数,抽取数据随机算法的种子
//如果不传递第三个参数,使用的是当前系统时间
println(rdd.sample(false, 0.4)
.collect.mkString(","))
//2,3,4,5,9
distinct
去重
val rdd = sc.makeRDD(Array(1,2,3,4,1,2,3,4))
val di = rdd.distinct()
di.collect.foreach(println)
//1 2 3 4
coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
val rdd = sc.makeRDD(Array(1,2,3,4,5,6),3)
//coalesce默认情况下不会将分区的数据打乱重新组合
//这种情况下的缩减分区会导致数据不均衡,出现数据均衡
//如果想要让数据均衡,可以进行shuffle处理
//val newRDD = rdd.coalesce(2)
val newRDD = rdd.coalesce(2,true)
newRDD.saveAsTextFile("out")
//1 3 5
//2 4 6 shuffle的效果
repartition
扩大分区
val rdd = sc.makeRDD(Array(1,2,3,4,5,6),2)
//coalesce是可以扩大分区的,但是如果不进行shuffle操作是没有意义的
//spark提供了简化操作
//缩减分区:coalesce 想要数据均衡,可以采用shuffle
//扩大分区:repartition
//val newRDD = rdd.coalesce(3,true)
val newRDD = rdd.repartition(3)
newRDD.saveAsTextFile("out")
sortBy
val rdd = sc.makeRDD(Array(3,5,2,6,1,4),2)
val sortRDD = rdd.sortBy(x=>x) //升序
sortRDD.collect.foreach(println)
val rdd = sc.makeRDD(Array(("1",2),("11",3),("2",4)),2)
val sortRDD = rdd.sortBy(x=>x._1)
sortRDD.collect.foreach(println)
(1,2)
(11,3)
(2,4)
双value类型
intersection union subtract zip
val rdd1 = sc.makeRDD(Array(1,2,3,4))
val rdd2 = sc.makeRDD(Array(3,4,5,6))
//交集
val rdd3 = rdd1.intersection(rdd2)
println(rdd3.collect.mkString(","))
//并集
val rdd4 = rdd1.union(rdd2)
println(rdd4.collect.mkString(","))
//差集
val rdd5 = rdd1.subtract(rdd2)
println(rdd5.collect.mkString(","))
//拉链
val rdd6 = rdd1.zip(rdd2)
println(rdd6.collect.mkString(","))
3,4
1,2,3,4,3,4,5,6
1,2
(1,3),(2,4),(3,5),(4,6)
key-value类型
partitionBy
将数据按照指定partitioner重新进行分区,spark默认的分区器是HashPartitioner
val rdd = sc.makeRDD(Array(1,2,3,4))
val mapRDD = rdd.map((_,1))
//根据规定的分区规则对数据进行重分区
mapRDD.partitionBy(new HashPartitioner(2)).saveAsTextFile("out")
(2,1)
(4,1)在一个分区
(1,1)
(3,1)
reduceByKey
reduceByKey分区内和分区间计算规则是相同的
val rdd = sc.makeRDD(Array(("a",1),("a",2),("a",3),("b",4)))
//reduceByKey:相同的key进行value数据的聚合操作
//如果key的数据只有一个时,是不会参与计算的
rdd.reduceByKey(_+_).collect.foreach(println)
(a,6)
(b,4)
groupByKey
val rdd = sc.makeRDD(Array(("a",1),("a",2),("a",3),("b",4)))
//groupByKey 将数据源中的数据,相同的key的数据分在一个组中,形成一个对偶元组
//元组中的第一个元素是key
//元组中的第二个元素就是相同key的value的集合
val group = rdd.groupByKey()
group.collect.foreach(println)
//(a,CompactBuffer(1, 2, 3))
//(b,CompactBuffer(4))
aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算
val rdd = sc.makeRDD(Array(("a",1),("a",2),("a",3),("a",4)),2)
//aggregateByKey存在函数柯里化,有两个参数列表
//第一个参数列表:初始值
//第二个参数列表:
//第一个参数表示分区内计算规则
//第二个参数表示分区间计算规则
rdd.aggregateByKey(0)(
(x,y)=> math.max(x,y),
(x,y)=> x + y
).collect.foreach(println)
(a,6)
foldByKey
val rdd = sc.makeRDD(Array(("a",1),("a",2),("b",3),("b",4),
("b",5),("a",3)),2)
//如果聚合计算时,分区内和分区间计算规则相同,提供了简化的方法
rdd.foldByKey(0)(_+_).collect.foreach(println)
(b,12)
(a,6)
RDD行动算子
触发任务的调度和作业的执行
aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
val rdd = sc.makeRDD(Array(1,2,3,4),2)
println(rdd.aggregate(5)(_ + _, _ + _))
rdd.aggregate(5)(
(x,y)=>{println("x":+x+"y":+y);x+y},
(x,y)=>{println("x":+x+"y":+y);x+y}
)
fold
分区内和分区间计算规则是相同的
是aggregate的简化
有初始值,只有一个函数
val rdd = sc.makeRDD(Array(1,2,3,4),2)
println(rdd.fold(5)(_ + _))
//25
countByValue
val rdd = sc.makeRDD(Array(1,2,3,4))
val value = rdd.countByValue()
println(value)
Map(4 -> 1, 2 -> 1, 1 -> 1, 3 -> 1)
countByKey
val rdd = sc.makeRDD(Array(("a",1),("a",4),("b",3)))
val key = rdd.countByKey()
println(key)
//Map(a -> 2, b -> 1)
saveAsTextFile saveAsObjectFile saveAsSequenceFile
val rdd = sc.makeRDD(Array(("a",1),("a",4),("b",3)),2)
rdd.saveAsTextFile("out")
rdd.saveAsObjectFile("out1")
//saveAsSequenceFile类型是k-v类型
rdd.saveAsSequenceFile("out1")
foreach
遍历
val rdd = sc.makeRDD(Array(1,2,3,4))
//foreach Driver端内存集合的循环遍历方法
rdd.collect.foreach(println)
println("----------")
//foreach executor端内存数据打印
rdd.foreach(println)
算子:RDD的方法和scala集合对象的方法不一样
集合对象的方法都是在同一个节点的内存中完成的
RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行
为了区分不同的处理效果,所以将RDD的方法称之为算子
RDD的方法的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行的
foreachAsync --异步处理
foreachPartition
分区遍历
//每个分区就是一个迭代器
//it.foreach 是scala的foreach函数
val rdd1 = sc.parallelize(Array(1,2,3,4),2)
rdd1.foreachPartition(
it=> it.foreach(x=> println(x))
) //3 1 2 4
foreach 是一条一条的拿数据进行处理
foreachPartition是一个分区一个分区的拿数据,一个分区中有很多数据的信息。
所以,在使用中,当我们要把处理结果保存到数据库中的时候,我们要使用foreachPartition这个方法,这样效率会更高。
2.通过上面的例子也说明了,foreach和foreachPartition这两个方法执行完之后不会将结果收集到driver端。
而collect,top,count这些方法是会把结果收集到driver端的。
foreachPartitionAsync-异步
++
类似union
val rdd1 = sc.parallelize(1 to 10)
val rdd2 = sc.parallelize(11 to 20)
val a = rdd1 ++ rdd2
println(a.collect.mkString(","))
//1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
barrier
在这里插入代码片
Cache型
cache
val conf = new SparkConf().setMaster("local[*]").setAppName("Cachedemo")
val sc = SparkContext.getOrCreate(conf)
val rdd1 = sc.textFile("in/users.csv")
rdd1.cache()
var start = System.currentTimeMillis()
println(rdd1.count())//执行完动作算子才缓存
var end = System.currentTimeMillis()
println("第一次打印数量时间差:" + (end - start))
start = System.currentTimeMillis()
println(rdd1.count())
end = System.currentTimeMillis()
println("第二次打印数量时间差:" + (end - start))
start = System.currentTimeMillis()
println(rdd1.count())
end = System.currentTimeMillis()
println("第三次打印数量时间差:" + (end - start))
persist
val conf = new SparkConf().setMaster("local[*]").setAppName("Cachedemo")
val sc = SparkContext.getOrCreate(conf)
val rdd1 = sc.textFile("in/users.csv")
//rdd1.cache() //设置缓存的方法
rdd1.persist(StorageLevel.MEMORY_ONLY_2)
var start = System.currentTimeMillis()
println(rdd1.count())//执行完动作算子才缓存
var end = System.currentTimeMillis()
println("第一次打印数量时间差:" + (end - start))
start = System.currentTimeMillis()
println(rdd1.count())
end = System.currentTimeMillis()
println("第二次打印数量时间差:" + (end - start))
start = System.currentTimeMillis()
println(rdd1.count())
end = System.currentTimeMillis()
println("第三次打印数量时间差:" + (end - start))
start = System.currentTimeMillis()
rdd1.unpersist() //让缓存失效
println(rdd1.count())
end = System.currentTimeMillis()
println("第四次打印数(缓存失效)量时间差:" + (end - start))
canEqual
val rdd1 = sc.parallelize(Array(1,2,3,4))
rdd1.canEqual(1) //false
cartesian
val rdd1 = sc.parallelize(Array(1,2,3,4))
val rdd2 = sc.parallelize(5 to 10)
rdd1.cartesian(rdd2).collect
checkpoint
val conf = new SparkConf().setMaster("local[*]").setAppName("Cachedemo")
val sc = SparkContext.getOrCreate(conf)
//设置检查点路径
sc.setCheckpointDir("file:///D:/kb11checkpointdir")
val rdd = sc.parallelize(Array(("a",1),("b",2),("c",3)))
rdd.checkpoint()
rdd.collect()
println("是否checkpoint:"+rdd.isCheckpointed)
println(rdd.getCheckpointFile)
collect
相当于toArray,collect将分布式的RDD返回为一个单机的scala Array数组,在这个数组上运用scala的函数式操作;
val rdd = sc.parallelize(Array(1,2,3,4))
rdd.collect.foreach(println)
collectAsync
异步
val rdd1 = sc.parallelize(Array(1,2,3,4),2)
println(rdd1.collectAsync().get())
WrappedArray(1, 2, 3, 4)
compute
context、sparkContext
返回创建rdd时的SparkContext
val conf = new SparkConf().setMaster("local[*]").setAppName("Practice")
val sc = SparkContext.getOrCreate(conf)
println(sc)
val rdd = sc.parallelize(Array(1,2,3,4))
println(rdd.context)
println(rdd.sparkContext)
copy
count
统计个数
val rdd1 = sc.parallelize(Array(1,2,3,4))
rdd1.count //4
dependencies
查看当前rdd是否有依赖,可以帮助恢复重建rdd
val rdd = sc.parallelize(Array(1,2,3,4))
println(rdd)
println(rdd.dependencies)
val rdd1 = rdd.map(_*2)
println(rdd1.dependencies)
first
第一个元素
val rdd1 = sc.parallelize(Array(1,2,3,4))
rdd1.first //1
getNumPartitions partitions.size
获取分区数
val rdd = sc.parallelize(Array(1,2,3,4),2)
println(rdd.getNumPartitions) //2
println(rdd.partitions.size) //2
getStorageLevel
存储等级
val rdd = sc.parallelize(Array(1,2,3,4),2)
println(rdd.getStorageLevel)
//StorageLevel(1 replicas)
histogram-直方图
输入的参数buckets可以是一个数字,也可以是一个列表
输出结果为一个元组,元组包含两个列表分别是桶(直方图的边界)和直方图的频数
【注意】:
1、桶必须是排好序的,并且不包含重复元素,至少有两个元素
2、所有直方图的结果集合区右边是开区间,最后一个区间除外。
计算方式 : 一、参数buckets是一个数字的情况: 根据桶的总数来计算
先用排好序的数组的边界值来得出两个桶之间的间距 (9.0-1.1)/5 = 1.58
所以得到第一个元组(直方图的边界) 1.1-2.68 2.68-4.26 以此类推
然后计算每个桶中的频数 : 数组中的数字在桶中的分布情况, 位于第一个区间(1.1~2.68) 中的数字有 1.1、1.2、1.3、2.0、2.1 一共5个 对应第二个数组中的第一个数字
val a = sc.parallelize(List(1.1,1.2,1.3,2.0,2.1,7.4,7.5,7.6,8.8,9.0),3)
a.histogram(5)
res0:(Array[Double], Array[Long]) = (Array(1.1, 2.68, 4.26, 5.84, 7.42, 9.0),Array(5, 0, 0, 1, 4))
id
返回rdd的编号
val rdd1 = sc.parallelize(Array(1,2,3,4),2)
println(rdd1.id)
isEmpty
val rdd1 = sc.parallelize(1 to 5)
rdd1.isEmpty //false
keyBy
指定一个函数产生的数据作为rdd的key
val rdd1 = sc.parallelize(Array("hello","scala","java","python"))
rdd1.keyBy(_.length).collect.foreach(println)
keys
获取rdd中的元组的key,key可以重复出现,List类型可以使用
val rdd1 = sc.parallelize(Array((1,"hello"),(2,"java"),(3,"scala"),(4,"spark")))
println(rdd1.keys.collect.mkString(",")) //1,2,3,4
join
join 用于两个key-value类型的RDD的内连接操作,类似于数据库中的内连接。只有两者的key相同时,才会连接
val a = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
val b = a.keyBy(_.length)
val c = sc.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear","bee"), 3)
//相同的key,就能连接在一起
val d = c.keyBy(_.length)
b.join(d).collect
res0: Array[(Int, (String, String))] = Array((6,(salmon,salmon)), (6,(salmon,rabbit)), (6,(salmon,turkey)), (6,(salmon,salmon)), (6,(salmon,rabbit)), (6,(salmon,turkey)), (3,(dog,dog)), (3,(dog,cat)), (3,(dog,gnu)), (3,(dog,bee)), (3,(rat,dog)), (3,(rat,cat)), (3,(rat,gnu)), (3,(rat,bee)))
max min mean sum
val rdd1 = sc.parallelize(1 to 5)
rdd1.max //5
rdd1.min //1
rdd1.mean //3.0
rdd1.sum //15.0
take
val rdd1 = sc.parallelize(1 to 5)
val a = rdd1.take(5)
println(a.mkString(",")) //1,2,3