Spark详细总结

一:算子统计

flatmap

map

mapValues

一:Spark简介

  1. park和Hadoop的根本差异是多个作业之间的数据通信问题 : Spark多个作业之间数据通信是基于内存,而Hadoop是基于磁盘。

 Spark的缓存机制比HDFS的缓存机制高效。

二:wordCount()分析 (flatmap() 与 map())

flatmap与map我的理解:

读取数据是一行一行读的,(如果每一行的数据源是    (Hello World Hello Spark)  

)

补充:

用任何一个框架都需要一个环境对象 sc就是SparkContext的环境对象(环境配置对象参数)

 val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)

 flatMap()将整体拆分成为个体。

val wordGroup: RDD[(String, Iterable[(String, Int)])] = fileDataRDD.flatMap(_.split(" "))
        .map((_, 1))
        .groupBy(_._1)
      wordGroup.mapValues(
        list => {
          list.map(_._2)
          list.map(_._2).sum
          list.map(_._2).reduce(_+_)
          list.map(_._2).size
        }
      )
      val wordCount: RDD[(String, Int)] = wordGroup.mapValues(_.map(_._2).reduce(_ + _))
      wordGroup.collect().foreach(println)

 // flatMap, map, groupBy, mapValues, collect方法都是Spark框架提供的方法,而不是Scala集合的方法
        // 为了降低学习的成本,使用的难度,让Spark的API和Scala方法变得类似。
        val words = fileDatas.flatMap(_.split(" "))
        val wordToOne = words.map((_, 1))

        // reduceByKey : 相同的key,对value进行两两聚合
        // (word, 1), (word, 1), (word, 1)
        // (word, (1,1,1,1,1))
        val wordCount = wordToOne.reduceByKey(_+_)
        wordCount.collect().foreach(println)

 三:spark环境

四:Spark运行架构

 

YARN中ApplicationMaster能做到资源和计算的解耦合,Driver和Executor使用的计算框架是Spark,因此也可以使用其它的框架,而YARN只做资源的调度使用

 Spark应用程序提交到Yarn环境中执行的时候,一般会有两种部署执行的方式:Client和Cluster。两种模式主要区别在于:Driver程序的运行节点位置。

五:Spark核心编程

 首先,装饰者模式只会进行功能组合,不会执行,另外RDD的装饰者模式与IO的装饰者模式的区别在于,RDD不保留数据,只对数据进行处理,而IO中有缓冲区可以保留数据。

5.1:执行原理

六:内存磁盘如何分区及存储原理

6.1:算子的创建:

因为简明知意,makeRDD用的比较多

 分区数量是如何计算的(只要牵涉的到分区就会出现数据倾斜现象)

 七:算子来啦(转换与行为算子)

 转换算子就是将旧的RDD转换成为新的RDD,因为要组合功能。

flatMap()保留,map()不保留

 不在一个分区是无法进行比较的

 map()  VS  mapPartitions()

mapPartitions()是批量处理的  因此入参为迭代器 出参也为迭代器,map处理一个返回一个,mapPartitions处理一批返回一批

 迭代器Iterator也是集合

7.1:groupBy()

 groupBy()引出shuffle

 shuffle一定会落盘,因为RDD不保留数据,因此,在shuffle阶段,一定会存在Write(shuffle左边)和read(shuffle右边)

 所有含有shuffle的算子都有改变分区的能力

 因为隐式转换所以字符串可以直接()写下标

7.2:补充零拷贝(NIO实现)页缓存

 

 

 

7.3:flatMap()

7.4:glom

7.5:filter

val fileRDD = sc.textFile("data/apache.log")
        // filter算子返回的结果为按照规则保留的数据本身
        fileRDD.filter(
            line => {
                val datas = line.split(" ")
                val time = datas(3)
                time.startsWith("17/05/2015")
            }
        ).map(
            line => {
                val datas = line.split(" ")
                datas(6)
            }
        ).collect.foreach(println)

7.6: sample

 

7.7: distinct

scala使用的是单点集合(缺点,单点的资源有限),而RDD使用的是集群去重

7.8:coalesce(缩小分区) 与 repartition(扩大分区) 

7.9:sortBy()

八:双值类型数据集的算子(交集并集差集拉链)

单Value(单一数据集)数据操作,双Value(多个数据集)数据操作

九:Key - Value类型算子(隐式转换rddToPairRDDFunction)

9.1:partitionBy()

 repartition : 重分区(分区数量)

partitionBy : 重分区(数据的位置 数据进入到哪个分区), 数据路由(Hash定位),分区器 如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)

Spark中默认常用的分区器:RangePartitioner(要求数据必须能排序) & HashPartitioner(默认)

思考一个问题:如果重分区的分区器和当前RDD的分区器一样怎么办?

此时不做任何处理,还是当前分区,不需要重新shuffle

9.2:reduceByKey  groupByKey

TODO reduceByKey算子用于将数据集中相同的key的value数据聚合在一起实现两两聚合
spark的算子有很多是以byKey结尾的,就是说将K-V独立使用
TODO reduceByKey算子可以实现 WordCount

9.3:aggregateByKey  foldByKey

需求 数据集中相同的Key的分区内取最大值,分区间求和

reduceByKey  groupByKey只考虑key不考虑分区而reduceByKey  在逻辑上,

分区内和分区间是一样的都是聚合   因此reduceByKey  groupByKey都不能处理该需求。

 需求  如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。aggregateByKey

aggregateByKey算子存在函数柯里化,有多个参数列表
第一个参数列表中有一个参数
第一个参数表示计算初始值
第二个参数列表中有二个参数
第一个参数表示 : 分区内计算规则
第二个参数表示 : 分区间计算规则

9.4:combineByKey

 

reduceByKey,aggregateByKey ,foldByKey, combineByKey本质区别

9.5:sortByKey

9.6:Join  leftOuterJoin  rightOuterJoin fullOuterJoin

9.7 :  cogroup

 join算子体现的是数据的关系
join算子将两个数据集中相同的key的数据,连接在一起
zip是将两个数据集位置相同的数据,连接起来与key无关
join 可能存在笛卡尔积  也 存在shuffle() 
笛卡尔积 在落盘的时候  如果存在大量相同的key的时候数据量会激增 
如果能够使用其它算子实现的功能,那么不推荐使用join

rdd1.join(rdd2).collect().foreach(println)

rdd1.leftOuterJoin(rdd2).collect().foreach(println) rdd1.rightOuterJoin(rdd2).collect().foreach(println) rdd1.fullOuterJoin(rdd2).collect().foreach(println)

cogroup = connect(多个数据集) + group(单一数据集)

9.6:案例实操

 方式一:

//    统计出每一个省份每个广告被点击数量排行的Top3
    val fileDatas: RDD[String] = sc.textFile("data/agent.log")

    val groupDatas: RDD[(String, Iterable[String])] = fileDatas.groupBy(
      line => {
        val datas: Array[String] = line.split(" ")
        datas(1)
      }
    )

    val top3: RDD[(String, List[(String, Int)])] = groupDatas.mapValues(
      //数据集是List(line, line, line)
      list => {
        //数据集是line
        val adToCount: Map[String, Int] = list.map(
          line => {
            val datas: Array[String] = line.split(" ")
            (datas(4), 1)
          }
          //数据集是List((省份,广告))  按照省份进行分组统计
        ).groupBy(_._1).mapValues(_.size)
        //Map[String, Int] [广告,点击次数]
        //      在  groupDatas.mapValues(  的架子中都是对value进行处理的
        //元组天生的排序  数据类型[广告,点击次数]   前三条
        adToCount.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    top3.collect().foreach(println)

    sc.stop()
  }

方式二:

 val fileDatas: RDD[String] = sc.textFile("data/agent.log")
    // 1. 将广告进行统计分析 (word, cnt)
    //    ((省份1,广告1), 1),((省份2,广告2), 1)((省份1,广告1), 1)
    //    ((省份1,广告1), sum),((省份1,广告2), sum)
    val reduceDatas: RDD[((String, String), Int)] = fileDatas.map(
      line => {
        val datas: Array[String] = line.split(" ")
        ((datas(1), datas(4)), 1)
      }
    ).reduceByKey(_ + _)
    // 1.5 将统计结果进行格式转换
    //    (省份1,(广告1, sum)),(省份1,(广告2, sum))
    val mapDatas: RDD[(String, (String, Int))] = reduceDatas.map {
      case ((prv, ad), sum) => {
        (prv, (ad, sum))
      }
    }
    // 2. 将统计结果按照省份进行分组
    //   (省份1, (广告1, sum),(广告2, sum))
    val prvGroupDatas: RDD[(String, Iterable[(String, Int)])] = mapDatas.groupByKey()
    // 3. 将分组后的数据按照点击数量进行排序(降序),取前3名
    val top3: RDD[(String, List[(String, Int)])] = prvGroupDatas.mapValues(
      iter => {
        iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    top3.collect().foreach(println)

十:action行动算子(是对结果进行处理的)

TODO : 所谓的行动(Action)算子,其实就是RDD用于触发作业执行的方法,类似于IO中的read方法

转换算子的返回值为RDD 行动算子的返回值为具体的结果

行动算子和作业的关系 :1 对 1

10.2:reduce

 reduce算子可以触发作业的执行,分区内先两两计算,分区间再两两计算

分区内计算都是在Executor。

分区间计算都是在Driver

10.3:count  first  take  takeOrdered

10.4:  aggregate  fold

10.5: countByKey  countByValue

10.5: save相关的算子

rdd.saveAsTextFile("output1")
rdd.saveAsObjectFile("output2")
rdd.saveAsSequenceFile("output3")

10.5:  collect  foreach

10.6: 序列化闭包检测

 一个函数使用了外部的数据,并且改变了这个数据的生命周期 将数据包含到函数内部,形成了一个闭合的环境,这个环境称之为闭包环境,简称闭包

Spark在运行作业之前,会判断程序逻辑中是否包含闭包
如果
存在闭包操作,那么需要检测闭包中使用的数据是否能够序列化,如果不能,那么发生错误
这个操作称之为闭包检测

 

十一:RDD阶段的划分

 前一个阶段不执行完,后面的阶段是无法执行的。

十二:依赖关系和血缘关系(宽依赖窄依赖)

 

十三:持久化

(持久化只针对于当前应用程序,资源使用完之后释放资源,不会给其它节点使用。因此诞生了检查点)

 将计算结果缓存起来。

数据的持久化默认将数据保存到内存中,重复使用
wordToOne.cache()   
持久化操作也可以保存到磁盘中
wordToOne.persist()

保存级别设置 

十三:RDD CheckPoint检查点

java中的finalize方法是Object类中提供的一个方法,在GC准备释放对象所占用的内存空间之前,它将首先调用finalize()方法。

持久化只针对于当前应用程序,资源使用完之后释放资源,不会给其它节点使用。

 持久化只针对于当前应用程序
跨应用程序的数据共享需要采用 检查点机制

设置的方式

① :sc.setCheckpointDir("cp")   该检查点的目录一般为分布式目录以便各个节点使用
②:wordToOne.checkpoint()

 持久化操作会在血缘关系中增加依赖
检查点一般会将数据保存到分布式文件系统中(HDFS)(新的数据源)
检查点会切断血缘即减少血缘依赖,因为可以把HDFS中相对可靠的数据作为新的数据源。

十四:自定义分区  RDD文件读取与保存

 Hash分区:对于给定的key,计算其hashCode,并除以分区个数取余

Range分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序

十四:累加器(数据模型)(功能比较单一没有shuffle性能相对高)

累加器的现象

 分析:

如果数据和RDD有关系,那么可以从Driver端发送到Executor端执行
如果执行完毕,那么可以将结果拉回到Driver端
如果数据和RDD没有关系,因为计算中存在闭包操作,所以RDD会将数据发送到Executor
但是无法将数据从Executor端拉回到Driver端。

如果想要将和RDD无关的数据拉回到Driver端,RDD模型处理不了,需要采用新的数据模型——累加器

 doubleAccumulator

collectionAccumulator  累加器

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

        // TODO Spark 环境
        val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
        val sc = new SparkContext(conf)

        // TODO 1. 创建累加器
        val wordCountAcc = new MyAcculumator
        // TODO 2. 将累加器注册到Spark中
        sc.register(wordCountAcc, "WordCount")

        val rdd = sc.makeRDD(
            List(
                "Hello",
                "Hello",
                "Hello",
                "World",
                "Hello",
            ),2
        )

        rdd.foreach(
            word => {
                // TODO 3. 使用累加器
                wordCountAcc.add(word)
            }
        )

        // TODO 4. 获取累加器的结果
        println(wordCountAcc.value)

        sc.stop()
    }
    // TODO 自定义数据累加器(WordCount)
    // 1. 继承AccumulatorV2
    // 2. 定义数据的泛型
    //    IN : String
    //    OUT : mutable.Map[String, Int]
    // 3. 重写方法(3(计算) + 3(状态) = 6)
    class MyAcculumator extends AccumulatorV2[String, mutable.Map[String, Int]]{
        private val wordCountMap = mutable.Map[String, Int]()

        // TODO 判断当前累加器是否为初始状态
        override def isZero: Boolean = {
            wordCountMap.isEmpty
        }

        // TODO 复制累加器
        override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
            new MyAcculumator()
        }

        // TODO 重置累加器
        override def reset(): Unit = {
            wordCountMap.clear()
        }

        // TODO 将外部的数据增加到累加器中
        override def add(word: String): Unit = {
            val oldCnt: Int = wordCountMap.getOrElse(word, 0)
            wordCountMap.update(word, oldCnt + 1)
        }

        // TODO 将多个累加器进行合并
        override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
            other.value.foreach {
                case (word, cnt) => {
                    val oldCnt: Int = wordCountMap.getOrElse(word, 0)
                    wordCountMap.update(word, oldCnt + cnt)
                }
            }
        }

        // TODO 获取累加器的结果
        override def value: mutable.Map[String, Int] = {
            wordCountMap
        }
    }

十五:Join现象引出广播变量

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

        // TODO Spark 环境
        val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
        val sc = new SparkContext(conf)

        val rdd1 = sc.makeRDD(
            List(
                ("a", 1),
                ("b", 2),
            )
        )

        val rdd2 = sc.makeRDD(
            List(
                ("a", 3),
                ("b", 4),
            )
        )
        // ("a", 1)
        // ( a,(1,3))

        //rdd1.join(rdd2).collect().foreach(println)
        // 计算的对象是以Task为单位进行数据发送的,所以可能会导致大量的数据冗余
        // 所以如果计算对象占用比较大的资源,性能会急剧下降
        // 为了提供数据访问效率,可以将数据发送给Executor,而不是发给Task。
        // RDD数据模型只能给Task发。只能采用其他的数据模型:TODO 广播变量
        val map = mutable.Map[String, Int](
            ("a", 3),
            ("b", 4)
        )
        // TODO 使用广播变量
        val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(map)

        rdd1.map {
            case (k, v1) => {
                // TODO 获取广播变量的值
                var v2 = bc.value.getOrElse(k, 0)
                (k, (v1, v2))
            }
        }.collect.foreach(println)

        sc.stop()
    }

计算的对象是以Task为单位进行数据发送的,所以可能会导致大量的数据冗余 且 如果计算对象占用比较大的资源,性能会急剧下降
为了提供数据访问效率,
可以将数据发送给Executor,而不是发给Task。
但是RDD数据模型只能给Task发。只能采用其他的数据模型: 广播变量(可以将数据发送给Executor  使得同一个节点上的Task可以共享一份资源,并且如果有Executor节点与Driver通信比较慢的时候 可以从就近的Executor上获得需要的资源)

十五:SparkSQL(DataFrame DataSet RDD 转换)

Spark + Hive =>  Shark

Spark SQL是Spark用于结构化数据(structured data)处理的Spark模块。

 1.2 SparkSQL特点:

  无缝的整合了 SQL 查询和 Spark 编程

 SparkSQL可以简化RDD的开发,提高开发效率

15.1:DataFrame  与 DataSet 与 RDD概述

 SQL语言本身没有类型

 

 工厂模式生产的对象是有相同的功能的,因为从同一条生产线下来的。

Builder模式要生产某个对象需要经过复杂的过程,但是builder模式会把这些过程顺序确定下来。

Scala中的构造方法有两种,主构造方法和辅助构造方法,辅助构造方法的函数名一定为this。

15.2:DataFrame  与 DataSet 与 RDD

 DataFrame : 是SparkSQL中的核心数据模型,是弱类型的模型,不关心数据的类型操作。

因为SQL的局限性,因此在处理数据的时候,DataFrame提供了一种DSL语法(面向对象的语法)

 SparkSQL 与 RDD的关系与转换

SparkSQL模块等同于是对SparkCore一个封装,专门用于结构化数据处理的场景

Dataset : 就是使用面向对象的方式操作数据, 类,属性 => 结构 就是

DataFrame的基础上,增加了类型信息

SparkSQL中存在大量的隐式转换操作,但是一般会导入使用 import时,使用的spark并不是包名,是环境对象的名称。

 DataFrame 与 DataSet的区别?

 DataFrame是特定类型的DataSet[Row]

说明DataSet兼容DataFrame()是将来用的比较多的

 注意:如果从内存中获取数据,spark可以知道数据类型具体是什么如果是数字,默认作为Int处理;但是从文件中读取的数字,不能确定是什么类型,所以用bigint接收,可以和Long类型转换,但是和Int不能进行转换

SparkSQL技术选型:

15.2:UDF 与 UDAF

package com.atguigu.bigdata.spark.sql01

import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.{DataFrame, Encoder, Encoders, SparkSession, functions}

/*
1: 创建环境
2:读取数据源
3: dataFrame 创建临时视图("user)
4:// 自定义的函数在SQL文使用
 */
object SparkSQL09_DataFrame_UDAF {
  def main(args: Array[String]): Unit = {
    // TODO SparkSQL 环境\
    val spark: SparkSession = SparkSession.builder
      .master("local[*]")
      .appName("SQL")
      .config("spark.testing.memory", "471859200")
      .getOrCreate()
    val df = spark.read.json("data/user.json")
    df.createOrReplaceTempView("user")
    // TODO UDAF使用时,需要采用特殊的方式实现自定义操作
    // TODO 强类型操作不能应用于弱类型操作,如果想用,那么需要转换functions.udaf
    // Spark3.0以后,可以通过转换,让强类型应用在弱类型操作
    spark.udf.register("avgAge",functions.udaf(new AvgAgeUDF()))
    // TODO : UDF
    // 自定义的函数在SQL文使用
    spark.sql("select avgAge(age) from user").show()

    spark.stop()
  }
  case class CalcBuffer(var total : Long , var cnt : Long)
  // TODO SparkSQL中UDAF一般情况下都采用自定义函数类
  //   1. 继承org.apache.spark.sql.expressions.Aggregator
  //   2. 定义泛型
  //      IN : Long
  //      BUFF : CalcBuffer
  //      OUT : Long
  //   3. 重写方法 (4 + 2(固定) = 6)
  class AvgAgeUDF extends Aggregator[Long,CalcBuffer,Long]{
    // TODO 缓冲区的初始化
    override def zero: CalcBuffer = {
      CalcBuffer(0l,0l)
    }
    // TODO 将输入的数据和缓冲区的数据进行聚合操作
    override def reduce(buff: CalcBuffer, a: Long): CalcBuffer ={
      buff.total += a
      buff.cnt += 1
      buff
    }
    // TODO 多个缓冲区的合并操作
    override def merge(b1: CalcBuffer, b2: CalcBuffer): CalcBuffer = {
      b1.total += b2.total
      b1.cnt += b2.cnt
      b1
    }
    // TODO 计算数据
    override def finish(reduction: CalcBuffer): Long = {
      reduction.total / reduction.cnt
    }

    override def bufferEncoder: Encoder[CalcBuffer] = Encoders.product

    override def outputEncoder: Encoder[Long] = Encoders.scalaLong
  }

}

UDAF老版本:

def main(args: Array[String]): Unit = {
    // TODO SparkSQL 环境\
    val spark: SparkSession = SparkSession.builder
      .master("local[*]")
      .appName("SQL")
      .config("spark.testing.memory", "471859200")
      .getOrCreate()
    val df = spark.read.json("data/user.json")
    df.createOrReplaceTempView("user")
    // TODO UDAF使用时,需要采用特殊的方式实现自定义操作
    // TODO 强类型操作不能应用于弱类型操作,如果想用,那么需要转换functions.udaf
    // Spark3.0以后,可以通过转换,让强类型应用在弱类型操作
    spark.udf.register("avgAge",new AvgAgeUDAF())
    // TODO : UDF
    // 自定义的函数在SQL文使用
    spark.sql("select avgAge(age) from user").show()

    spark.stop()
  }
  // TODO SparkSQL中UDAF一般情况下都采用自定义函数类
  //  使用早期Spark版本的UDAF(弱类型)
  //  1. 继承UserDefinedAggregateFunction
  //  2. 重写方法(8)
  class AvgAgeUDAF extends UserDefinedAggregateFunction{
    // TODO 输入数据的结构
    override def inputSchema: StructType = {
      StructType(
        Array(
          StructField("age",LongType)
        )
      )
    }
    // TODO 缓冲区数据的结构
    override def bufferSchema: StructType = {
      StructType(
        Array(
          StructField("total",LongType),
          StructField("cnt",LongType)
        )
      )
    }
    // TODO 输出数据的类型
    override def dataType: DataType = LongType
    // TODO 计算稳定性
    override def deterministic: Boolean = true
    // TODO 缓冲区的初始化
    override def initialize(buffer: MutableAggregationBuffer): Unit = {
      buffer.update(0,0l)
      buffer.update(1,0l)
    }
    // TODO 将输入的数据和缓冲区的数据进行聚合操作
    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
      buffer.update(0,buffer.getLong(0) + input.getLong(0))
      buffer.update(1,buffer.getLong(1)+1)
    }
    // TODO 多个缓冲区的合并操作
    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
      buffer1.update(0,buffer1.getLong(0) + buffer2.getLong(0))
      buffer1.update(1,buffer1.getLong(1) + buffer2.getLong(1))
    }

    override def evaluate(buffer: Row): Any = {
      buffer.getLong(0) / buffer.getLong(1)
    }
  }

UDAF_Class老版本:

def main(args: Array[String]): Unit = {
    // TODO SparkSQL 环境\
    val spark: SparkSession = SparkSession.builder
      .master("local[*]")
      .appName("SQL")
      .config("spark.testing.memory", "471859200")
      .getOrCreate()
    val df = spark.read.json("data/user.json")
    // TODO Spark3.0版本前,强类型的UDAF不能应用于弱类型的SQL中
    import spark.implicits._
    val udaf: AvgAgeUDAF = new AvgAgeUDAF
    val ds: Dataset[User] = df.as[User]
    ds.select(udaf.toColumn).show()


    spark.stop()
  }
  case class User(id : Long , name : String , age : Long)
  case class CalacBuffer(var total : Long , var cnt : Long)
  // TODO SparkSQL中UDAF一般情况下都采用自定义函数类
  //  使用早期Spark版本的UDAF(强类型)
  //  1. 继承Aggregator
  //  2. 定义泛型
  //     IN : User
  //     BUFF : CalcBuffer
  //     OUT : Long
  //  3. 重写方法(6)
  class AvgAgeUDAF extends Aggregator[User,CalacBuffer,Long]{
    override def zero: CalacBuffer = {
      CalacBuffer(0l,0L)
    }

    override def reduce(b: CalacBuffer, user: User): CalacBuffer = {
      b.total += user.age
      b.cnt += 1
      b
    }

    override def merge(b1: CalacBuffer, b2: CalacBuffer): CalacBuffer = {
      b1.total += b2.total
      b1.cnt += b2.cnt
      b1
    }

    override def finish(buffer: CalacBuffer): Long = {
      buffer.total / buffer.cnt
    }

    override def bufferEncoder: Encoder[CalacBuffer] = Encoders.product

    override def outputEncoder: Encoder[Long] = Encoders.scalaLong
  }

15.3:通用读取方式和默认读取方式

 user.json is not a Parquet file. expected magic number at tail [80, 65, 82, 49] but found [32, 53, 48, 125]
SparkSQL
通用读取的方法,读取的文件为Parquet格式(列式存储格式)
val df = spark.read.load("data/users.parquet")
如果想要读取其他格式的文件也可以,需要额外增加参数进行解析

df.write.mode("append").format("json").save("output")

sparkSQL基于SparkCore

SparkCore读取数据是依赖于Hadoop

Hadoop读取数据是按行读取

SparkSQL读取JSON格式的文件,要求每一行数据必须符合JSON格式

 读入数据:

val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL")

//创建SparkSession对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()

import spark.implicits._

//方式1:通用的load方法读取
spark.read.format("jdbc")
  .option("url", "jdbc:mysql://linux1:3306/spark-sql")
  .option("driver", "com.mysql.jdbc.Driver")
  .option("user", "root")
  .option("password", "123123")
  .option("dbtable", "user")
  .load().show


//方式2:通用的load方法读取 参数另一种形式
spark.read.format("jdbc")
  .options(Map("url"->"jdbc:mysql://linux1:3306/spark-sql?user=root&password=123123",
    "dbtable"->"user","driver"->"com.mysql.jdbc.Driver")).load().show

//方式3:使用jdbc方法读取
val props: Properties = new Properties()
props.setProperty("user", "root")
props.setProperty("password", "123123")
val df: DataFrame = spark.read.jdbc("jdbc:mysql://linux1:3306/spark-sql", "user", props)
df.show

//释放资源
spark.stop()

写入数据:

case class User2(name: String, age: Long)
。。。
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL")

//创建SparkSession对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._

val rdd: RDD[User2] = spark.sparkContext.makeRDD(List(User2("lisi", 20), User2("zs", 30)))
val ds: Dataset[User2] = rdd.toDS
//方式1:通用的方式  format指定写出类型
 
//方式2:通过jdbc方法
val props: Properties = new Properties()
props.setProperty("user", "root")
props.setProperty("password", "123123")
ds.write.mode(SaveMode.Append).jdbc("jdbc:mysql://linux1:3306/spark-sql", "user", props)

//释放资源
spark.stop()

十六:SparkStreaming(数据模型是离散流DStrem)

 实时  离线   批量  流式

Spark是一个离线的批量数据处理的框架

 SparkStreaming是一个准实时,微批次的处理框架

16.1:SparkStreaming知识点

Discretized Stream(离散流)是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark原语操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示每个RDD含有一段时间间隔内的数据

16.2:DStream

 blockingQueue队列:阻塞式队列,有反压机制,可以避免数据挤压。

 采集数据的方式:DStream监控目录变化,将RDD放入到离散化流DStream中,自定义数据源。

object SparkStreaming05_DIY {
  def main(args: Array[String]): Unit = {
    //TODO SparkStreaming 环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName("Streaming")
      .set("spark.testing.memory", "471859200")
    //StreamingContext独享的第二个构造参数表示数据采集周期
    //    val ssc: StreamingContext = new StreamingContext(conf,new Duration(3 * 1000))

    val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))

    val uuidDS: ReceiverInputDStream[String] = ssc.receiverStream(new MyReceiver)
    uuidDS.print()
    //启动采集器
    ssc.start()
    // 等待采集器结束
    ssc.awaitTermination()

  }
  
  class MyReceiver extends Receiver[String](StorageLevel.MEMORY_ONLY){
    private var flg = true
    override def onStart(): Unit = {
      //采集数据
      while (flg) {
        //采集数据
        val uuid: String = UUID.randomUUID().toString   
        //存储数据
        store(uuid)
        Thread.sleep(1000)
      }
    }

    override def onStop(): Unit = {
      //释放资源
      flg = false
    }
  }
}

十七:DStream转换(有无状态转换)

离散化流无法完成排序操作 ,离散化流其实就是每个时间段RDD的封装 ,离散化流可以转换为RDD实现操作

transform用于将离散化流实现不了的功能进行调用,transform方法是DStream的方法,称之为原语,RDD的方法称之为算子 如果想要在Driver端周期性执行一些逻辑的场合,需要使用transform。

将两个窗口key相同的连接起来

使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。

reduceByKey原语不会保留中间计算结果,一旦采集周期结束,数据丢弃 ,如果想要保留中间计算结果,需要有状态操作

从检查点中恢复数据

def main(args: Array[String]): Unit = {
    //从检查点中恢复数据
    val outerSSC = StreamingContext.getOrCreate("cp",() => {
      //TODO SparkStreaming 环境
      val conf: SparkConf = new SparkConf()
        .setMaster("local[*]")
        .setAppName("Streaming")
        .set("spark.testing.memory", "471859200")
      //StreamingContext独享的第二个构造参数表示数据采集周期
      //    val ssc: StreamingContext = new StreamingContext(conf,new Duration(3 * 1000))

      val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))

      ssc.checkpoint("cp")
      val socketDS: ReceiverInputDStream[String] = ssc.socketTextStream("localhost", 8888)
      val wordDS: DStream[String] = socketDS.flatMap(_.split(" "))
      val wordToOneDS: DStream[(String, Int)] = wordDS.map((_, 1))
      wordToOneDS.updateStateByKey(
        (seq : Seq[Int] , buffer : Option[Int]) => {
          Option(seq.sum + buffer.getOrElse(0))
        }
      ).print()
      ssc
    })
    //SparkStreaming (reduceByKey) + Redis内存数据库 = 有状态

    outerSSC.start()

    outerSSC.awaitTermination()

  }

十八:DStream窗口

窗口划过的规律是:数据由无到有再到无(先多后少后变没)

DStream离散流有window方法:wordToOneDS.window(Seconds(9),Seconds(3))可以设置窗口的大小。

window操作中,窗口范围和滑动的幅度应该为采集周期的整数倍

如果窗口范围和滑动的幅度相同, 那么就是滚动窗口,没有重复数据 默认情况下,不使用窗口,也会有窗口操作,窗口范围取值为1个采集周期,滑动幅度也是一个采集周期。

如果窗口范围和滑动的幅度不相同, 那么就是滑动窗口
1>窗口范围大于滑动的幅度, 存在重复数据
2>窗口范围小于滑动的幅度, 存在丢失数据的可能

二十:优雅的关闭

对于流式数据,线程的启动和关闭应该是分开的,属于两个线程。 

优雅的关闭,不要强制关闭,关闭前,不再接受数据,而是把手头上的数据处理完成之后再关闭。

二十一:DStream输出

如果想要将DStream进行特殊处理,那么可以转换为底层RDD实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值