目录
union()并集、subtract()差集、intersection()交集
aggregateByKey()按照k进行分区内和分区间逻辑
foldByKey()分区内核分区间具有相同逻辑的aggregateByKey()
takeOrdered(n)返回RDD排序后前n个元素组成的数组
foreach()&foreachPartition遍历RDD中每一个元素
前言
在 Spark Core中,RDD(Resilient Distributed Dataset,弹性分布式数据集) 支持 2 种操作:
1、transformation
从一个已知的 RDD 中创建出来一个新的 RDD 。例如: map就是一个transformation。
2、action
在数据集上计算结束之后, 给驱动程序返回一个值.。例如: reduce就是一个action。
注意:
本文只简单讲述Transformation算子和Action算子,目的是了解其相应的算子如何使用;并不打算深入理解每一个算子的执行过程和逻辑思想。
在 Spark 中几乎所有的transformation操作都是懒执行的(lazy),也就是说transformation操作并不会立即计算他们的结果,而是记住了这个操作。只有当通过一个action来获取结果返回给驱动程序的时候这些转换操作才开始计算。这种设计可以使 Spark 运行起来更加的高效。
默认情况下,你每次在一个 RDD 上运行一个action的时候,前面的每个transformed RDD 都会被重新计算。但是我们可以通过persist (or cache)方法来持久化一个 RDD 在内存中,也可以持久化到磁盘上, 来加快访问速度。
根据 RDD 中数据类型的不同,整体分为 2 种 RDD:
- Value类型
- Key-Value类型(其实就是存了一个二维的元组)
第一部分:Transformation算子
Value类型
map()映射
需求:
创建一个1~4数组的RDD,两个分区,将所有元素*2形成新的RDD。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
mapTest(sc)
//4.关闭连接
sc.stop()
}
def mapTest(sc: SparkContext): Unit = {
// 3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
// 3.2 调用map方法,每个元素乘以2
val mapRdd: RDD[Int] = rdd.map(_ * 2)
// 3.3 打印修改后的RDD中数据
mapRdd.collect().foreach(println)
}
}
map()操作如图所示:
map()函数结构
def map[U: ClassTag](f: T => U): RDD[U] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
}功能描述
参数f是一个函数,它可以接收一个参数。当某个RDD执行map方法时,会遍历该RDD中的每一个数据项,并依次应用f函数,从而产生一个新的RDD。即,这个新RDD中的每一个元素都是原来RDD中每一个元素依次应用f函数而得到的。
在本例中,f为:_*2
备注:rdd.map(_ * 2)是rdd.map((f: Int) => f * 2)的简写。
mapPartitions()以分区单位执行Map
需求:
创建一个RDD,4个元素,2个分区,使每个元素*2组成新的RDD
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
mapPartitionsTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 创建一个RDD,4个元素,2个分区,使每个元素*2组成新的RDD
* */
def mapPartitionsTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
// 3.2 需求实现
val value: RDD[Int] = rdd.mapPartitions((data: Iterator[Int]) => data.map((x: Int) => x * 2))
// 3.3 打印
value.foreach(println)
}
}
mapPartitions()操作如图所示:
mapPartitions()函数结构:
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U] = withScope {
val cleanedF = sc.clean(f)
new MapPartitionsRDD(
this,
(context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(iter),
preservesPartitioning)
}功能介绍:
- f函数把每一个分区的数据分别放入到迭代器中,批处理。
- preservesPartitioning:是否保留上游RDD的分区信息,默认false
- Map是一次处理一个元素,而mapPartitions一次处理一个分区数据。
备注:map()和mapPartitions()的区别
- map():每次处理一条数据。
- mapPartitions():每次处理一个分区的数据,这个分区的数据处理完后,原 RDD 中该分区的数据才能释放,可能导致 OOM。
- 开发指导:当内存空间较大的时候建议使用mapPartitions(),以提高处理效率。
mapPartitionsWithIndex()带分区号
需求:
创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
mapPartitionsWithIndexTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD
* */
def mapPartitionsWithIndexTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
// 3.2 需求实现
val value: RDD[(Int, Int)] = rdd.mapPartitionsWithIndex((index: Int, item: Iterator[Int]) => {
item.map((f: Int) => (index,f))
})
// 3.3 打印
value.foreach(println)
}
}
mapPartitionsWithIndex()操作如图所示:
mapPartitionsWithIndex()函数结构:
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U] = withScope {
val cleanedF = sc.clean(f)
new MapPartitionsRDD(
this,
(context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(index, iter),
preservesPartitioning)
}功能介绍:
- f: (Int, Iterator[T]) => Iterator[U]中Int表示分区编号
- 类似于mapPartitions,比mapPartitions多一个整数参数表示分区号
flatMap()压平
需求:
创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放入到一个大的集合中。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
flatMapTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放入到一个大的集合中
* */
def flatMapTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[List[Int]] = sc.makeRDD(Array(List(1,2),List(3,4),List(5,6),List(7)),2)
// 3.2 需求实现
val value: RDD[Int] = rdd.flatMap((list: List[Int]) => list)
// 3.3 打印
value.foreach(println)
}
}
flatMap操作如图所示:
扩展: 可以通过任务上下文获取分区号。
rdd.foreach((f: List[Int]) => {
// 通过任务上下文获取partitionID
println(TaskContext.getPartitionId() + "---"+ f.mkString(","))
})
flatMap()函数结构:
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.flatMap(cleanF))
}功能介绍:
- 与map操作类似,将RDD中的每一个元素通过应用f函数依次转换为新的元素,并封装到RDD中。
- 区别:在flatMap操作中,f函数的返回值是一个集合,并且会将每一个该集合中的元素拆分出来放到新的RDD中。并且新的RDD中继承了原RDD中的分区数。
glom()分区转换数组
需求:
创建一个2个分区的RDD,并将每个分区的数据放到一个数组,求出每个分区的最大值。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
glomTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 创建一个2个分区的RDD,并将每个分区的数据放到一个数组,求出每个分区的最大值
* */
def glomTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
// 3.2 需求实现
val value: RDD[Int] = rdd.glom().mapPartitions((x: Iterator[Array[Int]]) => x.map((f: Array[Int]) => f.max))
// 3.3 打印
value.foreach((f: Int) => {
println(TaskContext.getPartitionId() + ":" + f)
})
}
}
glom()操作如图所示:
glom()函数结构:
def glom(): RDD[Array[T]] = withScope {
new MapPartitionsRDD[Array[T], T](this, (context, pid, iter) => Iterator(iter.toArray))
}功能介绍:
该操作将RDD中每一个分区变成一个数组,并放置在新的RDD中,数组中元素的类型与原分区中元素类型一致。
groupBy()分组
需求:
创建一个RDD,按照元素模以2的值进行分组。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
groupByTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 创建一个RDD,按照元素模以2的值进行分组
* */
def groupByTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 10,3)
// 3.2 需求实现
val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
// 3.3 打印
value.foreach(println)
}
}
groupBy()操作如图所示:
groupBy()函数结构:
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])] = withScope {
groupBy[K](f, defaultPartitioner(this))
}功能介绍:
分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
备注:
1、groupBy会存在shuffle过程
2、shuffle:将不同的分区数据进行打乱重组的过程
3、shuffle一定会落盘。
扩展:复杂版的wordcount
需求:
有如下的数据,("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2),("I Love You",5),("I Miss You",2),("Best wish",9)。其中数字代表出现的个数。求每个单词出现的次数。
代码实现:
def worldCountTest(sc: SparkContext): Unit ={
val rdd: RDD[(String, Int)] = sc.makeRDD(Array(("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2), ("I Love You", 5), ("I Miss You", 2), ("Best wish", 9)))
// 方式一:适合scala
/*val value: String = rdd.map {
// 模式匹配
case (str, count) => {
// scala对字符串操作,("Hello Scala" + " ") * 2 = Hello Scala Hello Scala
(str + " ") * count
}
}
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_+_)
.collect()
.mkString(",")
*/
// 方式二:比较通用
val value: String = rdd.flatMap {
case (str, i) => {
str.split(" ").map((word: String) => (word, i))
}
}.reduceByKey(_ + _)
.collect()
.mkString(",")
println(value)
}
filter()过滤
需求:
创建一个1到10的RDD,过滤出偶数。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
filterTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 过滤偶数
* */
def filterTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 10, 3)
// 3.2 需求实现
val value: RDD[Int] = rdd.filter(_ % 2 == 0)
// 3.3 打印
value.foreach(println)
}
}
filter()操作过程如下:
filter()函数结构:
def filter(f: T => Boolean): RDD[T] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[T, T](
this,
(context, pid, iter) => iter.filter(cleanF),
preservesPartitioning = true)
}功能介绍:
- 接收一个返回值为布尔类型的函数作为参数。
- 当某个RDD调用filter方法时,会对该RDD中每一个元素应用f函数,如果返回值类型为true,则该元素会被添加到新的RDD中。
sample()采样
需求:
创建一个RDD(1-10),从中选择放回和不放回抽样。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
sampleTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 随机采样
* */
def sampleTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 20, 3)
// 3.2 需求实现
val value: String = rdd.sample(true,0.3,3).collect().mkString(",")
val value2: String = rdd.sample(false,0.3,3).collect().mkString(",")
// 3.3 打印
println("不放回采样结果:" + value2)
println("放回采样结果:" + value)
}
}
sample()操作如图所示:
sample()函数结构:
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T] = {
......
}功能介绍:
- 从大量的数据中采样
- withReplacement: Boolean: 抽出的数据是否放回
- fraction: Double:
- 当withReplacement=false时:选择每个元素的概率;取值一定是[0,1] ;底层使用泊松分布。
- 当withReplacement=true时:选择每个元素的期望次数; 取值必须大于等于0,底层使用伯努利采样。
- seed: Long: 指定随机数生成器种子
备注:
1、 该函数随机采样是伪随机,因为传入的随机种子是一样的,计算结果当然一样。
distinct()去重
需求:
对如下的数据去重:3,2,9,1,2,1,5,2,9,6,1
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
distinctTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 数据去重
* */
def distinctTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(3,2,9,1,2,1,5,2,9,6,1))
// 3.2 需求实现
val value: RDD[Int] = rdd.distinct()
// 3.3 打印
value.foreach(println)
}
}
distinct()操作如图所示:
distinct()函数结构:
def distinct(): RDD[T] = withScope {
distinct(partitions.length)
}def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)
}功能介绍:
对内部的元素去重,并将去重后的元素放到新的RDD中。
备注:
1、distinct()用分布式的方式去重比HashSet集合方式不容易OOM.
2、默认情况下,distinct会生成与原RDD分区个数一致的分区数,当然也可以指定分区数。
coalesce()重新分区
需求:
1、将4个分区的RDD合并成两个分区的RDD
2、将三个分区的RDD合并成两个分区的RDD
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
coalesceTest(sc)
//4.关闭连接
sc.stop()
}
/**
* coalesce重分区
* */
def coalesceTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4,4)
val rdd2: RDD[Int] = sc.makeRDD(1 to 4,3)
// 3.2 需求实现
val value1: Array[(Int, Int)] = rdd.coalesce(2).map((TaskContext.getPartitionId(),_)).collect()
val value2: Array[(Int, Int)] = rdd2.coalesce(2).map((TaskContext.getPartitionId(),_)).collect()
// 3.3 打印
println("value1:" + value1.mkString(","))
println("value2:" + value2.mkString(","))
}
}
coalesce()操作如图所示:
coalesce()函数结构:
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T] = withScope {......
}
功能介绍:
- 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。
- shuffle默认为false,则该操作会将分区数较多的原始RDD向分区数比较少的目标RDD,进行转换。
- shuffle:
- true: 进行shuffle,此时目标分区数可以大于可以小于原分区数。即:可以缩小和扩大原分区。
- false:不进行shuffle,此时目标分区数只能小于原分区数;当大于时,分区数不生效。即:只能缩小或等于原分区。
备注:
1、shuffle原理: 将数据打散,然后重新组合。
2、具体shuffle过程,读者可以参考“彻底搞懂spark的shuffle过程”。
reparation()重新分区(执行Shuffle)
需求:
创建一个4个分区的RDD,对其重新分区
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
repartitionTest(sc)
//4.关闭连接
sc.stop()
}
/**
* repartition重分区
* */
def repartitionTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 10,4)
// 3.2 需求实现
val value1: Array[(Int, Int)] = rdd.repartition(2).map((TaskContext.getPartitionId(),_)).collect()
val value2: Array[(Int, Int)] = rdd.repartition(5).map((TaskContext.getPartitionId(),_)).collect()
// 3.3 打印
println("value1:" + value1.mkString(","))
println("value2:" + value2.mkString(","))
}
}
reparation()操作如图所示:
reparation()函数结构:
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}功能介绍:
- 该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。
- 无论是将分区数多的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,repartition操作都可以完成,因为无论如何都会经shuffle过程。
备注: coalesce与reparation的区别
- coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。
- repartition实际上是调用的coalesce,进行shuffle。
- 如果是减少分区, 尽量避免 shuffle,使用coalesce完成。
- 绝大多数情况下,减少分区使用coalesce,增加分区使用reparation。
sortBy()排序
需求:
创建一个RDD,按照不同的规则进行排序。
- 按照数字大小升序排序
- 按照数字大小降序排序
- 按照模以5的余数降序排序
- 二元组,则先按照第一个元素升序,如果第一个元素相同,在按照第二个元素降序
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
sortByTest(sc)
//4.关闭连接
sc.stop()
}
/**
* sortBy排序
* */
def sortByTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(3,16,5,8,2,10,9,1,7,6))
// 3.2 需求实现
// 升序
val value1: String = rdd.sortBy((num: Int) => num).collect().mkString(",")
// 降序
val value2: String = rdd.sortBy((num: Int) => num,false).collect().mkString(",")
// 按照%5排序
val value3: String = rdd.sortBy((num: Int) => num % 5,false).collect().mkString(",")
// 先按照第一个元素升序,如果第一个元素相同,在按照第二个元素降序
val value4: String = rdd2.sortBy((x: (Int, Int)) => (x._1,-x._2)).collect().mkString(",")
// 3.3 打印
println("value1:" + value1)
println("value2:" + value2)
println("value3:" + value3)
println("value4:" + value4)
}
}
sortBy()操作如图所示:
sortBy()函数结构:
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
this.keyBy[K](f)
.sortByKey(ascending, numPartitions)
.values
}功能介绍:
- 该操作用于排序数据。
- 在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。
- 默认情况下,排序后新产生的RDD的分区数与原RDD的分区数一致。
- 可以通过numPartitions参数调整目标分区数。
pipe()调用脚本
需求:
编写一个脚本,使用管道将脚本作用于RDD上。
代码实现:
# 编写一个脚本,并增加执行权限
[root@node001 spark]$ vim pipe.sh
#!/bin/sh
echo "Start"
while read LINE; do
echo ">>>"${LINE}
done
[root@node001 spark]$ chmod 777 pipe.sh
# 创建一个只有一个分区的RDD
scala> val rdd = sc.makeRDD (List("hi","Hello","how","are","you"),1)
# 将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res18: Array[String] = Array(Start, >>>hi, >>>Hello, >>>how, >>>are, >>>you)
#创建一个有两个分区的RDD
scala> val rdd = sc.makeRDD(List("hi","Hello","how","are","you"),2)
# 将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res19: Array[String] = Array(Start, >>>hi, >>>Hello, Start, >>>how, >>>are, >>>you)
pipe()操作如图所示:
pipe()函数结构:
def pipe(command: String): RDD[String] = withScope {
// Similar to Runtime.exec(), if we are given a single string, split it into words
// using a standard StringTokenizer (i.e. by spaces)
pipe(PipedRDD.tokenize(command))
}功能介绍:
- 管道,针对每个分区,都调用一次shell脚本,返回输出的RDD
脚本要放在 worker 节点可以访问到的位置
每个分区执行一次脚本, 但是每个元素算是标准输入中的一行
双Value类型交互
双Value类型的交互常用的算子是数学中的并集、交集合差集。
union()并集、subtract()差集、intersection()交集
需求:
创建两个RDD,求并集、交集、差集
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
aggTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 求并集、交集、差集
* */
def aggTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD (1 to 6)
val rdd2: RDD[Int] = sc.makeRDD (4 to 10)
// 3.2 需求实现
// 并集
val value1: Array[Int] = rdd1.union(rdd2).collect()
// 差集
val value2: Array[Int] = rdd1.subtract(rdd2).collect()
// 交集
val value3: Array[Int] = rdd1.intersection(rdd2).collect()
// 3.3 打印
println("并集:" + value1.mkString(","))
println("差集:" + value2.mkString(","))
println("交集:" + value3.mkString(","))
}
}
union()并集操作如图所示:
subtract()差集操作如图所示:
intersection()交集操作如图所示:
union()函数结构:
def union(other: RDD[T]): RDD[T] = withScope {
sc.union(this, other)
}subtract()函数结构:
def subtract(other: RDD[T]): RDD[T] = withScope {
subtract(other, partitioner.getOrElse(new HashPartitioner(partitions.length)))
}intersection()函数结构:
def intersection(other: RDD[T]): RDD[T] = withScope {
this.map(v => (v, null)).cogroup(other.map(v => (v, null)))
.filter { case (_, (leftGroup, rightGroup)) => leftGroup.nonEmpty && rightGroup.nonEmpty }
.keys
}功能介绍:
- 并集:对源RDD和参数RDD求并集后返回一个新的RDD
- 差集:计算差的一种函数,去除两个RDD中相同元素,不同的RDD将保留下来
交集:对源RDD和参数RDD求交集后返回一个新的RDD
zip()拉链
需求:
创建两个RDD,并将两个RDD组合到一起形成一个(k,v)RDD
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
zipTest(sc)
//4.关闭连接
sc.stop()
}
/**
* zip拉链
* */
def zipTest(sc: SparkContext): Unit ={
// 3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD (1 to 3,2)
val rdd2: RDD[String] = sc.makeRDD (List("a","b","c"),2)
// 3.2 需求实现
val value: Array[(String, Int)] = rdd2.zip(rdd1).collect()
// 3.3 打印
println(value.mkString(","))
}
}
zip()操作如图所示:
zip()函数结构:
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)] = withScope {
......
}
功能介绍:
- 该操作可以将两个RDD中的元素,以键值对的形式进行合并。其中,键值对中的Key为第1个RDD中的元素,Value为第2个RDD中的元素。
- 将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。
Key-Value类型
partitionBy()按照k重新分区
需求:
创建一个5个分区的RDD,对其重新分区。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
partitionByKeyTest(sc)
//4.关闭连接
sc.stop()
}
/**
* partitionByKey:根据key重新分区
* */
def partitionByKeyTest(sc: SparkContext): Unit ={
val tuples: List[(Int, String)] = List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc"), (4, "dd")
,(1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd"))
// 3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD (tuples,5)
rdd.cache()
println("-------分区前-------")
val value1: String = rdd.mapPartitionsWithIndex((index: Int, data: Iterator[(Int, String)]) => {
data.map((index,_))
}).collect().mkString("\t")
println(value1)
// 3.2 需求实现
val value: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))
// 3.3 打印
println("-------分区后-------")
val value2: String = value.mapPartitionsWithIndex((index: Int, data: Iterator[(Int, String)]) => {
data.map((index,_))
}).collect().mkString("\t")
println(value2)
}
}
partitionBy()操作如图所示:
partitionBy()函数结构:
def partitionBy(partitioner: Partitioner): RDD[(K, V)] = self.withScope {
if (keyClass.isArray && partitioner.isInstanceOf[HashPartitioner]) {
throw new SparkException("HashPartitioner cannot partition array keys.")
}
if (self.partitioner == Some(partitioner)) {
self
} else {
new ShuffledRDD[K, V, V](self, partitioner)
}
}功能介绍:
- 将RDD[K,V]中的K按照指定Partitioner重新进行分区;会产生Shuffle过程。
- 如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区,否则进行分区。
- 默认的分区器是HashPartitioner。
备注:
1、HashPartitioner的原理是:
- 依据RDD中key值的hashCode的值将数据取模后得到该key值对应的下一个RDD的分区id值,
- 支持key值为null的情况,当key为null的时候,返回0;
- 该分区器基本上适合所有RDD数据类型的数据进行分区操作。
2、详细的分区器原理,请参考:[Spark] - HashPartitioner & RangePartitioner 区别
注意:
1、由于JAVA中数组的hashCode是基于数组对象本身的,不是基于数组内容的,所以如果RDD的key是数组类型,那么可能导致数据内容一致的数据key没法分配到同一个RDD分区中。(未测)
2、在scala中,如果RDD的key是Array数组类型,编译不通过。Exception in thread "main" org.apache.spark.SparkException: HashPartitioner cannot partition array keys.
这个时候最好自定义数据分区器,采用数组内容进行分区或者将数组的内容转换为集合。
自定义分区
class MyPartitioner(num: Int) extends Partitioner {
// 设置的分区数
override def numPartitions: Int = num// 具体分区逻辑
override def getPartition(key: Any): Int = {if (key.isInstanceOf[Int]) {
val keyInt: Int = key.asInstanceOf[Int]
if (keyInt % 2 == 0)
0
else
1
}else{
0
}
}
}
reduceByKey()按照k进行聚合v
需求:
统计单词出现次数(wordCount)。
代码实现:
为了增加难度,本次实验给字符串加了一些中英文的字符。
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
reduceByKeyTest(sc)
//4.关闭连接
sc.stop()
}
def reduceByKeyTest(sc: SparkContext): Unit = {
val tuple1: String = "Apache Spark™ is a: multi-language engine for executing★ data engineering, "
val tuple2: String = "$Apache Spark™ is a multi-language ※engine for executing data ¥engineering, "
val tuple3: String = "Apache Spark™ is (a multi-language) engine for executing data engineering, "
val tuples: String = tuple1 + tuple2 + tuple3
// 忽略标点字符
val str: String = tuples.replaceAll("\\pP|\\pS", "")
// 3.1 创建第一个RDD
val rdd: RDD[String] = sc.parallelize(Array(str))
// 3.2 需求实现
val value2: String = rdd.flatMap(f => f.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.collect()
.mkString("\n")
// 3.3 打印
println(value2)
}
}
运行结果:
备注: 正则表达式过滤字符串的方法
/pP :其中的小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。大写 P 表示 Unicode 字符集七个字符属性之一:标点字符。
其他六个是:
L:字母;
M:标记符号(一般不会单独出现);
Z:分隔符(比如空格、换行等);
S:符号(比如数学符号、货币符号等);
N:数字(比如阿拉伯数字、罗马数字等);
C:其他字符
reduceByKey()操作如图:
reduceByKey()函数结构:
def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = self.withScope {
combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
}def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)] = self.withScope {
reduceByKey(new HashPartitioner(numPartitions), func)
}def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
reduceByKey(defaultPartitioner(self), func)
}功能介绍:
- 该操作可以将RDD[K,V]中的元素按照相同的K对V进行聚合。
- 其存在多种重载形式,还可以设置新RDD的分区数。
- 默认的分区器为HashPartitioner
groupByKey()按照k重新分组
需求:
求List(("a",1),("b",5),("a",5),("b",2),("a",1),("c",5),("b",5),("b",2))中abc平均出现的次数。
函数实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
groupByKeyTest(sc)
//4.关闭连接
sc.stop()
}
/**
* groupByKey():按照key分组
* */
def groupByKeyTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("c",15),("a",5),("b",2),("a",1),("c",9),("b",5),("b",2)), 2)
// 3.2 需求实现
val value: Array[(String, String)] = rdd.groupByKey().map(f => {
val format: DecimalFormat = new DecimalFormat("#0.00")
val sum: Int = f._2.sum
val count: Int = f._2.size
val nums: Double = sum.toDouble / count
(f._1,format.format(nums))
}).collect()
// 3.3 打印
println(value.mkString("\n"))
}
}
备注: DecimalFormat 类。
该类主要靠 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。
如:#.00表示小数点后保留两位,小数点前不确定,有几位都行。
00.00表示整数部分两位,不足用0补足;小数部分2位,不足用0补足。
groupByKey()操作如图:
groupByKey()函数结构:
def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {
groupByKey(defaultPartitioner(self))
}def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] = self.withScope {
}
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])] = self.withScope {
groupByKey(new HashPartitioner(numPartitions))
}功能介绍:
- groupByKey对每个key进行操作,但只生成一个seq,并不进行聚合。
- 该操作可以指定分区器或者分区数(默认使用HashPartitioner)
备注: reduceByKey和groupByKey区别
- reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
- groupByKey:按照key进行分组,直接进行shuffle。
- 开发指导:
- 在不影响业务逻辑的前提下,优先选用reduceByKey。
- 求和操作不影响业务逻辑,求平均值影响业务逻辑。
aggregateByKey()按照k进行分区内和分区间逻辑
需求
创建一个RDD,取出RDD中每个分区相同key对应值的最大值,然后相加。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
aggregateByKeyTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 取出每个分区相同key对应值的最大值,然后相加
* */
def aggregateByKeyTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val array: Array[(String, Int)] = Array(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8))
val rdd: RDD[(String, Int)] = sc.makeRDD(array, 2)
// 3.2 取出每个分区相同key对应值的最大值,然后相加
// val value: RDD[(String, Int)] = rdd.aggregateByKey(0)(math.max, _ + _)
// 不使用math函数
val value: RDD[(String, Int)] = rdd.aggregateByKey(0)((a: Int, b: Int) => {
if (a > b) a else b
}, _ + _)
// 3.3 打印结果
value.foreach(println)
}
aggregateByKey()操作如图所示:
函数结构 :
def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(
seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
*********
}功能介绍:
(1)zeroValue(初始值):给每一个分区中的每一种key一个初始值;
(2)seqOp(分区内):函数用于在每一个分区中用初始值逐步迭代value;
(3)combOp(分区间):函数用于合并每个分区中的结果。
foldByKey()分区内核分区间具有相同逻辑的aggregateByKey()
需求:
将上例中的数据求一个worldcount。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
foldByKeyTest(sc)
//4.关闭连接
sc.stop()
}
def foldByKeyTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val array: Array[(String, Int)] = Array(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8))
val rdd: RDD[(String, Int)] = sc.makeRDD(array)
// 3.2 worldcount
// val value: RDD[(String, Int)] = rdd.aggregateByKey(0)(_+_, _ + _)
val value: RDD[(String, Int)] = rdd.foldByKey(0)(_ + _)
// 3.3 打印
value.foreach(println)
}
foldByKey()操作如图所示:
foldByKey()函数结构:
def foldByKey(
zeroValue: V,
partitioner: Partitioner)(func: (V, V) => V): RDD[(K, V)] = self.withScope {........
}
功能介绍:
- aggregateByKey的简化操作,seqop和combop相同。即,分区内逻辑和分区间逻辑相同。
参数zeroValue:是一个初始化值,它可以是任意类型
- 参数func:是一个函数,两个输入参数相同
combineByKey()转换结构后分区内和分区间操作
需求:
创建一个pairRDD,根据key计算每种key的均值。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
combineKeyTest(sc)
//4.关闭连接
sc.stop()
}
def combineKeyTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)))
// 3.2 需求实现: 将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
val value: Array[(String, String)] = rdd.combineByKey(
(_, 1),
(acc: (Int, Int), other: Int) => (acc._1 + other, acc._2 + 1), // 注意此处的类型必须手动添加,否则无法使用
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
).map(f => {
val format: DecimalFormat = new DecimalFormat("#0.00")
(f._1, format.format(f._2._1 / f._2._2.toDouble))
}).collect()
// 3.3 打印
println(value.mkString("\n"))
}
}
combineByKey()操作如图所示:
combineByKey()函数功能:
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
partitioner: Partitioner,
mapSideCombine: Boolean = true,
serializer: Serializer = null): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
partitioner, mapSideCombine, serializer)(null)
}def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
numPartitions: Int): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, numPartitions)(null)
}功能介绍:
- createCombiner:V=>C 分组内的创建组合的函数。通俗点将就是对读进来的数据进行初始化,其把当前的值作为参数,可以对该值做一些转换操作,转换为我们想要的数据格式
- mergeValue: (C, V) => C该函数主要是分区内的合并函数,作用在每一个分区内部。其功能主要是将V合并到之前(createCombiner)的元素C上,注意,这里的C指的是上一函数转换之后的数据格式,而这里的V指的是原始数据格式(上一函数为转换之前的)
- mergeCombiners:(C,C)=>R 该函数主要是进行多分区合并,此时是将两个C合并为一个C,例如两个C:(Int)进行相加之后得到一个R:(Int)
备注: reduceByKey、aggregateByKey、foldByKey、combineByKey 四种对key聚合的区别和联系
- 四种底层都调用了combineByKeyWithClassTag
- reduceByKey 没有初始值,分区内和分区间逻辑相同
- aggregateByKey 第一个初始值和分区内处理规则一致,分区内和分区间逻辑可以不同
foldByKey 有初始值,分区内和分区间逻辑一致
combineByKey 初始值可以变化结构,分区内和分区间逻辑不同
sortByKey()按照k进行排序
需求:
创建一个pairRDD,按照key的正序和倒序进行排序
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
sortByKeyTest(sc)
//4.关闭连接
sc.stop()
}
/**
* sortByKeyTest():在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
* */
def sortByKeyTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)))
// 3.2 需求实现
val value1: Array[(String, Int)] = rdd.sortByKey().collect()
val value2: Array[(String, Int)] = rdd.sortByKey(false).collect()
// 3.3 打印
println(value1.mkString("\n"))
println("--------------------------")
println(value2.mkString("\n"))
}
}
sortByKey()操作如图所示:
sortByKey()函数结构:
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)] = self.withScope
{
val part = new RangePartitioner(numPartitions, self, ascending)
new ShuffledRDD[K, V, V](self, part)
.setKeyOrdering(if (ascending) ordering else ordering.reverse)
}功能介绍:
- 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
- 默认升序
mapValues()只对v进行操作
需求:
创建一个pairRDD,并将value数据转换成大写。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
mapValuesTest(sc)
//4.关闭连接
sc.stop()
}
/**
* mapValuesTest():只对Values操作
* */
def mapValuesTest(sc: SparkContext): Unit = {
// 3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))
// 3.2 需求实现
val value: Array[(Int, String)] = rdd.mapValues(_.toUpperCase).collect()
// 3.3 打印
println(value.mkString("\n"))
}
}
mapValues()操作如图所示:
mapValues()函数结构:
def mapValues[U](f: V => U): RDD[(K, U)] = self.withScope {
val cleanF = self.context.clean(f)
new MapPartitionsRDD[(K, U), (K, V)](self,
(context, pid, iter) => iter.map { case (k, v) => (k, cleanF(v)) },
preservesPartitioning = true)
}功能介绍:
针对于(K,V)形式的类型只对V进行操作
join()将相同k对应的多个v关联在一起
需求:
创建两个pairRDD,并将key相同的数据聚合到一个元组。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
joinTest(sc)
//4.关闭连接
sc.stop()
}
/**
* joinTest():在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
* */
def joinTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c"),(0,"零")))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))
// 3.2 需求实现
val value: Array[(Int, (String, Int))] = rdd.join(rdd1).collect()
// 3.3 打印
println(value.mkString("\n"))
}
}
join()操作如图所示:
join()函数结构:
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues( pair =>
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
)
}功能介绍:
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
扩展:左外连接、右外连接和全外连接
返回类型为:(Int, (String, Option[Int]))。
Option[A] (sealed trait) 有两个取值:
1. Some[A] 有类型A的值
2. None 没有值
一般通过f._2._2.getOrElse("默认值")获取值。
左外连接(左连接):左边都有,右边关联不上,用None表示,
def leftOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)
右外连接(右连接):右边都有,左边关联不上,用None表示
def rightOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)
全外连接(全连接):左右都有,关联不上,用None表示,
def fullOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)
cogroup()类似全连接,但是在同一个RDD中对k聚合
需求:
创建两个pairRDD,并将key相同的数据聚合到一个迭代器。
代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
cogroupTest(sc)
//4.关闭连接
sc.stop()
}
/**
* cogroupTest():在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
* */
def cogroupTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (2, 55),(4, 6),(1, 44)))
// 3.2 需求实现
val value: Array[(Int, (Iterable[String], Iterable[Int]))] = rdd.cogroup(rdd1).collect()
// 3.3 打印
println(value.mkString("\n"))
}
}
cogroup()操作如图所示:
cogroup()函数结构:
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
cogroup(other, defaultPartitioner(self, other))
}功能介绍:
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
第二部分:Action行动算子
行动算子是触发了整个作业的执行。因为转换算子都是懒加载,并不会立即执行。常用的行动算子有如下几个。
说明:为了方便,行动算子的测试也将在object TransformationDemo包下实现。不再新建一个包。
reduce()聚合
1)函数签名:def reduce(f: (T, T) => T): T
2)功能说明:f函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
3)需求说明:创建一个RDD,将所有元素聚合得到结果
4)代码如下:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
reduceTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* reduce:聚集RDD内所有元素
* */
def reduceTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
val rdd1: RDD[Int] = sc.makeRDD(1 to 10)
// 3.2 需求实现
val value: (Int, String) = rdd.reduce((k1: (Int, String), k2: (Int, String)) => (k1._1 + k2._1,k1._2 + k2._2))
val value1: Int = rdd1.reduce(_+_)
// 3.3 打印
println(value)
println(value1)
}
}
collect()以数组的形式返回数据集
1)函数签名:def collect(): Array[T]
2)功能说明:在驱动程序中,以数组Array的形式返回数据集的所有元素。
注意:所有的数据都会被拉取到Driver端,当数据量大时,会发生OOM,慎用。
3)需求说明:创建一个RDD,并将RDD内容收集到Driver端打印
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
collectTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* collect:收集数据到Driver端,以数组的形式返回。
* */
def collectTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
// 3.2 需求实现
val value: Array[(Int, String)] = rdd.collect()
// 3.3 打印
value.foreach(println)
}
}
count()返回RDD中元素个数
1)函数签名:def count(): Long
2)功能说明:返回RDD中元素的个数。
3)需求说明:创建一个RDD,统计该RDD的条数。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
countTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* count:返回RDD的条数
* */
def countTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
// 3.2 需求实现
val value: Long = rdd.count()
// 3.3 打印
println(value)
}
}
first()返回RDD中的第一个元素
1)函数签名: def first(): T
2)功能说明:返回RDD中的第一个元素。
3)需求说明:创建一个RDD,返回该RDD中的第一个元素。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
firstTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* first:返回RDD中第一条数据
* */
def firstTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
// 3.2 需求实现
val value: (Int, String) = rdd.first()
// 3.3 打印
println(value)
}
}
take(n)返回由RDD前n个元素组成的数组
1)函数签名: def take(num: Int): Array[T]
2)功能说明:返回一个由RDD的前n个元素组成的数组。
3)需求说明:返回RDD中key值最大的前3个元素。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
takeTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* take(n):返回由RDD组成的前n个元素的数组。
* */
def takeTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(4, "4c"), (5, "5c"), (3, "cC"), (2, "b"), (6, "c"),(0,"零"),(7, "aA")))
// 3.2 需求实现
val value: Array[(Int, String)] = rdd.sortByKey(false).take(3)
// 3.3 打印
println(value.mkString("\n"))
}
}
takeOrdered(n)返回RDD排序后前n个元素组成的数组
1)函数签名: def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
2)功能说明:返回该RDD排序后的前n个元素组成的数组。(默认升序)
3)需求说明:创建一个RDD,获取该RDD排序后的前2个元素。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[*]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
takeTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* takeOrdered(n):返回该RDD排序后的前n个元素组成的数组.
* */
def takeOrderedTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(4, "4c"), (5, "5c"), (3, "cC"), (2, "b"), (6, "c"),(0,"零"),(7, "aA")))
// 3.2 需求实现
val value: Array[(Int, String)] = rdd.takeOrdered(5) // 升序
// 自定义排序(重写compare方法):降序
val value1: Array[(Int, String)] = rdd.takeOrdered(5)(new Ordering[(Int,String)](){
override def compare(x: (Int, String), y: (Int, String)): Int = y._1-x._1
})
// 3.3 打印
println(value.mkString("\n"))
println(value1.mkString("\n"))
}
}
aggregate()案例
1)函数签名: def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
2)功能说明:aggregate函数将每个分区里面的元素通过分区内逻辑和初始值进行聚合,然后用分区间逻辑和初始值(zeroValue)进行操作。
注意:1、分区间逻辑再次使用初始值和aggregateByKey是有区别的。
2、zeroValue 分区内聚合和分区间聚合的时候各会使用一次。
操作流图:
3)需求说明:创建一个RDD,将所有元素相加得到结果。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[1]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
aggregateTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* aggregateTest(n):aggregate函数将每个分区里面的元素通过分区内逻辑和初始值进行聚合,然后用分区间逻辑和初始值(zeroValue)进行操作
* */
def aggregateTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(2, "4c"), (3, "5c"), (4, "cC")),2)
// 3.2 需求实现
val value: Int = rdd.aggregate(0)(
(a: Int, b: (Int, String)) => a + b._1,
(x: Int, y: Int) => x + y
)
val value3: Int = rdd.aggregate(10)(
(a: Int, b: (Int, String)) => a + b._1,
(x: Int, y: Int) => x + y
)
val value2: String = rdd.aggregate("x")(
(a: String, b: (Int, String)) => a + b._1,
(x: String, y: String) => x + y
)
// 3.3 打印
println(value)
println(value2)
println(value3)
}
fold()案例
1)函数签名: def fold(zeroValue: T)(op: (T, T) => T): T
2)功能说明:折叠操作,aggregate的简化操作。即,分区内逻辑和分区间逻辑相同。
3)需求说明:创建一个RDD,将所有元素相加得到结果。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[1]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
foldTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* fold:折叠操作,aggregate的简化操作。即,分区内逻辑和分区间逻辑相同。
* */
def foldTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(2, "4c"), (3, "5c"), (4, "cC")),2)
// 3.2 需求实现
val value: (Int, String) = rdd.fold((0," "))((z: (Int, String), x: (Int, String)) => (z._1 + x._1,x._2 + z._2))
// 3.3 打印
println(value)
}
}
countByKey()统计每种key的个数
1)函数签名:def countByKey(): Map[K, Long]
2)功能说明:统计每种key的个数。
注:可以用来查看数据是否倾斜。
3)需求说明:创建一个PairRDD,统计每种key的个数。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[1]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
countByKeyTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* countByKey:统计每种key的个数
* */
def countByKeyTest(sc: SparkContext): Unit = {
//3.1 创建第两个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))
// 3.2 需求实现
val value: collection.Map[Int, Long] = rdd.countByKey()
// 3.3 打印
println(value)
}
}
save相关算子
1)saveAsTextFile(path)保存成Text文件
(1)函数签名:def saveAsTextFile(path: String)
(2)功能说明:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本。
2)saveAsSequenceFile(path) 保存成Sequencefile文件
(1)函数签名:def saveAsSequenceFile(path: String)
(2)功能说明:将数据集中的元素以Hadoop Sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
注意:只有kv类型RDD有该操作,单值的没有
3)saveAsObjectFile(path) 序列化成对象保存到文件
(1)函数签名:def saveAsObjectFile(path: String)
(2)功能说明:用于将RDD中的元素序列化成对象,存储到文件中。
代码演示:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[1]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
saveTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* savexxx:将数据持久化到本地(或HDFS)
* */
def saveTest(sc: SparkContext): Unit = {
//3.1 创建RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))
// 3.2 需求实现
rdd.repartition(1).saveAsTextFile("file:///C:/tmp/output/txt/")
rdd.repartition(1).saveAsObjectFile("file:///C:/tmp/output/obj/")
rdd.repartition(1).saveAsSequenceFile("file:///C:/tmp/output/seq/")
}
}
foreach()&foreachPartition遍历RDD中每一个元素
1)函数签名:def foreach(f: T => Unit): Unit
2)功能说明:遍历RDD中的每一个元素,并依次应用f函数。
两种形式的foreach算子操作如图所示:
备注:
1、调用collect算子时,数据在Drive端打印;未调用collect算子时,数据不会返回Drive端,而是直接在Executor端打印。
2、foreach和foreachPartition的区别:
foreach 是一条一条的拿数据进行处理;
foreachPartition是一个分区一个分区的拿数据,一个分区中有很多数据的信息。
所以,在使用中,当我们要把处理结果保存到数据库中的时候,我们要使用foreachPartition这个方法,这样效率会更高。
3)需求说明:创建一个RDD,对每个元素进行打印。
4)代码实现:
object TransformationDemo {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setMaster("local[1]")
.setAppName("TransformationDemo_test")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
foreachTest(sc)
//4.关闭连接
sc.stop()
}
/**
* 行动算子:
* foreach和foreachPartition:遍历RDD中的元素
* */
def foreachTest(sc: SparkContext): Unit = {
//3.1 创建RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 100,5)
// 3.2 需求实现
rdd.foreach((f: Int) => println(TaskContext.getPartitionId() + "--" + f))
rdd.foreachPartition((f: Iterator[Int]) => println(TaskContext.getPartitionId() + "--" + f.mkString(",")))
}
}
总结
至此,常用的RDD算子已全部汇总完毕,当然,还有许多遗漏的算子和方法的使用未涉及到,感兴趣的读者可以自行研究学习。
下面为读者汇总关于RDD算子中面试常见问题:
- map与mapPartitions的区别
map():每次处理一条数据。
mapPartitions():每次处理一个分区的数据,这个分区的数据处理完后,原 RDD 中该分区的数据才能释放,可能导致 OOM。
开发指导:当内存空间较大的时候建议使用mapPartitions(),以提高处理效率。
- coalesce与repartition两个算子的作用以及区别与联系
1)关系:
两者都是用来改变RDD的partition数量的,repartition底层调用的就是coalesce方法:coalesce(numPartitions, shuffle = true)2)区别:
repartition一定会发生shuffle,coalesce根据传入的参数来判断是否发生shuffle。
一般情况下增大rdd的partition数量使用repartition,减少partition数量时使用coalesce。注:
1、coalesce填写的参数小于等于RDD的partition数量,大于不起作用
2、coalesce中的参数过小可能会导致OOM,这时,又想保证小文件的数量少,可以使用reparation。
- 使用zip算子时需要注意的是什么(即哪些情况不能使用)
在 Spark 中, 两个 RDD 的元素的数量和分区数都必须相同, 否则会抛出异常。
其实本质就是要求的每个分区的元素的数量相同.
- aggregateByKey与aggregate之间的区别与联系。
aggregate和aggregateByKey的参数是一样的,作用也一样,只不过aggregateByKey多了key而已。
如果将RDD分别有设置成2个分区和4个分区,则
- 不影响aggregateByKey()的输出结果,因为其是按照K处理分区内和分区间逻辑。
- 会影响aggregate的输出结果,2个分区中,初始值共使用了3次;4个分区中,初始值共使用了5次。
- reduceByKey跟groupByKey之间的区别。
reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
groupByKey:按照key进行分组,直接进行shuffle。返回结果是RDD[(K, Iterable[V])]。
开发指导:reduceByKey比groupByKey性能更好,并且groupByKey会造成OOM。建议使用。但是需要注意是否会影响业务逻辑。
- reduceByKey跟aggregateByKey之间的区别与联系。
reduceByKey可以认为是aggregateByKey的简化版。
aggregateByKey,分为三个参数(zeroValue、seqOp、combOp),可以自定义初始值、分区内、分区间的操作。
- combineByKey的参数作用,说明其参数调用时机。
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)](1)createCombiner:V=>C 分组内的创建组合的函数。会遍历分区中的每个key-value对. 如果第一次碰到这个key, 则调用createCombiner函数,传入value, 得到一个C类型的值。(如果不是第一次碰到这个 key, 则不会调用这个方法)
(2)mergeValue:(C,V)=>C 该函数主要是分区内的合并函数,作用在每一个分区内部。如果不是第一个遇到这个key,则调用这个函数进行合并操作。
(3)mergeCombiners:(C,C)=>R 该函数主要是进行多分区合并,此时是将两个C合并为一个C。跨分区合并相同的key的值。
路漫漫其修远兮,吾将上下而求索。 ---屈原