使用高性能的算子
1.使用reduceByKey、aggregateByKey替代groupByKey。
详细看 https://blog.csdn.net/qq1021979964/article/details/102678016
2.使用mapPartitions替代普通map
mapPartitions类的算子,一次函数会调用处理一个partition所有的数据,不是一次函数调用处理一条数据,性能相对会比较高。
注意:由于单次函数调用处理一个partition所有的数据,如果内存不够,垃圾回收时是无法回收太多的对象,很可能会出现OOM(内存溢出)异常,所以该类操作要谨慎。
案例:
package com.kevin.scala.tuning
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ArrayBuffer
/**
* 使用mapPartitions替代普通map
*/
object MapTuning {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf
val conf = new SparkConf().setAppName("MapTuning").setMaster("local")
// 2.创建SparkContext
val sc = new SparkContext(conf)
val file = "DTSparkCore\\src\\main\\resources\\records.txt"
// 未优化:使用map,一次函数只处理一条数据
// sc.textFile(file).map(_.split(" ")).collect().foreach(value => (value.foreach(println(_))))
// 优化:改为mapPartitions并设置分区,每次函数处理一个partition的数据
sc.textFile(file,3).mapPartitions(iter => {
val result = new ArrayBuffer[String]()
while (iter.hasNext) {
for(value <- iter.next().split(" ")){
result.+=(value)
}
}
result.iterator
// 例如
// con = getConnect() //获取数据库连接
// iter.foreach(iter=>{
// con.insert(data) //循环插入数据
// })
// con.commit() //提交数据库事务
// con.close() //关闭数据库连接
}).collect().foreach(println(_))
// 4.关闭sc
sc.stop()
}
}
3.使用foreachPartition替代foreach
原理与使用mapPartitions替代普通map类似,都是单次函数处理一个partition的所有数据。
比如:用foreach将数据写入mysql,则是一条条数据的写,每次函数调用可能会创建一个数据库连接,此时会频繁的创建与销毁数据库连接,性能差。
foreachPartition:每次函数调用处理一个partition的数据,那么每个partition只需要创建一个数据库连接,然后执行批量插入操作,性能相对会比较高。
案例:
package com.kevin.scala.tuning
import org.apache.spark.{SparkConf, SparkContext}
/**
* 使用foreachPartition替代foreach
*/
object ForeachTuning {
def main(args: Array[String]): Unit = {
val file = "DTSparkCore\\src\\main\\resources\\test.txt"
// 1.创建SparkConf
val conf = new SparkConf().setAppName("ForeachTuning").setMaster("local")
// 2.创建SparkContext
val sc = new SparkContext(conf)
// 未优化:使用foreach一次函数遍历一个数据
// sc.textFile(file).foreach(println(_))
// 优化:foreachPartition分区为3个分区遍历,一次函数变量一个partition的数据
sc.textFile(file,3).foreachPartition(_.foreach(println(_)))
// 关闭sc
sc.stop()
}
}
4. 使用filter之后进行coalesce操作
filter过滤掉较多数据后(30%以上的数据),建议使用coalesce算子,手动减少rdd的partition的数量,将rdd的数据压缩到更少的partition中,因为filter之后,rdd中的partition都会有很多数据被过滤掉,如果此时直接进行后续操作,那么每个task处理的partition的数据量都并不多,比较浪费资源,且处理的task越多速度越慢。
因此用coalesce减少partition数量,将rdd中的数据压缩到更少的partition后,只需要较少的task就可以处理完所有的partition。
案例:
package com.kevin.scala.tuning
import org.apache.spark.{SparkConf, SparkContext}
/**
* 使用filter之后进行coalesce操作
*/
object CoalesceTuning {
def main(args: Array[String]): Unit = {
val file = "DTSparkCore\\src\\main\\resources\\test2.txt"
// 创建SparkConf
val conf = new SparkConf().setAppName("CoalesceTuning").setMaster("local")
// 创建SparkContext
val sc = new SparkContext(conf)
val rdd = sc.textFile(file,5)
println("rdd: "+rdd.getNumPartitions)
println("原数据量: "+rdd.count())
// 过滤数据
val rdd1 = rdd.filter(value => !value.contains("server"))
// coalesce减少分区,true为产生shuffle,flase为不产生shuffle
val coalesce = rdd1.coalesce(3,true)
println("coalesce: "+coalesce.getNumPartitions)
println("过滤后: "+coalesce.count())
// 关闭sc
sc.stop()
}
}
5.使用repartitionAndSortWithinPartition替代reparation与sort类操作
官方推荐,如果在repartition重分区后,还需要进行排序,建议直接shiyongrepartitionAndSortWithinPartition算子,因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle和sort两个操作同时进行,比先shuffle再sort的性能高。
案例:
package com.kevin.scala.tuning
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
/**
* 使用repartitionAndSortWithinPartition替代reparation与sort类操作
*/
object RepartitionSortTuning {
def main(args: Array[String]): Unit = {
// 1.创建SparkConf
val conf = new SparkConf().setAppName("RepartitionSortTuning").setMaster("local")
// 2.创建SparkContext
val sc = new SparkContext(conf)
val list = List((1, 1), (2, 2), (1, 3), (5, 4), (3, 5), (7, 6))
// 3.parallelize将集合转成rdd,repartitionAndSortWithinPartitions先重新分区,再对其进行排序
val rdd = sc.parallelize(list,5)
// 未优化:使用repartition+Sort,先shuffle分区,然后在排序
// val repartition = rdd.repartition(3)
// repartition.sortByKey(true).map(v => (v._2,v._1)).foreach(result => println(result._1+","+result._2))
// 优化:使用repartitionAndSortWithinPartitions替代repartition+Sort,一边进行重分区的shuffle操作,一边进行排序
val rdd1 = rdd.repartitionAndSortWithinPartitions(new HashPartitioner(3))
println("rdd1 partitions size: " + rdd1.partitions.length)
// 4.对数据进行分区处理,但只创建与分区数量相同的对象,并得到当前分区索引
rdd1.mapPartitionsWithIndex((index,iter) => {
while (iter.hasNext) {
println("rdd1 partition index: " + index + " ,value: " + iter.next())
}
iter
},true).count()
// 5.关闭sc
sc.stop()
}
}