spark算子

日志处理配置

log4j.rootCategory=ERROR, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n

# Set the default spark-shell log level to ERROR. When running the spark-shell, the
# log level for this class is used to overwrite the root logger's log level, so that
# the user can have different defaults for the shell and regular Spark apps.
log4j.logger.org.apache.spark.repl.Main=ERROR

# Settings to quiet third party logs that are too verbose
log4j.logger.org.spark_project.jetty=ERROR
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=ERROR
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=ERROR
log4j.logger.org.apache.parquet=ERROR
log4j.logger.parquet=ERROR

# SPARK-9183: Settings to avoid annoying messages when looking up nonexistent UDFs in SparkSQL with Hive support
log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=FATAL
log4j.logger.org.apache.hadoop.hive.ql.exec.FunctionRegistry=ERROR
log4j.properties

spark 依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spark_test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>3.0.0</version>
        </dependency>
    </dependencies>
    

</project>
pom.xml

转化算子

map 和mapPartions ,mapPartitionsWithIndex,flatMap 

1. map

一行一行处理,相对不耗内存

2. mapPartions

以分区为单位进行数据转化操作

但会将整个分区中的数据加载到内存中进行引用

在内存较小,数据量较大的时候容易发生内存溢出。

将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处
理,哪怕是过滤数据。
 
package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkMapPartions {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val rdd1 =sc.makeRDD(List(2,1,3,2,3,4))
    val rdd2= rdd1.mapPartitions(
      data => data.filter(_==2)
    )
    rdd2.collect().foreach(println)
    sc.stop()
  }

}

 获取每个分区中的最大值

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkMapPartions {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val rdd1 =sc.makeRDD(List(2,1,3,2,3,4),2)
    val rdd2= rdd1.mapPartitions(
      iter=> {
        // 输入一个迭代器,要返回一个迭代器
        List(iter.max).iterator
      }

    )
    rdd2.collect().foreach(println)
    sc.stop()
  }

}

3.mapPartitionsWithIndex

可以知道数据来自哪个分区

案例1: 区分数据来源哪个分区

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkMapPartitionsWithIndex2 {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.makeRDD(List(1,2,3,4),2)
    val dataRDD1 = dataRDD.mapPartitionsWithIndex(
      (index, iter) => {

        iter.map(
          num=>{
            (index,num)
          }
        )
      })


    dataRDD1.collect().foreach(println)
    sc.stop()
  }

}

案例2:保留某分区中的数据

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkMapPartitionsWithIndex {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.makeRDD(List(1,2,3,4),2)
    val dataRDD1 = dataRDD.mapPartitionsWithIndex(
      (index, iter) => {
        if (index==1){
          // 保留一号分区的数据
          iter
        }else{
          Nil.iterator
        }
      })
    dataRDD1.collect().foreach(println)
    sc.stop()
  }

}

4.flatMap

package deng.xiakeyun.bigdata.operator
import org.apache.spark.{SparkConf, SparkContext}
object SparkFlatMap {
  def main(args:Array[String]) :Unit ={

    val conf = new SparkConf().setAppName("flatMapTest")
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)
    val rdd1 = sc.makeRDD(List(List(1, 2, 3), List(45, 56), 23))

    var flatRdd = rdd1.flatMap(
      data=>{
// 模式匹配 data match {
case list: List[_]=>list case dat=>List(dat) } }) flatRdd.collect().foreach(println) } }

 glom 

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
package deng.xiakeyun.bigdata.operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object SparkGlom {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)
    // Array--- > Int
    val dataRDD: RDD[Int]=sc.makeRDD(List(1,2,3,4),2)
    // Int ---> Array
    val glomRDD:RDD[Array[Int]]= dataRDD.glom()
    glomRDD.collect().foreach(data=>println(data.mkString(",")))
    sc.stop()
  }

}
计算所有分区最大值求和(分区内取最大值,分区间最大值求和)
package deng.xiakeyun.bigdata.operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object SparkGlom2 {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)
    // Array--- > Int
    val dataRDD: RDD[Int]=sc.makeRDD(List(1,2,3,4),2)
    // Int ---> Array
    val glomRDD:RDD[Array[Int]]= dataRDD.glom()
    val maxRDD = glomRDD.map(
      // 获取每个分区的最大值
      data=>data.max
    )
    println(maxRDD.collect().sum)
    sc.stop()
  }

}

groupBy

例1. 按奇偶分组

package deng.xiakeyun.bigdata.operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object SparkGroupBy {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.makeRDD(List(1,2,3,4),2)
    // groupBy 会将数据源中的每一个数据进行分组判断,根据返回的分组
    // key进行分组,相同key值的数据会放在一个组中
    def groupFunction(num:Int):Int={
      // 奇数和偶数分开
        num%2
    }
    val groupRDD:RDD[(Int,Iterable[Int])]=dataRDD.groupBy(groupFunction)

    groupRDD.collect().foreach(println)

    sc.stop()
  }

}

例2. 按首字母分组

package deng.xiakeyun.bigdata.operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object SparkGroupBy {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.makeRDD(List("spark","deng","wang","den","spk"),2)
    // groupBy 会将数据源中的每一个数据进行分组判断,根据返回的分组
    // key进行分组,相同key值的数据会放在一个组中
    // 分区和分组没有必然关系
    // 数据会打乱,重新组合(shuffer)
    val groupRDD=dataRDD.groupBy(_.charAt(0))

    groupRDD.collect().foreach(println)

    sc.stop()
  }

}

 例3:统计日志中不同时间段的访问量

a.log日志格式:

83.149.9.216 - - 17/05/2015:10:05:03 +0000 GET /presentations/logstash-monitorama-2013/images/kibana-search.png
83.149.9.216 - - 17/05/2015:10:05:43 +0000 GET /presentations/logstash-monitorama-2013/images/kibana-dashboard3.png
83.149.9.216 - - 17/05/2015:10:05:47 +0000 GET /presentations/logstash-monitorama-2013/plugin/highlight/highlight.js
83.149.9.216 - - 17/05/2015:10:05:12 +0000 GET /presentations/logstash-monitorama-2013/plugin/zoom-js/zoom.js
83.149.9.216 - - 17/05/2015:10:05:07 +0000 GET /presentations/logstash-monitorama-2013/plugin/notes/notes.js
代码:
package deng.xiakeyun.bigdata.operator

import java.text.SimpleDateFormat

import org.apache.spark.{SparkConf, SparkContext}

object SparkGroup {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.textFile("datas/a.log")

    val timeRdd = dataRDD.map(
      line=>{
        val datas = line.split(" ")
        // 获取时间那短字符串 7/05/2015:10:05:34
        val time = datas(3)
        val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
        val date = sdf.parse(time)
        val sdf1= new SimpleDateFormat("HH")
        val hour:String  = sdf1.format(date)
        (hour,1)
      }
    ).groupBy(_._1)

    val resultRDD = timeRdd.map{
      case (hour,iter) =>{
        (hour,iter.size)
      }
    }
    resultRDD.collect().foreach(println)

    sc.stop()
  }

}
package deng.xiakeyun.bigdata.operator

import java.text.SimpleDateFormat

import org.apache.spark.{SparkConf, SparkContext}

object SparkGroup {
  def main(args:Array[String]) :Unit ={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)

    val dataRDD =sc.textFile("datas/a.log")
    def groupFunction(t:Tuple2[String,Int]):String={
      // 奇数和偶数分开
      t._1
    }

    val timeRdd = dataRDD.map(
      line=>{
        val datas = line.split(" ")
        // 获取时间那短字符串 7/05/2015:10:05:34
        val time = datas(3)
        val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
        val date = sdf.parse(time)
        val sdf1= new SimpleDateFormat("HH")
        val hour:String  = sdf1.format(date)
        (hour,1)
      }
//    ).groupBy(_._1)
    ).groupBy(groupFunction)

    val resultRDD = timeRdd.map{
      case (hour,iter) =>{
        (hour,iter.size)
      }
    }
    resultRDD.collect().foreach(println)

    sc.stop()
  }

}
非简化版

filter 和distinct

package deng.xiakeyun.bigdata.operator
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SparkFilterAndDistinct {

  def main(args:Array[String]):Unit={
    val conf = new SparkConf().setMaster("local[*]").setAppName("deng")
    val sc = new SparkContext(conf)
    val rdd1 = sc.makeRDD(List(1,2,34,2,2,3))
    // distinct 去重底层: (1,null),(2,null) 通过reduceByKey() ,map 实现
    val rdd2 = rdd1.distinct()
    rdd2.collect().foreach(println)
    // filter 过滤
    println("==========spark filter ===========")
    // 过滤掉值为2 后的结果
    rdd1.filter(x=>x!=2).collect().foreach(println)

  }

}

coalesce 和repartiton 

package deng.xiakeyun.bigdata.operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SparkCoalesces {

  def main(args:Array[String]):Unit={
    //
    val conf = new SparkConf().setMaster("local").setAppName("spark coalesce")
    val sc = new SparkContext(conf)

    val rdd1 = sc.makeRDD(List(1,2,3,4),2)
    // 1. coalesce 缩减分区
    // 2. coalesce 第二个参数,是否需要打乱重整 shuffle=flase 时,不打乱重整,可能会出现数据分布不均,此情况扩大分区无效
    // 3. coalesce 扩大分区:shuffle=true 时,可以用来扩大分区,以及数据重分区
    // 4. repartition: 扩大分区 ,底层就是coalesce 设置了shuffle =true 来实现的
//    val rdd2 = rdd1.coalesce(4,false)
    val rdd2 = rdd1.repartition(4)
    rdd2.saveAsTextFile("outpath")
    sc.stop()

  }
}

sortBy

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkSortBy {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)
    val rdd1 = sc.makeRDD(List(1, 34, 2, 4))
    // 根据本身进行排序
    val rdd2 = rdd1.sortBy(x => x)
    rdd2.collect().foreach(println)
    var rdd3 = sc.makeRDD(List(("21", "deng"), ("34", "wang"), ("110", "lin")))
    val rdd4 = rdd3.sortBy(x => x._1)
    rdd4.collect().foreach(println)
    /*
    * (110,lin)
      (21,deng)
      (34,wang)
    *
    * */
    val rdd5 = rdd3.sortBy(x => x._1.toInt)

    rdd5.collect().foreach(println)
    /*
  *
    (21,deng)
    (34,wang)
    (110,lin)
  *
  * */
    // sortBy 默认不会改变分区操作,但存在shuffle操作
    // ascending =false降序 排列
    val rdd6 = rdd3.sortBy(x => x._1.toInt, false)
    rdd6.collect().foreach(println)

  }

}

交集,并集,差集,拉链

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object Spark2Vaule {

  def main(args:Array[String]):Unit={
    /**
     * 交集,并集,差集 两个数据源的数据类型需要一致
     * 拉链zip 数据类型可以不一致,但两个数据源的分区数需要一致,两个数据源分区数据量要保持一致
     *
     */
    /
    val conf = new SparkConf().setMaster("local").setAppName("spark coalesce")
    val sc = new SparkContext(conf)
    // 双vaule算子
    val rdd1 = sc.makeRDD(List(1,2,3,4),2)
    val rdd2 = sc.makeRDD(List(3,4,5,6),2)
    // 1.交集 3,4
    val rdd3 = rdd1.intersection(rdd2)
    println(rdd3.collect().mkString(","))

    // 2.并集 1,2,3,4,3,4,5,6
    val rdd4 = rdd1.union(rdd2)
    println(rdd4.collect().mkString(","))
    // 3. 差集1,2
    val rdd5= rdd1.subtract(rdd2)
    println(rdd5.collect().mkString(","))
    // 4. 拉链 (1,3),(2,4),(3,5),(4,6)
    val rdd6 = rdd1.zip(rdd2)
    println(rdd6.collect().mkString(","))

    sc.stop()

  }
}

partitionBy

自定义分区器:

package deng.part

import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}
object  SparkPartitionBy {

  def main(args:Array[String]):Unit={

    /*
    * 自定义分区器
    * 重写方法
    * */

    val conf = new SparkConf().setAppName("partitioner").setMaster("local")

    val sc = new SparkContext(conf)

    val rdd1 = sc.makeRDD(List(("nba",1),("nba",2),("wba",4),("dj",6)))

    val p_rdd = rdd1.partitionBy(new MyPartitioner)
    p_rdd.saveAsTextFile("outpath")

    sc.stop()


  }

  // 自定义分区器
  class MyPartitioner extends Partitioner{
    //多少个分区
    override def numPartitions: Int = 3
    // 将不同的数据分配到不同的分区中
    override def getPartition(key: Any): Int = {
//      if (key=="nba"){
//        0
//      }else if(key=="wba"){
//        1
//      }else{
//        2
//      }

      key match {
        case "nba"=>0
        case "wba"=>1
        case _=>2
      }

    }
  }

}
import org.apache.spark.{Partitioner, SparkConf, SparkContext}

import scala.util.Random

object SparkMyParitition {

  def main(args: Array[String]): Unit = {
    //
    val conf = new SparkConf().setMaster("local[*]").setAppName("partition")

    val sc = new SparkContext(conf)

    val rdd1 = sc.textFile("datas/word.txt")
    val rdd2 = rdd1.flatMap(_.split(" ")).map((_, 1)).reduceByKey(new MyPartitioner(),_+_)
    rdd2.saveAsTextFile("data/out")

    sc.stop()
  }

  // 自定义分区器
  class MyPartitioner extends Partitioner{
    override def numPartitions: Int =3

    override def getPartition(key: Any): Int = {

      if(key.equals("spark")){
        Random.nextInt(2)
      }else{
        2
      }
    }
  }
}

groupBy, groupByKey, reduceByKey,aggrateByKey, foldByKey,combineByKey

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkKvVaule {

  def main(args:Array[String]):Unit={
    val conf = new SparkConf().setMaster("local").setAppName("wordcount")
    val sc = new SparkContext(conf)
    /*
    * groupBy : [("a",[("a",1),("a",2),("a",3)]),("b",[("b",1)])]
    * groupByKey: [("a",[1,2,3]),("b",4)],会导致数据打乱重组,存在shuffle 操作,再用map聚合
    * reduceByKey: 支持分区内预聚合功能combine,分区内先聚合,之后再分区间聚合,可以减少shuffle时落盘的数据量,所以性能优于groupByKey()
    * reduceByKey 分区内和分区间的聚合规则是一样的
    * aggregateByKey: 分区内和分区间的规则可以不一样,例: 分区内求最大值,分区间求和
    * spark 中,shuffle 操作必须落盘处理,不能在内存中数据等待,会导致内存溢出。
    * */
    val rdd1 = sc.makeRDD(List(("a",1),("a",2),("a",3),("b",4)),2)

    val rdd2 = rdd1.groupBy(x=>x._1)
    println(rdd2.collect().mkString(","))  //(a,CompactBuffer((a,1), (a,2), (a,3))),(b,CompactBuffer((b,4)))

    val rdd3 = rdd1.groupByKey()
    println(rdd3.collect().mkString(",")) // (a,CompactBuffer(1, 2, 3)),(b,CompactBuffer(4))
    /*
    * aggregateByKey
    * 函数柯里化
    * 第一个参数:初始值zeroVaule 一般为0
    * 第二个参数:分区内规则和分区间规则(可以不同)
    * aggregateByKey 最终的返回值类型应该和初始值保持一致
    *
    * flodByKey:
    * 如何聚合时,分区内和分区间的聚合规则一样
    * */
    //
    // aggregateByKey 分区内和分区间的规则可以不一样 也可以一样
    //1.  aggregateByKey 分区内和分区间的规则可以不一样时:
    val rdd4= rdd1.aggregateByKey(0)(
      (x,y)=>math.max(x,y),// 分区内取最大值
      (x,y)=>x+y  // 分区间最大值求和
    )
    println(rdd4.collect().mkString(",")) //(b,4),(a,5)
    //2.  aggregateByKey 分区内和分区间的规则一样时:
    val rdd5 = rdd1.aggregateByKey(0)(_+_,_+_)
    println(rdd5.collect().mkString(",")) //(b,4),(a,6)

    // 3. foldBykey: 分区内和分区间的规则一样时:
    val rdd6= rdd1.foldByKey(0)(_+_)
    println(rdd6.collect().mkString(",")) //(b,4),(a,6)

  }

}
reduceByKey 和 groupByKey 的区别?
从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey
可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的
数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较
高。
从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚
合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那
么还是只能使用 groupByKey
reduceByKey、foldByKey、aggregateByKey、combineByKey 的区别?
reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同
FoldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同
AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规
则可以不相同
CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区
内和分区间计算规则不相同。
源码分析:
 /*
        reduceByKey:

             combineByKeyWithClassTag[V](
                 (v: V) => v, // 第一个值不会参与计算,第一个值是啥返回啥
                 func, // 分区内计算规则
                 func, // 分区间计算规则
                 )

        aggregateByKey :

            combineByKeyWithClassTag[U](
                (v: V) => cleanedSeqOp(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
                cleanedSeqOp, // 分区内计算规则
                combOp,       // 分区间计算规则
                )

        foldByKey:

            combineByKeyWithClassTag[V](
                (v: V) => cleanedFunc(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
                cleanedFunc,  // 分区内计算规则
                cleanedFunc,  // 分区间计算规则
                )

        combineByKey :

            combineByKeyWithClassTag(
                createCombiner,  // 相同key的第一条数据进行的处理函数
                mergeValue,      // 表示分区内数据的处理函数
                mergeCombiners,  // 表示分区间数据的处理函数
                )

         */


        rdd.reduceByKey(_+_) // wordcount
        rdd.aggregateByKey(0)(_+_, _+_) // wordcount
        rdd.foldByKey(0)(_+_) // wordcount
        rdd.combineByKey(v=>v,(x:Int,y)=>x+y,(x:Int,y:Int)=>x+y) // wordcount

 例子:每个相同key的平均值

(1)用 aggregateByKey实现
package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkAggreateByKey {

  def main(args:Array[String]):Unit={
    //配置信息
    var conf = new SparkConf().setMaster("local").setAppName("deng")
    //
    val sc = new SparkContext(conf)

    // 1.分区内求最大值,分区间求和 aggregateByKey 函数柯里化
    val rdd1 = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",3),("a",4),("b",6)),2)
    val rdd2 = rdd1.aggregateByKey(0)(
      (x,y)=>math.max(x,y),
      (x,y)=>x+y
    )
    println(rdd2.collect().mkString(","))

    //2. 求每个 key 的平均值 (a,5),(b,5)
    //下面第一个位置的(0,0)第一个0 代表初始值的value,第2个0 代表初始值表示某key出现次数
    val rdd3= rdd1.aggregateByKey((0,0))(
      // v 表示key代表的值,t 表示前一个结果 和(0,0)类似
//      ((0,0),1)=>(0+1,0+1),
      (t,v)=>(t._1+v,t._2+1),
      (t1,t2)=>(t1._1+t2._1,t1._2+t2._2)
    )
    rdd3.collect().foreach(println)
    /*
      结果:
    * (b,(10,2))
      (a,(10,4))
    * */
    val rdd4= rdd3.mapValues(
      x=>x._1/x._2
    )
    rdd4.collect().foreach(println)
    /*
    结果
    * (b,5)
      (a,2)
    * */
  }
}

(2)使用combineByKey() 实现, 其有3个参数,第一个参数不是对初始值的转化规则

package deng.xiakeyun.bigdata.operator

import org.apache.spark.{SparkConf, SparkContext}

object SparkAggreateByKey {

  def main(args:Array[String]):Unit={
    //配置信息
    var conf = new SparkConf().setMaster("local").setAppName("deng")
    //
    val sc = new SparkContext(conf)

    // 分区内求最大值,分区间求和 aggregateByKey 函数柯里化
    val rdd1 = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",3),("a",4),("b",6)),2)
    val rdd2 = rdd1.aggregateByKey(0)(
      (x,y)=>math.max(x,y),
      (x,y)=>x+y
    )
    println(rdd2.collect().mkString(","))

    // 求每个 key 的平均值 (a,5),(b,5)
    //下面第一个位置的(0,0)第一个0 代表初始值的value,第2个0 代表初始值表示某key出现次数
    val rdd3= rdd1.aggregateByKey((0,0))(
      // v 表示key代表的值,t 表示前一个结果 和(0,0)类似
//      ((0,0),1)=>(0+1,0+1),
      (t,v)=>(t._1+v,t._2+1),
      (t1,t2)=>(t1._1+t2._1,t1._2+t2._2)
    )
    rdd3.collect().foreach(println)
    /*
      结果:
    * (b,(10,2))
      (a,(10,4))
    * */
    val rdd4= rdd3.mapValues(
      x=>x._1/x._2
    )
    rdd4.collect().foreach(println)
    /*
    结果
    * (b,5)
      (a,2)
    * */

    //使用combineByKey()
    val rdd5 = rdd1.combineByKey(
      x=>(x,1),// 将分区内的相同key的第一个数转化成初始值 v=>(v,1)
      (t:(Int,Int),v)=>(t._1+v,t._2+1),
      (t1:(Int,Int),t2:(Int,Int))=>(t1._1+t2._1,t1._2+t2._2)
    )
    println("=============combineByKey==================")
    rdd5.collect().foreach(println)
    /*
    * 结果:
    * (b,(10,2))
      (a,(10,4))
    * */
     println(rdd5.mapValues(x=>x._1/x._2).collect().mkString(","))
    // (b,5),(a,2)
  }
}

 行动算子

行动算子会触发作业的执行

1. rdd.collect() 

会将不同分区的数据按分区顺序采集到Driver端内存,形成数组。

2.rdd.reduce() 

......

package deng.xiakeyun.bigdata.operator.action

import org.apache.spark.{SparkConf, SparkContext}

object SparkAction {

  def main(args:Array[String]):Unit={
    //1. 配置信息
    val conf = new SparkConf().setMaster("local").setAppName("action")
    //2. 构建上下文对象
    val sc = new SparkContext(conf)
    // 3
    val rdd = sc.makeRDD(List(2,7,3,4,5))

    // reduce 聚合
    val sum = rdd.reduce(_+_)
    println(sum)// 15
    // rdd.count 统计数据
    println(rdd.count())
    // 获取数据源中的第一个数
    println(rdd.first())
    //rdd.take(3)// 获取数据源中的前3个数据
    println(rdd.take(3))
    //rdd.takeOrdered(num) 数据排序后取前num数据

    println(rdd.takeOrdered(3))
    // aggregate
    println(rdd.aggregate(0)(_+_,_+_)) //21
    println(rdd.aggregate(10)(_+_,_+_))//41

    //fold 折叠操作,aggregate 的简化版操作
    println(rdd.fold(0)(_+_))//21
    println(rdd.fold(10)(_+_))//41

    //countByKey 统计每个key 出现的次数
    val rdd2 = sc.makeRDD(List(("a",2),("a",1),("b",2),("a",2),("a",3),("b",2)))
    println(rdd2.countByKey()) //Map(a -> 4, b -> 2)

  }

}

 RDD 序列化

从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor
端执行
案例1:
object serializable02_function {
 def main(args: Array[String]): Unit = {
 //1.创建 SparkConf 并设置 App 名称
 val conf: SparkConf = new 
SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
 //2.创建 SparkContext,该对象是提交 Spark App 的入口
 val sc: SparkContext = new SparkContext(conf)
 //3.创建一个 RDD
 val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark", 
"hive", "atguigu"))
 //3.1 创建一个 Search 对象
 val search = new Search("hello")
 //3.2 函数传递,打印:ERROR Task not serializable
 search.getMatch1(rdd).collect().foreach(println)
 //3.3 属性传递,打印:ERROR Task not serializable
 search.getMatch2(rdd).collect().foreach(println)
 //4.关闭连接
 sc.stop()
 } }
class Search(query:String) extends Serializable {
 def isMatch(s: String): Boolean = {
 s.contains(query)
 }
 // 函数序列化案例
 def getMatch1 (rdd: RDD[String]): RDD[String] = {
 //rdd.filter(this.isMatch)
 rdd.filter(isMatch)
 }
// 属性序列化案例

def getMatch2(rdd: RDD[String]): RDD[String] = {

//rdd.filter(x => x.contains(this.query))

rdd.filter(x => x.contains(query))

//val q = query

//rdd.filter(x => x.contains(q))

}

}
 
Kryo 序列化框架
使用 Kryo 序列化,也要继承 Serializable 接口
object serializable_Kryo {
 def main(args: Array[String]): Unit = {
 val conf: SparkConf = new SparkConf()
 .setAppName("SerDemo")
 .setMaster("local[*]")
 // 替换默认的序列化机制
 .set("spark.serializer", 
"org.apache.spark.serializer.KryoSerializer")
 // 注册需要使用 kryo 序列化的自定义类
 .registerKryoClasses(Array(classOf[Searcher]))
 val sc = new SparkContext(conf)
 val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello atguigu", 
"atguigu", "hahah"), 2)
 val searcher = new Searcher("hello")
 val result: RDD[String] = searcher.getMatchedRDD1(rdd)
 result.collect.foreach(println)
 } }
case class Searcher(val query: String) {
 def isMatch(s: String) = {
 s.contains(query)
 }
 def getMatchedRDD1(rdd: RDD[String]) = {
 rdd.filter(isMatch) 
 }
 def getMatchedRDD2(rdd: RDD[String]) = {
 val q = query
 rdd.filter(_.contains(q))
 } }

RDD 持久化

1.RDD Cache 缓存
// cache 操作会增加血缘关系,不改变原有的血缘关系
println(wordToOneRdd.toDebugString)
// 数据缓存。
wordToOneRdd.cache()
// 可以更改存储级别
//mapRdd.persist(StorageLevel.MEMORY_AND_DISK_2)
存储级别
object StorageLevel {
 val NONE = new StorageLevel(false, false, false, false)
 val DISK_ONLY = new StorageLevel(true, false, false, false)
 val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
 val MEMORY_ONLY = new StorageLevel(false, true, false, true)
 val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
 val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
 val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
 val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
 val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
 val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
 val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
 val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用 persist 或 cache。
2.RDD CheckPoint 检查点
所谓的检查点其实就是通过将 RDD 中间结果写入磁盘由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。
// 设置检查点路径
sc.setCheckpointDir("./checkpoint1")
// 创建一个 RDD,读取指定位置文件:hello spark hello scala
val lineRdd: RDD[String] = sc.textFile("input/1.txt")
// 业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
 word => {
 (word, System.currentTimeMillis())
 } }
// 增加缓存,避免再重新跑一个 job 做 checkpoint
wordToOneRdd.cache()
// 数据检查点:针对 wordToOneRdd 做检查点计算
wordToOneRdd.checkpoint()
// 触发执行逻辑
wordToOneRdd.collect().foreach(println)
3.缓存和检查点区别
1)Cache 缓存只是将数据保存起来,不切断血缘依赖。Checkpoint 检查点切断血缘依赖。
2)Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint 的数据通常存
储在 HDFS 等容错、高可用的文件系统,可靠性高。
3)建议对 checkpoint()的 RDD 使用 Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存
中读取数据即可,否则需要再从头计算一次 RDD。

累加器:分布式共享只写变量

 1.用来把excutor端的数据聚合到driver端。

2. 传回driver 进行merge

3. 转化算子中如果调用累加器没有触发行动算子时,是不会执行的(少加现象)。

4. 多少次调用包含累加器逻辑的rdd 触发行动算子时,会出现多加现象

5. 一般情况下累加器放在行动算子中。

未使用累加器时:

 

 

 

 使用累加器时:

例1:系统自带的累加器

import org.apache.spark.{SparkConf, SparkContext}

object SparkAcc {

  def main(args:Array[String]):Unit={

    //conf
    val conf = new SparkConf().setMaster("local").setAppName("deng")
    val sc  = new SparkContext(conf)
    // 系统累加器
    // 累加器的作用是将excutor端的数据聚合到driver端
    val rdd = sc.makeRDD(List(1,2,3,4,5))
    //定义累加器
    val acc = sc.longAccumulator("sum")
    rdd.foreach(
      num=>{

        // 使用累加器
        acc.add(num)
      }
    )
    // 获取累加器的值
    println(s"acc的值为${acc.value}")
  }

}

例2: 自定义累加器(wordcount)

import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

object MyAcc {

  def main(args: Array[String]): Unit = {

    //conf
    val conf = new SparkConf().setMaster("local").setAppName("deng")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark", "scala", "spark", "python"))
    /*
    * 自定义累加器  WordCount
    * 继承 AccumulatorV2[IN,OUT] 定义泛型
    * IN: 累加器输入的数据类型,String
    * OUT: 累加器返回的数据类型 mutable.Map[String,Long]
    //1. 创建累加器
    * 2. 注册累加器
    * 3. 使用累加器
    * 4. 获取累加器的结果
    * */
    // 1.创建累加器
    val wcAcc = new MyAccumulator()
    // 2. 注册累加器
    sc.register(wcAcc, name = "wordCountAcc")

    rdd.foreach(
      word => {
        // 3. 使用累加器
        wcAcc.add(word)
      }
    )
    // 获取累加器的值
    val result = wcAcc.value
    println(result)
    sc.stop()
  }

  //自定义累加器,extends  AccumulatorV2
  class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Long]] {
    // 准备一个空的map
    private var wcMap = mutable.Map[String, Long]()
    // 判断是否为初始状态
    override def isZero: Boolean = {
      wcMap.isEmpty
    }

    override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
      new MyAccumulator()
    }

    override def reset(): Unit = {
      wcMap.clear()
    }

    // 获取累加器需要计算的值
    override def  add(word:String): Unit = {
        val newCnt = wcMap.getOrElse(word,0L)+1
        wcMap.update(word,newCnt)
    }
    // driver 合并多个累加器的结果
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
      val map1 = this.wcMap
      val map2 = other.value
      map2.foreach{
        case (word,count)=>{
          val newCount = map1.getOrElse(word,0L)+count
          map1.update(word,newCount)
        }
      }
    }
    // 累加器结果
    override def value: mutable.Map[String, Long] = {
      wcMap
    }
  }

}

广播变量:分布式共享只读变量

1.可以解决join 性能太差问题。(字典的k-v )

import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

object SparkBC {

  def main(args:Array[String]):Unit={

    //conf
    val conf = new SparkConf().setMaster("local").setAppName("deng")
    val sc  = new SparkContext(conf)
    // join 会进行shuffle 性能差,可以考虑用map的方式
    val rdd1= sc.makeRDD(List(("a",1),("b",3),("c",4)))

    val map = mutable.Map(("a",5),("b",1),("c",4))

    rdd1.map {
      case(w,c)=>{
        val nc = map.getOrElse(w,0)
        (w,(c,nc))
      }
    }.collect().foreach(println)

    sc.stop()

  }

}

但闭包数据都是以task 为单位发送的,每个task 中都包含了一份闭包数据。闭包数据偏大或task 比较多时容易占用大量内存。

广播变量方式:

import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

object SparkBC {

  def main(args:Array[String]):Unit={

    //conf
    val conf = new SparkConf().setMaster("local").setAppName("deng")
    val sc  = new SparkContext(conf)
    // join 会进行shuffle 性能差,可以考虑用map的方式
    val rdd1= sc.makeRDD(List(("a",1),("b",3),("c",4)))

    val map = mutable.Map(("a",5),("b",1),("c",4))

    // 定义广播变量
    val bc = sc.broadcast(map)

    rdd1.map {
      case(w,c)=>{
        val nc = bc.value.getOrElse(w,0)
        (w,(c,nc))
      }
    }.collect().foreach(println)

    sc.stop()

  }

}

 

 

 

posted @ 2021-06-30 06:47  冰底熊  阅读(142)  评论(0)    收藏  举报