目录
2.1.11 intersection 、union、subtract、zip算子
2.1.13 reduceByKey、groupByKey 算子
2.1.14 aggregateByKey、foldByKey 算子
2.1.16 join、leftOuterJoin、coGroup算子
案例实操:从log文件中统计出每一个省份每个广告被点击数量排行的Top3
1.RDD并行度与分区
Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。
1.1从内存中创建RDD -- 分区
分区的源码如下:
(0 until numSlices).iterator.map{
i => val start = ((i * length)/numSlices).toInt
val end = ((i+1)*length/numSlices).toInt
(start,end)
}
numSlices表示分区数,i表示第i个分区,start和end表示该分区的数据从start开始取,取到end-1
例如:
val rdd1 : RDD[Int] = context.makeRDD(
Seq(1, 2, 3, 4,5),2
)
表示从内存创建RDD,分区数为2,按照公式计算每个分区的数据具体如下:
i -->(0,1)
分区0:start = (0*5)/2 = 0 end = (0+1)*5/2 = 2 所以分区0的数据为(1,2)
分区1:start = (1*5)/2 = 2 end = (1+1)*5/2 = 5 所以分区1的数据为(3,4,5)
如果创建RDD时没有指明分区数,则按照默认值分区
源码中的默认值:
scheduler.conf.getInt("spark.default.parallelism",totalCores)
totalCores : 当前环境的总(虚拟)核数
分区设置的优先级:方法参数>配置参数>环境配置(默认)
1.2从文件中创建RDD -- 分区
1.spark读取文件底层其实是hadoop读取文件
2.spark的分区数其实来自于hadoop读取文件时的切片
val rdd: RDD[String] = context.textFile("data/word.txt")
textFile可以在读取文件时设定分区,如果不设定存在默认值math.min(defaultParallelism,2)
defaultParallelism为默认并行度,取决于当前环境的虚拟核数
切片规则:
文件大小totalSize,预计每个分区的字节大小goalSize = totalSize/numSplits
例如文件大小5字节,分为3个分区
numSplits = 3 totalSize = 5 goalSize = 5/3 = 1
numSlices = totalSize/goalSize = 5/1 = 5 最终会分为5个分区。
2.RDD转换算子
算子其实就是RDD对象的方法,为了和Scala集合中的方法进行区分称之为算子
RDD中的方法一般分为两大类
1,逻辑封装:将旧的逻辑转换为新的逻辑,称之为转换算子
2,执行逻辑:将封装好的逻辑执行,运行整个作业,称之为行动算子
2.1转换算子
2.1.1map算子
object Spark_01_RDD_Oper_Transform {
//TODO 算子 - 转换 - map
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val context = new SparkContext(conf)
val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4),2)
//TODO 1.分区:新的RDD默认情况下的分区数与旧的RDD的分区数相同
//TODO 2.数据在处理的过程中,默认分区不变
//数据在执行的过程中,遵循执行顺序:分区内有序,分区间无序
//map算子表示将数据源中的每一条数据进行处理
//map算子的参数是函数类型: Int => U
val rdd1: RDD[Int] = rdd.map(_ * 3)
//RDD是对逻辑的封装,如果存在多个RDD对数据处理,那么每个分区的一条数据走完所有RDD后才轮到下一条数据
val rdd2: RDD[Unit] = rdd1.map(println(_))
}
}
利用map算子从日志数据中获取用户请求的url路径案例:
object Spark_01_RDD_Oper_Transform_1 {
//TODO 算子 - 转换 - map
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val context = new SparkContext(conf)
//从服务器日志数据apache.log中获取用户请求的URL路径
val linesRdd: RDD[String] = context.textFile("data/apache.log")
val urlRdd: RDD[String] = linesRdd.map(
line => {
val datas: Array[String] = line.split(" ")
datas(6)
})
urlRdd.collect().foreach(println)
context.stop()
}
}
2.1.2 mapPartitions算子
mapPartitions是将数据按照分区进行批处理,效率比map算子更高,但是如果数据量大会长时间占用内存,如果内存有限的情况下,推荐使用map
object Spark_02_RDD_Oper_Transform {
//TODO 算子 - 转换 - map
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val context = new SparkContext(conf)
val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4), 2)
//对整个分区的数据进行操作
//有几个分区就执行几遍,比处理单个数据效率高(批处理)
val rdd1: RDD[Int] = rdd.mapPartitions(
list => list.map(_ * 2)
)
}
}
2.1.3mapPartitionsWithIndex算子
mapPartitionsWithIndex算子的参数列表有两个参数,分别为index,Iterator,返回值为Iterator,可以根据参数对指定分区的数据进行处理。
object Spark_03_RDD_Oper_Transform {
//TODO 算子 - 转换 - map
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val context = new SparkContext(conf)
val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4,5,6), 3)
//获取第二个分区的数据
val rdd1: RDD[Int] = rdd.mapPartitionsWithIndex(
(index, list) => {
if (index == 1) {
list
} else {
Nil.iterator
}
}
)
rdd1.collect().foreach(println)
}
}
2.1.4 flatMap算子
flatMap是将数据集中的每个独立的数据扁平化后用容器返回
object Spark_04_RDD_Oper_Transform_1 {
//TODO 算子 - 转换 - 扁平化
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val context = new SparkContext(conf)
val rdd: RDD[String] = context.makeRDD(
List("hello scala", "hello spark")
)
//flatMap扁平化
//flatMap将数据集中每个独立的数据整体扁平化后用容器返回
val rdd1: RDD[String] = rdd.flatMap(_.split(" "))
rdd1.collect().foreach(println)
context.stop()
}
}
案例:将List(List(1,2),3,List(4,5))扁平化