Spark性能调优系列:(六)开发调优(使用高性能的算子)

使用高性能的算子

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()
  }

}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值