1. SparkSQL概述

1.1 SparkSQL

Spark SQL是Spark用于结构化数据(structured data)处理的Spark模块。
与基本的Spark RDD API不同,Spark SQL的抽象数据类型为Spark提供了关于数据结构和正在执行的计算的更多信息。
在内部,Spark SQL使用这些额外的信息去做一些额外的优化,有多种方式与Spark SQL进行交互,比如: SQL和DatasetAPI。
当计算结果的时候,使用的是相同的执行引擎,不依赖你正在使用哪种API或者语言。这种统一也就意味着开发者可以很容易在不同的API之间进行切换,这些API提供了最自然的方式来表达给定的转换。
我们已经学习了Hive,它是将Hive SQL转换成 MapReduce然后提交到集群上执行,大大简化了编写 MapReduce的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。所以Spark SQL的应运而生,它是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快!
Spark SQL它提供了2个编程抽象,类似Spark Core中的RDD
DataFrame
DataSet

1.2 SparkSQL特点

1.2.1 易整合

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

Spark对比 Redis spark和sparksql_Spark对比 Redis

1.2.2 统一的数据访问方式

使用相同的方式连接不同的数据源

Spark对比 Redis spark和sparksql_SQL_02

1.2.3 兼容Hive

Spark对比 Redis spark和sparksql_API_03

1.2.4 标准的数据连接

通过 JDBC 或者 ODBC 来连接

Spark对比 Redis spark和sparksql_spark_04

1.3 什么是DataFrame

在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame与RDD的主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。
同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。从 API 易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API 要更加友好,门槛更低。

Spark对比 Redis spark和sparksql_SQL_05


上图直观地体现了DataFrame和RDD的区别。

左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得 Spark SQL 可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。

DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待

DataFrame也是懒执行的,但性能上比RDD要高,主要原因:优化的执行计划,即查询计划通过Spark catalyst optimiser进行优化。比如下面一个例子:

Spark对比 Redis spark和sparksql_Spark对比 Redis_06


为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。

如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。

Spark对比 Redis spark和sparksql_spark_07

1.4 什么是DataSet

DataSet是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda函数的能力)以及Spark SQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作map,flatMap,filter等等)。

是DataFrame API的一个扩展,是SparkSQL最新的数据抽象

用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性;

用样例类来定义DataSet中数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称;

DataSet是强类型的。比如可以有DataSet[Car],DataSet[Person]。

DataFrame是DataSet的特列,DataFrame=DataSet[Row] ,所以可以通过as方法将DataFrame转换为DataSet。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息都用Row来表示。

Spark对比 Redis spark和sparksql_SQL_08

2.SparkSQL编程

2.1 SparkSession新的起始点

在老的版本中,SparkSQL提供两种SQL查询起始点:一个叫SQLContext,用于Spark自己提供的SQL查询;一个叫HiveContext,用于连接Hive的查询。

SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContex和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了sparkContext,所以计算实际上是由sparkContext完成的。当我们使用 spark-shell 的时候, spark 会自动的创建一个叫做spark的SparkSession, 就像我们以前可以自动获取到一个sc来表示SparkContext。

Spark对比 Redis spark和sparksql_spark_09

2.2 DataFrame

Spark SQL的DataFrame API 允许我们使用 DataFrame 而不用必须去注册临时表或者生成SQL表达式。DataFrame API 既有transformation操作也有action操作,DataFrame的转换从本质上来说更具有关系, 而 DataSet API 提供了更加函数式的 API。

2.2.1 创建DataFrame

在Spark SQL中SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建;从一个存在的RDD进行转换;还可以从Hive Table进行查询返回。

Spark支持创建文件的数据源格式:

Spark对比 Redis spark和sparksql_spark_10

2.2.2 SQL风格语法、

1)创建一个DataFrame

var df = spark.read.json("/opt/module/spark-local/people.json")
df.show()

Spark对比 Redis spark和sparksql_API_11


2)对DataFrame创建一个临时表

df.createOrReplaceTempView("people")

3)通过SQL语句实现查询全表

scala> var sqlDF = spark.sql("SELECT * FROM people")
sqlDF: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
sqlDF.show

Spark对比 Redis spark和sparksql_API_12

2.2.3 DSL风格语法

DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据,可以在 Scala, Java, Python 和 R 中使用 DSL,使用 DSL 语法风格不必去创建临时视图了。
1)创建DataFrame

val df = spark.read.json("/opt/module/spark-local /people.json")

2)查看DataFrame的Schema信息

Spark对比 Redis spark和sparksql_SQL_13


3)只查看"name"列数据

df.select("name").show()

Spark对比 Redis spark和sparksql_Spark对比 Redis_14


4)查看所有列

df.select("*").show

Spark对比 Redis spark和sparksql_SQL_15


5)查看name类以及age+1的数据

df.select($"name",$"age" + 1).show

Spark对比 Redis spark和sparksql_API_16


6)查看age大于19的数据

df.filter($"age">19).show

Spark对比 Redis spark和sparksql_API_17


7)按照”age”分组,查看数据条数

df.groupBy("age").count.show

Spark对比 Redis spark和sparksql_API_18

2.2.4 RDD转换为DataFrame

注意:如果需要RDD与DF或者DS之间操作,那么都需要引入 import spark.implicits._ (spark不是包名,而是sparkSession对象的名称,所以必须先创建SparkSession对象再导入. implicits是一个内部object)

数据准备:

在/opt/module/spark-local/目录下准备people.txt

qiaofeng,18
xuzhu,19
duanyu,20

创建RDD

peopleRDD.map{
    str=>{
        var fields = str.split(",");
        (fields(0),fields(1).toInt)
    }
}.toDF("name","age").show

1)手动确定转换

peopleRDD.map{
    str=>{
        var fields = str.split(",");
        (fields(0),fields(1).toInt)
    }
}.toDF("name","age").show

Spark对比 Redis spark和sparksql_API_19


2)通过样例类反射转换(常用)

创建一个样例类:

case class People(name:String,age:Int)
peopleRDD.map{x=> var fields=x.split(",");People(fields(0),fields(1).toInt)}.toDF.show

输出同上

2.2.5 DataFrame转换为RDD

1)创建一个DataFrame

df = spark.read.json("/opt/module/spark-local/people.json")

2)将DataFrame转换为RDD

val dfToRDD = df.rdd

3)打印RDD

dfToRDD.collect

输出:

res2: Array[org.apache.spark.sql.Row] = Array([30,qiaofeng], [20,xuzhu], [10,duanyu])

2.3 DataSet

DataSet是具有强类型的数据集合,需要提供对应的类型信息。

2.3.1 创建DataSet

1)使用样例类序列创建DataSet

case class Person(name: String, age: Long)
val caseClassDS = Seq(Person("wangyuyan",2)).toDS()
caseClassDS.show

Spark对比 Redis spark和sparksql_Spark对比 Redis_20


2)使用基本类型的序列创建DataSet

val ds = Seq(1,2,3,4,5,6).toDS
ds.show

Spark对比 Redis spark和sparksql_API_21

2.3.2 RDD转换为DataSet

SparkSQL能够自动将包含有样例类的RDD转换成DataSet,样例类定义了table的结构,样例类属性通过反射变成了表的列名。样例类可以包含诸如Seq或者Array等复杂的结构。

1)创建一个RDD

val peopleRDD = sc.textFile("/opt/module/spark-local/people.txt")

2)创建一个样例类

case class Person(name:String,age:Int)

3)将RDD转换为DataSet

peopleRDD.map(line => {val fields = line.split(",");Person(fields(0),fields(1). toInt)}).toDS
org.apache.spark.sql.Dataset[Person] = [name: string, age: int]

2.4 Dataset与DataFrame操作

2.4.1 DataFrame转为DataSet

1)创建一个DataFrame

val df = spark.read.json("/opt/module/spark-local/people.json")

2)创建一个样例类

case class Person(name: String,age: Long)

3)将DataFrame转换为DataSet

df.as[Person]
res8: org.apache.spark.sql.Dataset[Person] = [age: bigint, name: string]

给出每一行的类型后,使用as方法,转换成Dataset,这在数据类型是DataFrame又需要针对各个字段处理时较为方便。在使用一些特殊操作时,要加上import spark.implicits._,否则toDF、toDS无法使用。

2.4.2 Dataset转换为DataFrame

1)创建一个样例类

case class Person(name: String,age: Long)

2)创建DataSet

Seq(Person("zhangwuji",32)).toDS()

3)将DataSet转换为DataFrame

var df = ds.toDF

4)展示

df.show

Spark对比 Redis spark和sparksql_SQL_22

2.5 RDD、DataFrame和DataSet之间的关系

在SparkSQL中Spark为我们提供了两个新的抽象,分别是DataFrame和DataSet。他们和RDD有什么区别呢?首先从版本的产生上来看:
RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。在后期的Spark版本中,DataSet有可能会逐步取代RDD和DataFrame成为唯一的API接口。

2.5.1 三者共性

  1. RDD、DataFrame、DataSet全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利;
  2. 三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算;
  3. 三者有许多共同的函数,如filter,排序等;
  4. 在对DataFrame和Dataset进行操作许多操作都需要这个包:import spark.implicits._(在创建好SparkSession对象后尽量直接导入)
  5. 三者都会根据 Spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
  6. 三者都有partition的概念
  7. DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型

2.5.2 三者区别

  1. RDD
    RDD一般和Spark MLib同时使用
    RDD不支SparkSQL操作
  2. DataFrame
    与RDD和Dataset不同,DataFrame每一行的类型固定为Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值
    DataFrame与DataSet一般不与 Spark MLib 同时使用
    DataFrame与DataSet均支持 SparkSQL 的操作,比如select,groupby之类,还能注册临时表/视窗,进行 sql 语句操作
    DataFrame与DataSet支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然(后面专门讲解)
  3. DataSet
    Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。 DataFrame其实就是DataSet的一个特例
    type DataFrame = Dataset[Row]
    DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段。而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息

2.5.3 三者的相互转换

Spark对比 Redis spark和sparksql_API_23

2.6 IDEA创建SparkSQL程序

1)添加依赖

<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.1.1</version>
</dependency>

2)代码实现:

/**
 * 演示RDD&DF&DS之间的关系以及转换
 */
object SparkSQL01_Demo {
  def main(args: Array[String]): Unit = {
    //创建SparkConf配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("SparkSQL01_Demo").setMaster("local[*]")

    //创建SparkSQL执行的入口点对象 SparkSession
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()

    //spark不是包名,也不是类名,是我们创建的SparkSession的对象名称
    import spark.implicits._

    //读取json文件创建DataFrame
    val dataFrame: DataFrame = spark.read.json("E:\\spark-0701\\input\\test.json")

    //查看DF里面的数据
    //dataFrame.show()

    //SQL语言风格
    dataFrame.createOrReplaceTempView("user")
    spark.sql("select * from user").show()

    //DSL风格
    dataFrame.select("username", "age").show()

    //RDD-->DataFrame-->DataSet
    //创建RDD
    val rdd1: RDD[(Int, String, Int)] = spark.sparkContext.makeRDD(List((1, "aaa", 20), (2, "bbb", 18), (3, "ccc", 30)))

    //RDD-->DataFrame
    val df: DataFrame = rdd1.toDF("id", "name", "age")
    df.show()

    //DataFrame-->DataSet
    val ds: Dataset[User] = df.as[User]


    //Data-->DataFrame-->RDD
    val df1: DataFrame = ds.toDF()

    val rdd2: RDD[Row] = df1.rdd

    //释放资源
    spark.stop()
  }

}

case class User(id: Int, name: String, age: Int) //样例类

输出:

+---+--------+
|age|username|
+---+--------+
| 20|zhangsan|
| 18|    lisi|
| 16|  wangwu|
+---+--------+

+--------+---+
|username|age|
+--------+---+
|zhangsan| 20|
|    lisi| 18|
|  wangwu| 16|
+--------+---+

+---+----+---+
| id|name|age|
+---+----+---+
|  1| aaa| 20|
|  2| bbb| 18|
|  3| ccc| 30|
+---+----+---+

2.7 用户自定义函数

2.7.1 UDF

输入一行,返回一个结果。在Shell窗口中可以通过spark.udf功能用户可以自定义函数。

代码实现:

/**
 * 自定义UDF函数,在每一个查询的名字前,加问候语
 */
object SparkSQL02_UDF {
  def main(args: Array[String]): Unit = {
    //创建SparkConf配置文件对象
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL02_UDF")

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

    //创建DF
    val df: DataFrame = spark.read.json("E:\\spark-0701\\input\\test.json")

    //注册自定义函数 在每一个name前面添加一个hello
    spark.udf.register("addSayHi", (name: String) => {
      "hello " + name
    })

    //创建临时视图
    df.createOrReplaceTempView("user")

    //通过SQL语句,从临时视图查询数据
    spark.sql("select addSayHi(username),age from user").show()

    //释放资源
    spark.stop()
  }
}

输出:

+----------------------+---+
|UDF:addSayHi(username)|age|
+----------------------+---+
|        hello zhangsan| 20|
|            hello lisi| 18|
|          hello wangwu| 16|
+----------------------+---+

2.7.2 UDAF

输入多行,返回一行。强类型的Dataset和弱类型的DataFrame都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),max(),min()。除此之外,用户可以设定自己的自定义聚合函数。通过继承UserDefinedAggregateFunction来实现用户自定义聚合函数。

需求:实现求平均年龄

1)RDD算子方式实现
代码实现:

/**
 * 求平均年龄---RDD算子方式实现
 */
object SparkSQL03_RDD {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("SparkSQL03_RDD").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("aaa", 20), ("bbb", 40), ("ccc", 25)))

    //转换结构
    val mapRDD: RDD[(Int, Int)] = rdd.map {
      case (name, age) => {
        (age, 1)
      }
    }

    //对年龄以及总人数进行聚合操作 (ageSum,countSum)
    val resRDD: (Int, Int) = mapRDD.reduce {
      (t1, t2) => {
        (t1._1 + t2._1, t1._2 + t2._2)
      }
    }

    //输出
    println(resRDD._1 / resRDD._2)

    //关闭连接
    sc.stop()
  }
}

输出:

28

2)自定义累加器方式实现

object SparkSQL04_Accumulator {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("SparkSQL04_Accumulator").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建一个RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("aaa", 20), ("bbb", 40), ("ccc", 25)))

    //创建累加器对象
    val myAcc = new MyAccumulator

    //注册累加器
    sc.register(myAcc)

    //使用累加器
    rdd.foreach {
      case (username, age) => {
        myAcc.add(age)
      }
    }

    //获取累加器的值
    print(myAcc.value)

    //关闭连接
    sc.stop()
  }
}

//定义累加器
class MyAccumulator extends AccumulatorV2[Int, Double] {
  var ageSum: Int = 0
  var countSum: Int = 0

  override def isZero: Boolean = {
    ageSum == 0 && countSum == 0
  }

  override def copy(): AccumulatorV2[Int, Double] = {
    val newMyAcc = new MyAccumulator
    newMyAcc.ageSum = this.ageSum
    newMyAcc.countSum = this.countSum
    newMyAcc
  }

  override def reset(): Unit = {
    ageSum = 0
    countSum = 0
  }

  override def add(age: Int): Unit = {
    ageSum += age
    countSum += 1
  }

  override def merge(other: AccumulatorV2[Int, Double]): Unit = {
    other match {
      case mc: MyAccumulator => {
        this.ageSum += mc.ageSum
        countSum += mc.countSum
      }
      case _ =>
    }
  }

  override def value: Double = {
    ageSum / countSum
  }
}

输出:

28.0

3)自定义聚合函数实现-弱类型(应用于SparkSQL更方便)

/**
 * 自定义UDAF(弱类型,主要应用在SQL风格的DF查询)
 */
object SparkSQL05_UDAF {
  def main(args: Array[String]): Unit = {
    //创建配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("SparkSQL05_UDAF").setMaster("local[*]")

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

    //读取json文件,创建DF
    val df: DataFrame = spark.read.json("E:\\spark-0701\\input\\test.json")

    //创建自定义函数对象
    val myAvg = new MyAvg

    //注册自定义函数
    spark.udf.register("myAvg", myAvg)

    //创建临时视图
    df.createOrReplaceTempView("user")

    //使用聚合函数进行查询
    spark.sql("select myAvg(age) from user").show()

    //释放资源
    spark.stop()
  }

}

//自定义UDAF函数(弱类型)
class MyAvg extends UserDefinedAggregateFunction {
  /**
   * 聚合函数输入的数据类型
   *
   * @return
   */
  override def inputSchema: StructType = {
    StructType(Array(StructField("age", IntegerType)))
  }

  /**
   * 缓存数据的类型
   *
   * @return
   */
  override def bufferSchema: StructType = {
    StructType(Array(StructField("sum", LongType), StructField("count", LongType)))
  }

  /**
   * 聚合函数返回的数据类型
   *
   * @return
   */
  override def dataType: DataType = DoubleType

  /**
   * 稳定性,默认不处理,直接返回true,相同输入是否会得到相同的输出
   *
   * @return
   */
  override def deterministic: Boolean = true

  /**
   * 初始化,让缓存设置为初始状态
   *
   * @param buffer
   */
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    //让缓存中年龄总和归0
    buffer(0) = 0L
    //让缓存中总人数归0
    buffer(1) = 0L
  }

  /**
   * 更新缓存数据
   *
   * @param buffer
   * @param input
   */
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    if (!buffer.isNullAt(0)) {
      buffer(0) = buffer.getLong(0) + input.getInt(0)
      buffer(1) = buffer.getLong(1) + 1L
    }
  }

  /**
   * 分区间的合并
   *
   * @param buffer1
   * @param buffer2
   */
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
    buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }

  /**
   * 计算逻辑
   *
   * @param buffer
   * @return
   */
  override def evaluate(buffer: Row): Any = {
    buffer.getLong(0).toDouble / buffer.getLong(1)
  }
}

输出:

+-----------------------+
|myavg(CAST(age AS INT))|
+-----------------------+
|                   18.0|
+-----------------------+

4)自定义聚合实现-强类型(应用于DataSet的DSL更为方便)

object SparkSQL06_UDAF {
  def main(args: Array[String]): Unit = {
    //创建配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("SparkSQL06_UDAF").setMaster("local[*]")

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

    import spark.implicits._

    //读取json文件,创建DF
    val df: DataFrame = spark.read.json("E:\\spark-0701\\input\\test.json")

    //创建自定义函数对象
    val myAvgNew = new MyAvgNew

    //将df转换为ds
    val ds: Dataset[User06] = df.as[User06]

    //将自定义函数对象转换为查询列
    val col: TypedColumn[User06, Double] = myAvgNew.toColumn

    //在进行查询的时候,会将查询出来的记录(User06类型)交给自定义的函数进行处理
    ds.select(col).show()

    //释放资源
    spark.stop()
  }
}

//输入类型的样例类
case class User06(username: String, age: Long)

case class AgeBuffer(var sum: Long, var count: Long)

//自定义UDAF函数(强类型)
/**
 * IN 输入数据类型
 * BUF 缓存数据类型
 * OUT 输出结果数据类型
 */
class MyAvgNew extends Aggregator[User06, AgeBuffer, Double] {
  /**
   * 对缓存数据进行初始化
   *
   * @return
   */
  override def zero: AgeBuffer = {
    AgeBuffer(0L, 0L)
  }

  /**
   * 对当前分区内的数据进行聚合
   *
   * @param b
   * @param a
   * @return
   */
  override def reduce(b: AgeBuffer, a: User06): AgeBuffer = {
    b.sum += a.age
    b.count += 1
    b
  }

  /**
   * 分区间合并
   *
   * @param b1
   * @param b2
   * @return
   */
  override def merge(b1: AgeBuffer, b2: AgeBuffer): AgeBuffer = {
    b1.sum += b2.sum
    b1.count += b2.count
    b1
  }

  /**
   * 返回计算结果
   *
   * @param reduction
   * @return
   */
  override def finish(buffer: AgeBuffer): Double = {
    buffer.sum.toDouble / buffer.count
  }

  /**
   * Dataset的编码以及解码器,用于进行序列化,固定写法
   * 用户自定义Ref类型 product 系统值类型 根据具体类型进行选择即可
   *
   * @return
   */
  override def bufferEncoder: Encoder[AgeBuffer] = {
    Encoders.product
  }

  override def outputEncoder: Encoder[Double] = {
    Encoders.scalaDouble
  }
}

输出:

+----------------------------------------+
|MyAvgNew(com.atguigu.spark.day08.User06)|
+----------------------------------------+
|                                    18.0|
+----------------------------------------+