Spark的算子的分类
从大方向来说,Spark 算子大致可以分为以下两类:
1)Transformation 变换/转换算子:这种变换并不触发提交作业,完成作业中间过程处理。
Transformation 操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算。
2)Action 行动算子:这类算子会触发 SparkContext 提交 Job 作业。
Action 算子会触发 Spark 提交作业(Job),并将数据输出 Spark系统。
从小方向来说,Spark 算子大致可以分为以下三类:
1)Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Value型的数据。
2)Key-Value数据类型的Transfromation算子,这种变换并不触发提交作业,针对处理的数据项是Key-Value型的数据对。
3)Action算子,这类算子会触发SparkContext提交Job作业。
spark作业按照算子表示的执行过程图:
常见的transformation算子:
1.map 原rdd的形态,转化为另外一种形态
/**
* 1、map:将集合中每个元素乘以7
* map是最常用的转换算子之一,将原rdd的形态,转化为另外一种形态,
* 需要注意的是这种转换是one-to-one
* @param sc
*/
def transformationMap_01(list:List[Int], sc:SparkContext): Unit = {
val bs = 7
val listRDD = sc.parallelize(list)
val retRDD = listRDD.map(num => num * bs)
retRDD.foreach(println)
}
2.flatMap:将行拆分为单词
/**
* 3、flatMap:将行拆分为单词
* 和map算子类似,只不过呢,rdd形态转化对应为one-to-many
* @param sc
*/
def transformationFlatMap_02(sc:SparkContext): Unit = {
val list = List(
"lu jia hui",
"chen zhi xiang"
)
val listRDD = sc.parallelize(list)
listRDD.flatMap(line => line.split("\\s+")).foreach(println)
}
3.filter 条件过滤算子
/**
* filter:过滤出集合中的奇数(even)
*/
def transformationFilter_03(list:List[Int], sc:SparkContext): Unit = {
val listRDD = sc.parallelize(list )
val filterRDD = listRDD.filter(num => {
// 1 / 0
num % 2 == 0
})
filterRDD.foreach(println)
}
4.sample:根据给定的随机种子seed,随机抽样出数量为frac的数据
/**
* sample:根据给定的随机种子seed,随机抽样出数量为frac的数据
* 参数:
* withReplacement:true或者false
* true:代表有放回的抽样
* false:代表无放回的抽样
* fraction:抽取样本空间占总体的比例(分数的形式传入)
* without replacement: 0 <= fraction <= 1
* with replacement: fraction >= 0
* seed:随机数生成器
* new Random().nextInt(10)
* 注意:我们使用sample算子不能保证提供集合大小就恰巧是rdd.size * fraction,
* 结果大小会在前面数字上下浮动
* sample算子,在我们后面学习spark调优(dataskew)的时候,可以用的到
*/
def transformationSample_04(sc:SparkContext): Unit = {
val list = 0 to 99999
val listRDD = sc.parallelize(list)
val sampleRDD = listRDD.sample(false, 0.05)
println(s"sampleRDD的集合大小:${sampleRDD.count()}")
}
5.union:返回一个新的数据集,由原数据集和参数联合而成
/**
* union:返回一个新的数据集,由原数据集和参数联合而成
* 该union操作和sql中的union all操作一模一样
*/
def transformationUnion_05(sc:SparkContext): Unit = {
val list1 = List(1, 2, 3, 4, 5)
val list2 = List(6, 5, 4, 7, 9)
val lRDD1 = sc.parallelize(list1)
val lRDD2 = sc.parallelize(list2)
lRDD1.union(lRDD2).foreach(t => println(t + " "))
}
6.groupByKey:对数组进行 group by key操作 慎用
/**
* groupByKey:对数组进行 group by key操作 慎用
* 一张表,student表
* stu_id stu_name class_id
* 将学生按照班级进行分组,把每一个班级的学生整合到一起
* 建议groupBykey在实践开发过程中,能不用就不用,主要是因为groupByKey的效率低,
* 因为有大量的数据在网络中传输,而且还没有进行本地的预处理
* 我可以使用reduceByKey或者aggregateByKey或者combineByKey去代替这个groupByKey
*/
def transformationGBK_06(sc:SparkContext): Unit = {
val list = List(
"1 郑祥楷 1807bd-bj",
"2 王佳豪 1807bd-bj",
"3 刘鹰 1807bd-sz",
"4 宋志华 1807bd-wh",
"5 刘帆 1807bd-xa",
"6 何昱 1807bd-xa"
)
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2StuInfo = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), line)
}}
val gbkRDD = cid2StuInfo.groupByKey()
gbkRDD.foreach{case (cid, stus) => {
println(s"$cid ---> ${stus} ----> ${stus.size}")
}}
}
7.reduceByKey 聚合算子会进行本地预聚合
/**
*
* 一张表,student表
* stu_id stu_name class_id
* 统计每个班级的人数
* 相同的统计下,reduceByKey要比groupByKey效率高,因为在map操作完毕之后发到reducer之前
* 需要先进行一次本地的预聚合,每一个mapper(对应的partition)执行一次预聚合
* @param sc
*/
def transformationRBK_07(sc:SparkContext): Unit = {
val list = List(
"1 郑祥楷 1807bd-bj",
"2 王佳豪 1807bd-bj",
"3 刘鹰 1807bd-sz",
"4 宋志华 1807bd-wh",
"5 刘帆 1807bd-xa",
"6 何昱 1807bd-xa"
)
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2Count = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), 1)
}}
cid2Count.reduceByKey(_+_).foreach(println)
}
8.join: 打印关联的组合信息
/**
* join: 打印关联的组合信息
* 关联操作中rdd的类型必须(K, V)
* inner join:等值连接 返回左右两张表中能对应上的数据
* left/right outer join:左(右)外连接--->
* 返回左(右)表所有数据,右(左)表能对应上的话显示,对应不上的话显示为null
* left/right full outer join
* 返回左右两张表中都有的数据:左外连接+右外连接
* 一张学生信息表stu,一张班级信息表class
* stu---> stu_id stu_name cid
* class--> cid cname
* 现在要求查询以下信息:
* stu_id stu_name cname
* 用SQL:
* select
* s.stu_id,
* s.stu_name,
* c.cname
* from stu s
* left join class c on s.cid = c.cid
* @param sc
*/
def transformationJOIN_08(sc:SparkContext): Unit = {
val stu = List(
"1 郑祥楷 1",
"2 王佳豪 1",
"3 刘鹰 2",
"4 宋志华 3",
"5 刘帆 4",
"6 OLDLi 5"
)
val cls = List(
"1 1807bd-bj",
"2 1807bd-sz",
"3 1807bd-wh",
"4 1807bd-xa",
"7 1805bd-bj"
)
val stuRDD = sc.parallelize(stu)
val clsRDD = sc.parallelize(cls)
val cid2STURDD:RDD[(String, String)] = stuRDD.map{case line => {
val fields = line.split("\\s+")
(fields(2), line)
}}
val cid2ClassRDD:RDD[(String, String)] = clsRDD.map{case line => {
val fields = line.split("\\s+")
(fields(0), fields(1))
}}
//两张表关联--join
println("---------inner join-------------")
val cid2InfoRDD:RDD[(String, (String, String))] = cid2STURDD.join(cid2ClassRDD)
cid2InfoRDD.foreach(println)
println("---------left join-------------")
val cid2LefJoinRDD:RDD[(String, (String, Option[String]))] = cid2STURDD.leftOuterJoin(cid2ClassRDD)
cid2LefJoinRDD.foreach(println)
println("---------full join-------------")
val cid2FullJoinRDD:RDD[(String, (Option[String], Option[String]))] = cid2STURDD.fullOuterJoin(cid2ClassRDD)
cid2FullJoinRDD.foreach(println)
}
9.sortByKey:将学生成绩进行排序
/**
* sortByKey:将学生成绩进行排序
* 分区内部有序
* 学生表stu:id name chinese-score
*
*/
def transformationsbk_09(sc:SparkContext): Unit = {
val stuList = List(
"1 王浩玉 93.5",
"2 贾小红 56.5",
"3 薛亚曦 60.5",
"4 宋其 125"
)
val stuRDD = sc.parallelize(stuList)
println("------------------sortByKey----------------------")
val score2InfoRDD:RDD[(Double, String)] = stuRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2).toDouble, line)
}}
score2InfoRDD.sortByKey(false, numPartitions = 1).foreach(println)
println("------------------sortBy----------------------")
val sbkRDD2:RDD[String] = stuRDD.sortBy(line => line, false, 1)(
new Ordering[String](){//按照成绩排序
override def compare(x: String, y: String) = {
val xScore = x.split("\\s+")(2).toDouble
val yScore = y.split("\\s+")(2).toDouble
yScore.compareTo(xScore)
}
},
ClassTag.Object.asInstanceOf[ClassTag[String]]
)
sbkRDD2.foreach(println)
}
10.combineByKey
/**
* 使用combineByKey和aggregateByKey来模拟groupByKey和reduceByKey
* 不管combineByKey还是aggregateByKey底层都是使用combineByKeyWithClassTag来实现的
*
* 这两个有啥区别?
*
*
*
*
*/
object _02SparkTransformationOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.project-spark").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_02SparkTransformationOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
val list = List(
"5 刘帆 1807bd-xa",
"2 王佳豪 1807bd-bj",
"8 邢宏 1807bd-xa",
"3 刘鹰 1807bd-sz",
"4 宋志华 1807bd-wh",
"1 郑祥楷 1807bd-bj",
"7 张雨 1807bd-bj",
"6 何昱 1807bd-xa"
)
// cbk2rbk(sc, list)
cbk2gbk(sc, list)
sc.stop()
}
def cbk2gbk(sc:SparkContext, list:List[String]): Unit = {
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2Info:RDD[(String, String)] = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), line)
}}
val cbk2gbk:RDD[(String, ArrayBuffer[String])] = cid2Info.combineByKey(
line => ArrayBuffer[String](line),
(ab:ArrayBuffer[String], line:String) => {
ab.append(line)
ab
},
(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]) => {
ab1.appendAll(ab2)
ab1
}
)
cbk2gbk.foreach(println)
}
/**
* 使用combineByKey来模拟reduceByKey
* @param sc
* @param list
*/
def cbk2rbk(sc:SparkContext, list:List[String]): Unit = {
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2Info:RDD[(String, String)] = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), line)
}}
cid2Info.foreachPartition(patition => {
patition.foreach(println)
})
val cid2Counts = cid2Info.combineByKey(createCombiner, mergeValue, mergeCombiners)
cid2Counts.foreach(println)
}
/**
* V是被聚合的rdd中k-v键值对中的value的类型
* C是经过聚合操作之后又v转化成的类型
* 当前方法,rdd中一个key,在一个分区中,只会创建/调用一次,做数据类型初始化
* 比如:hello这个key,在partition0和partition1中都有出现
* 在partition0中聚合的时候createCombiner之被调用一次
*
* @return
*/
def createCombiner(line:String):Int = {
val fields = line.split("\\s+")
println("-----createCombiner---> " + fields(2))
1
}
/**
* scala代码,求1+。。。+10
* var sum = 0
* for(i <- 1 to 10) {
* sum = sum + i
* }
* println(sum)
* mergeValue就类似于上面的代码
* 合并同一个分区中,相同key对应的value数据,在一个分区中,相同的key会被调用多次
* @return
*/
def mergeValue(sum:Int, line:String):Int = {
val fields = line.split("\\s+")
println(">>>-----mergeValue---> " + fields(2))
sum + 1
}
/**
* 相同key对应分区间的数据进行合并
* @return
*/
def mergeCombiners(sum1:Int, sum2:Int):Int = {
println(">>>-----mergeCombiners--->>> sum1: " + sum1 + "--->sum2: " + sum2)
sum1 + sum2
}
}
11.aggregateByKey
/**
* 使用combineByKey和aggregateByKey来模拟groupByKey和reduceByKey
* 不管combineByKey还是aggregateByKey底层都是使用combineByKeyWithClassTag来实现的
*
* 这两个有啥区别?
*
* 1、本质上combineByKey和aggregateByKey都是通过combineByKeyWithClassTag来实现的,只不过实现的细节或者方式不大一样。
* 2、combineByKey更适合做聚合前后数据类型不一样的操作,aggregateByKey更适合做聚合前后数据类型一致的操作
* 因为我们可以在combineByKey提供的第一个函数中完成比较复杂的初始化操作,而aggregateByKey的第一个参数是一个值
* 3、我们使用时最简单的版本,而在实际生产过程中,一般都是相对比较复杂的版本,还有其它参数的,比如partitioner,
* mapSideCombine。
* partitioner制定并行度,
* mapSideCombine控制是否执行本地预聚合
*
*/
object _03SparkTransformationOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.project-spark").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_03SparkTransformationOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
val list = List(
"5 刘帆 1807bd-xa",
"2 王佳豪 1807bd-bj",
"8 邢宏 1807bd-xa",
"3 刘鹰 1807bd-sz",
"4 宋志华 1807bd-wh",
"1 郑祥楷 1807bd-bj",
"7 张雨 1807bd-bj",
"6 何昱 1807bd-xa"
)
// abk2rbk(sc, list)
abk2gbk(sc, list)
sc.stop()
}
def abk2gbk(sc:SparkContext, list:List[String]): Unit = {
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2Info:RDD[(String, String)] = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), line)
}}
val abk2gbk:RDD[(String, ArrayBuffer[String])] = cid2Info.aggregateByKey(ArrayBuffer[String]())(
(ab:ArrayBuffer[String], line:String) => {
ab.append(line)
ab
},
(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]) => {
ab1.appendAll(ab2)
ab1
}
)
// abk2gbk.foreach(println)
}
/**
* 使用aggregateByKey来模拟reduceByKey
* @param sc
* @param list
*/
def abk2rbk(sc:SparkContext, list:List[String]): Unit = {
val listRDD = sc.parallelize(list)
println("分区个数:" + listRDD.getNumPartitions)
val cid2Info:RDD[(String, String)] = listRDD.map { case (line) => {
val fields = line.split("\\s+")
(fields(2), line)
}}
val cid2rbkRDD:RDD[(String, Int)] = cid2Info.aggregateByKey(0)(
(sum:Int, line:String) => {
sum + 1
},
(sum1:Int, sum2:Int) => {
sum1 + sum2
}
)
cid2rbkRDD.foreach(println)
}
}
12 action 类算子:
/*
1、reduce:
执行reduce操作,返回值是一个标量
2、collect: 慎用
将数据从集群中的worker上拉取到dirver中,所以在使用的过程中药慎用,意外拉取数据过大造成driver内存溢出OOM(OutOfMemory)
NPE(NullPointerException)
所以在使用的使用,尽量使用take,或者进行filter再拉取
3、count:返回当前rdd中有多少条记录
select count(*) from tbl;
4、take:take(n)
获取rdd中前n条记录
如果rdd数据有序,可以通过take(n)求TopN
5、first:take(1)
获取rdd中的第一条记录
6、saveAsTextFile:
将rdd中的数据保存到文件系统中
7、countByKey:和reduceByKey效果相同,但reduceByKey是一个Transformation
统计相同可以出现的次数,返回值为Map[String, Long]
8、foreach:略 遍历
*/
object _04SparkActionOps extends App {
// Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
// Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
// Logger.getLogger("org.project-spark").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_03SparkTransformationOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
val list = List(
"5 刘帆 1807bd-xa",
"2 王佳豪 1807bd-bj",
"8 邢宏 1807bd-xa",
"3 刘鹰 1807bd-sz",
"4 宋志华 1807bd-wh",
"1 郑祥楷 1807bd-bj",
"7 张雨 1807bd-bj",
"6 何昱 1807bd-xa"
)
val listRDD: RDD[String] = sc.parallelize(list)
val list1 = 1 to 9
val listRDD1 = sc.parallelize(list1)
//reduce:统计rdd中的和
val 和 = listRDD1.reduce((v1, v2) => v1 + v2)
println(和)
//2 count
println(s"listRDD1的条数:${listRDD1.count()}")
// 3 take 获取rdd中的n条记录
val take: Array[Int] = listRDD1.take(3)
println(take.mkString("[", ",", "]"))
private val first: Int = listRDD1.first()
println(first)
val top3:Array[Int] = listRDD1.takeOrdered(3)(new Ordering[Int]{
override def compare(x: Int, y: Int) = {
y.compareTo(x)
}
})
println(top3.mkString("[", ",", "]"))
println("---------------saveAsTextFile---------------------")
// listRDD.saveAsTextFile("E:/data/spark/out")
listRDD.saveAsObjectFile("E:/data/spark/out1")
println("---------------saveAsHadoop/SequenceFile---------------------")
val cid2InfoRDD:RDD[(String, String)] = listRDD.map{case line => {
val fields = line.split("\\s+")
(fields(2), line)
}}
/**
* saveAsHadoopFile() ---> org.apache.hadoop.mapred.OutputFormat 借口
* saveAsNewAPIHadoopFile() --> org.apache.hadoop.mapreduce.OutputFormat 抽象类
* job.setOutputFormat(xxx.classs)
*
*
* path: String --->将rdd数据存储的目的地址
keyClass: Class[_], mr中输出记录包含key和value,需要指定对应的class
valueClass: Class[_],
job.setOutputKeyClass()
job.setOutputValueClass()
outputFormatClass: Class[_ <: NewOutputFormat[_, _]] 指定对应的Format来完成数据格式化输出
TextOutputFormat
*/
cid2InfoRDD.saveAsNewAPIHadoopFile(
"E:/data/spark/out2",
classOf[Text],
classOf[Text],
classOf[TextOutputFormat[Text, Text]]
)
//countByKey作用就是记录每一个key出现的次数,作用同reduceByKey(_+_)
private val cid2Count: collection.Map[String, Long] = cid2InfoRDD.countByKey()
for((k, v) <- cid2Count) {
println(k + "---" + v)
}
sc.stop()
}