Spark Sql知识点总结

一、Spark SQL概述

1.1 Spark SQL是什么?

Spark SQL是Spark用来处理结构化数据的一个模块,它提供了 2 个编程抽象:DataFrameDataSet,并且作为分布式SQL查询引擎的作用。

之前学习了Hive,它是将Hive SQL转换成MapReduce然后提交到集群上执行,大大简化了编写MapReduc的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。之后有Spark SQL,它是使用Hive解析sql生成AST语法树,将其后的逻辑计划生成、优化、物理计划都自己完成,而不依赖Hive;执行计划和优化交给优化器Catalyst

Spark SQL是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快

1.2 特点

  1. 易整合
  2. 统一的数据访问方式
  3. 兼容Hive
  4. 标准的数据连接

1.3 Spark SQL相较于RDD的优势在哪?

  1. 提供了更好的外部数据源读写支持

    因为大部分外部数据源是有结构化的,需在RDD之外有一个新的解决方案,来整合这些结构化的数据源

  2. 提供了直接访问列的能力

    因为Spark Sql主要用于处理结构化数据,所以其提供的API具有一些普通数据库的能力

  3. 存储的数据不同

    RDD:半结构化和非结构化

    Spark Sql:结构化(速度比RDD块)

二、Spark Sql编程

2.1 Spark Session

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

SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContext和HiveContext上可用的API在SparkSession上同样是可以使用的。

SparkSession内部封装了SparkContext,所以计算实际上是由SparkContext 完成的。

SparkSession作为RDD的创建入口,其主要作用有如下两点:

  1. 创建RDD,主要是通过读取文件创建RDD
  2. 监控和调度任务,包含了一系列组件,例如DAGScheduler,TaskScheduler

2.2 DataFrame

是一个分布式数据容器,DataFrame更像传统数据库的二维表格,除了记录数据的结构信息,即schema;同时,与Hive类似,DataFrame也支持文件嵌套数据类型(struct,array和map等)

2.2.1 创建
  1. 通过Spark的数据源进行创建
  2. 从一个存在的RDD进行转换
  3. 从Hive Table进行查询返回
2.2.2 SQL风格语法

对DataFrame创建一个临时表df.createOrReplaceTempView("临时表名")

临时表是Session范围内的,Session退出后,表就失效了。

如果向应用范围内有效,可以使用全局表;注意使用全局表时需要全路径访问,如:global_temp.people

对DataFrame创建一个全局表df.createGlobalTempView("全局表名")

查看对DataFrame的Schema信息df.printSchema

2.2.3 RDD转换为DataFrame

注意:如果需要RDD与DF或者DS之间操作,那么都需要引入 import spark.implicits._spark不是包名,而是sparkSession对象的名称

前置条件:导入隐式转换并创建一个RDD

  1. 通过手动确定转换(.toDF
  2. 通过反射确定(需要用到样例类
  3. 通过编程的方式(创建Schema,根据数据及给定的schema创建DataFrame)
2.2.4 DataFrame转换为RDD

直接调用rdd即可: df.rdd

2.3 DataSet

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

2.3.1 创建

创建一个样例类,通过样例类创建DataSet

2.3.2 RDD转换为DataSet

SparkSQL能够自动将包含有case类的RDD转换成DataFrame,case类定义了table的结构,case类属性通过反射变成了表的列名(创建样例类)(.toDS

2.3.3 DataSet转换为RDD

调用rdd方法即可:DS.rdd

2.4 DataFrame与DataSet的相互操作

  1. DataFrame转换为DataSet

    导入隐式转换import spark.implicits._

    创建一个样例类

    转换:df.as[Person]

  2. DataSet转换为DataFrame

    这个很简单,因为只是把case class封装成Row

    导入隐式转换import spark.implicits._

    创建一个样例类

    转换: ds.toDF

2.5 RDD、DataFrame、DataSet

2.5.1 三者的共性
  1. RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利
  2. 三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action,如foreach时,三者才会开始遍历运算
  3. 三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
  4. 三者都有partition的概念
  5. 三者有许多共同的函数,如filter,排序等
  6. 在对DataFrame和Dataset进行操作许多操作都需要这个包进行支持(import spark.implicits._)
  7. DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型
2.5.2 三者的区别
优点缺点
RDD面向对象的操作方式;可以处理任何类型的数据运行速度比较慢,执行过程没有优化;API比较僵硬,对结构化数据的访问和操作没有优化
DataFrame针对结构化数据高度优化,可以通过列名访问和转换数据;增加Catalyst优化器,执行过程是优化的,避免了因为开发者的原因影响效率只能操作结构化数据;只有无类型的API,也就是只能针对列和SQL操作数据,API依然僵硬
DateSet结合了RDD和DataFrame的API,既可以操作结构化数据,也可以操作非结构化数据;既有有类型的API,也有无类型的API,灵活选择
  1. DataFrame表达的含义是一个支持函数式操作的表,而DataSet表达的是一个类似RDD的东西,DataSet可以处理任何对象
  2. DataFrame中所存放的是Row对象,而DataSet中可以存放任何类型的对象
  3. DataFrame的操作方式和DataSet是一样的,但是对于强类型操作而言,它们处理的类型不同
  4. DataFrame只能做到运行时类型检查,DataSet能做到编译和运行时都有类型检查

2.6 用户自定义UDF函数

在 Shell 窗口中可以通过 spark.udf 功能用户可以自定义函数

/**
 * 通过spark.udf功能用户可以自定义函数
 */
object Udf {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()

    // 通过Spark的数据源进行创建DataFrame
    val df = spark.read.json("D://people.json")
    // 打印数据
    df.show()

    // 用户自定义函数,并注册
    spark.udf.register("addName", (n: String) => "Name:" + n)

    // 对DataFrame创建一个临时表
    df.createOrReplaceTempView("people")

    spark.sql("select addName(name) Name, age from people").show()

  }

  // 自定义函数
  def udfFunction(name: String): String = {
    val result = "Name:" + name
    result
  }
}

2.7 用户自定义聚合函数

强类型的DataSet和弱类型的DataFrame都提供了相关的聚合函数,除此之外,用户可以设定自己的自定义聚合函数

import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, LongType, StructField, StructType}

/**
 * 弱类型用户自定义聚合函数:通过继承UserDefinedAggregateFunction来实现用户自定义聚合函数
 */
object Udaf {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()

    val df = spark.read.json("D://students.json")
    df.printSchema()

    // SparkSql自定义聚合函数,并注册函数
    spark.udf.register("myAverage", new MyAverage)

    // 对DataFrame创建一个临时表
    df.createOrReplaceTempView("students")
    df.show()

    val resulr = spark.sql("select name, myAverage(score) avg_score from students group by name").show()
  }

  class MyAverage extends UserDefinedAggregateFunction {
    /*
     * 聚合函数输入参数的数据类型
     */
    override def inputSchema: StructType = StructType(StructField("score", LongType) :: Nil)

    /*
     * 聚合缓冲区中的值得数据类型
     */
    override def bufferSchema: StructType = {
      StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
    }

    /*
     * 返回值的数据类型
     */
    override def dataType: DataType = DoubleType

    /*
     * 对于相同的输入是否一直返回相同的输出
     */
    override def deterministic: Boolean = true

    /*
     * 初始化
     */
    override def initialize(buffer: MutableAggregationBuffer): Unit = {
      // 存分数的总成绩
      buffer(0) = 0L
      // 存分数的个数
      buffer(1) = 0L
    }

    /*
     * 相同Executor间的数据合并
     */
    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
      if(!input.isNullAt(0)) {
        // 对输入的成绩不断求和
        buffer(0) = buffer.getLong(0) + input.getLong(0)
        // 不断计数
        buffer(1) = buffer.getLong(1) + 1
      }
    }

    /*
     * 不同Executor间的数据合并
     */
    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
      // 成绩合并
      buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
      // 计数合并
      buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
    }

    /*
     * 计算最终结果
     */
    override def evaluate(buffer: Row): Any = {
      // 平均成绩 = 成绩总和 / 计数
      buffer.getLong(0).toDouble / buffer.getLong(1)
    }
  }
}
import org.apache.spark.sql.{Encoder, Encoders, SparkSession}
import org.apache.spark.sql.expressions.Aggregator

/**
 * 强类型用户自定义聚合函数:通过继承Aggregator来实现强类型自定义聚合函数
 */
object Udaf {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()

    import spark.implicits._

    val ds = spark.read.json("D://students.json").as[Students]
    ds.printSchema()
    ds.show()

    // 将函数转换为‘TypedColumn’并给它起一个名字
    val myAverage1 = new MyAverage1
    val avg_score = myAverage1.toColumn.name("avg_score")
    ds.select(avg_score).show()
  }
}

// 既然是强类型,可能有样例类
// 定义输入的样例类
case class Students(name: String, score: Long)
// 定义缓存的样例类
case class Average(var sum: Long, var count: Long)

/**
 * 自定义聚合函数
 */
class MyAverage1 extends Aggregator[Students, Average, Double] {
  /*
   * 定义一个数据结构,保存成绩总分和成绩总条数,初识都为0
   */
  override def zero: Average = {
    Average(0L, 0L)
  }

  /*
   * 组合两个值以生成一个新值;为了提高性能,函数可以修改“buffer”并返回它,而不是构造一个新的对象
   */
  override def reduce(buffer: Average, students: Students): Average = {
    buffer.sum += students.score
    buffer.count += 1
    buffer
  }

  /*
   * 聚合不同Executor的结果
   */
  override def merge(buffer1: Average, buffer2: Average): Average = {
    buffer1.sum += buffer2.sum
    buffer1.count += buffer2.count
    buffer1
  }

  /*
   * 计算输出
   */
  override def finish(reduction: Average): Double = {
    reduction.sum.toDouble / reduction.count
  }

  /*
   * 设定之间值类型的编码器,要转换成case类
   * Encoders.product是进行scala元组和case类型转换的编码器
   */
  override def bufferEncoder: Encoder[Average] = Encoders.product

  /*
   * 设定最终输出值的编码器
   */
  override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}

三、SparkSQL数据源

3.1 通用加载/保存方法

SparkSql的DataFrame接口支持多种数据源的操作;一个DataFrame可以进行RDDs方式的操作,也可以被注册为临时表;把DataFrame注册为临时表之后,就可以对该DataFrame执行SQL查询

SparkSql的默认数据源为Parquet格式

Parquet是一种流行的格式存储格式,可以高效地存储具有嵌套字段地记录

可以采用SaveMode执行存储操作,SaveMode定义了对数据的处理模式,这些保存模式不使用任何锁定,不是原子操作

Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists(default)“error”(default)如果文件存在,则报错
SaveMode.Append“append”追加
SaveMode.Overwrite“overwrite”覆写
SaveMode.Ignore“ignore”数据存在,则忽略
import org.apache.spark.sql.SparkSession
import org.junit.Test

class ReadAndWrite {
  val spark = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()

  /**
   * 通用加载/保存方法
   * 数据源为Parquet文件时,SparkSql可以方便的执行所有的操作;修改配置项spark.sql.sources.default,可修改默认数据源格式
   * 当数据源格式不是Parquet格式文件时,需要手动指定数据源的格式;数据源格式需要指定全名(例如:org.apache.spark.sql.parquet)
   * 如果数据源格式为内置格式,则只需要指定简称(json,parquet,jdbc,orc,libsvm,csv,text)来指定数据的格式
   */
  @Test
  def saveTest(): Unit = {
    // 通过SparkSession提供的read.load方法用于通用加载数据
    val df = spark.read.format("json").load("D://students.json")
    // 使用write和save保存数据
    df.write.format("parquet").save("D://out/0001")
    // SaveMode定义数据的处理模式
    df.write.format("orc").mode("append").save("D://out/0001")
    df.show()

    // 还可以直接运行SQL在文件上
    val sqlDF = spark.sql("select * from parquet.`D://users.parquet`")
    sqlDF.write.format("json").save("D://out/0002")
    sqlDF.show()
  }

  /**
   * JSON文件
   * SparkSql能够自动推荐JSON数据集的结构,并将它加载为一个DataSet[Row]
   * 可以通过SparkSession.read.json()去加载一个一个JSON文件
   * 注意:这个JSON文件不是一个传统的JSON文件,每一行都要时一个JSON串
   */
  @Test
  def jsonTest(): Unit = {
    // JSON数据集是通过路径指向的;路径可以是单个文本文件,也可以是存储文本文件的目录
    val path = "D://students.json"
    val df = spark.read.json(path)

    df.printSchema()

    df.createOrReplaceTempView("students")

    spark.sql("select * from students where score between 80 and 90").show()

    // 也可以为表示的JSON数据集创建DataFrame;为每个字符串存储一个JSON对象的Dataset[String]
    import spark.implicits._
    val ds = spark.createDataset(
    """{"score":"98","name":"zhangsan"}""" :: Nil
    )
    spark.read.json(ds).show()
  }
}

3.2 JDBC

import java.util.Properties
import org.apache.spark.sql.SparkSession

/**
 * SparkSQL可以通过JDBC从关系型数据库中读取数据的方式创建DataFrame
 * 通过对DataFrame一系列的计算后,还可以将数据再协会关系型数据库中
 * 注意:需要将相关的数据库驱动放到spark的类路径下
 */
object JDBC {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()

    // 从MySQL数据库加载数据方式一
    val jdbcDF = spark.read
      .format("jdbc")
      .option("url", "jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8")
      .option("dbtable", "dynasty")
      .option("user", "root")
      .option("password", "123456")
      .load()
    jdbcDF.show()

    // 将数据写入MySQL方式一
    jdbcDF.write
      .format("jdbc")
      .option("url", "jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8")
      .option("dbtable", "dynasty")
      .option("user", "root")
      .option("password", "123456")
      .mode("ignore")
      .save()

    // 从MySQL数据库加载数据方式二
    val cp = new Properties()
    cp.put("user", "root")
    cp.put("password", "123456")
    val jdbcDF2 = spark.read.jdbc("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8", "emperor", cp)
    jdbcDF2.show()

    // 将数据写入MySQL方式二
    jdbcDF2.write.mode("ignore").jdbc("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8", "db", cp)
  }
}

3.3 Hive数据库

Apache Hive是Hadoop上的SQL引擎,Spark SQL编译时可以包含Hive支持,也可以不包含。包含Hive支持的Spark SQL可以支持Hive表访问、UDF(用户自定义函数)以及Hive查询语言(HiveQL/HQL)等。

需要强调的一点是,如果要在Spark SQL中包含 Hive 的库,并不需要事先安装Hive。一般来说,最好还是在编译Spark SQL时引入Hive支持,这样就可以使用这些特性了。

如果下载的是二进制版本的Spark,它应该已经在编译时添加了Hive支持。若要把Spark SQL连接到一个部署好的Hive上,必须把hive-site.xml复制到Spark的配置文件目录中($SPARK_HOME/conf)。即使没有部署好Hive,Spark SQL也可以运行。

需要注意的是,如果没有部署好Hive,Spark SQL会在当前的工作目录中创建出自己的Hive元数据仓库,叫作metastore_db。此外,如果你尝试使用HiveQL中的CREATE TABLE(并非CREATE EXTERNALTABLE)语句来创建表,这些表会被放在你默认的文件系统中的/user/hive/warehouse目录中(如果classpath中有配好的hdfs-site.xml,默认的文件系统就是HDFS,否则就是本地文件系统)。

Spark连接Hive三种方式

注意: 如果使用的是内部的Hive,在Spark2.0之后,spark.sql.warehouse.dir用于指定数据仓库的地址,如果需要是用HDFS作为路径,那么需要将core-site.xml和hdfs-site.xml加入到Spark conf目录,否则只会创建master节点上的warehouse目录,查询时会出现文件找不到的问题,这是需要使用HDFS,则需要将metastore删除,重启集群。

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.hive.HiveContext

/**
 * Spark SQL编译时可以包含Hive支持,也可以不包含
 * 包含Hive支持的Spark SQL可以支持Hive表访问、UDF(用户自定义函数)以及Hive查询语言(HiveQL/HQL)等
 */
object Hive {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").enableHiveSupport().getOrCreate()
    spark.sql("show databases").show()

    // 使用库
    spark.sql("use mydata")

    // 查看mydata库里的所有表
    spark.sql("show tables").show()

    // 创建第一张表
    var sql =
      """
        |create table if not exists mydata.studentInfo (
        |name string,
        |age int
        |)
        |row format delimited fields terminated by ','
        |lines terminated by '\n'
        |stored as textfile
        |""".stripMargin
    spark.sql(sql)
    // 加载数据(hdfs)
    sql = "load data inpath '/data/hive/student_infos.txt' into table mydata.studentInfo"
    spark.sql(sql)

    // 创建第二张表
    sql =
      """
        |create table if not exists mydata.studentScore (
        |name string,
        |score int
        |)
        |row format delimited fields terminated by ','
        |lines terminated by '\n'
        |stored as textfile
        |""".stripMargin
    spark.sql(sql)
    // 加载数据(hdfs)
    sql = "load data inpath '/data/hive/student_scores.txt' into table mydata.studentScore"
    spark.sql(sql)

    // 需求:关联两张表(join)
    sql =
      """
        |select in.name,in.age,sc.score
        |from mydata.studentInfo in
        |join mydata.studentScore sc
        |on in.name = sc.name
        |""".stripMargin
    spark.sql(sql).show()

    spark.stop()


//    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]").set("spark.executor.memory", "512m")
//    val sc = new SparkContext(conf)
//    val hc = new HiveContext(sc)
//    val sql = hc.sql("select * from mydata.psn_1").collect()
//    println(sql.toBuffer)
//    sc.stop()

  }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hadoop和Spark大数据处理领域中最流行的两个框架。以下是它们的知识点整理汇总: Hadoop: 1. Hadoop是一个开源的分布式计算框架,用于存储和处理大规模数据集。 2. Hadoop包括两个核心组件:HDFS(Hadoop分布式文件系统)和MapReduce(分布式计算框架)。 3. HDFS是一个分布式文件系统,用于存储大规模数据集。它将数据分成块并存储在不同的节点上,以实现数据的高可靠性和可扩展性。 4. MapReduce是一种分布式计算框架,用于处理大规模数据集。它将数据分成小块并在不同的节点上并行处理,以实现高效的数据处理。 5. Hadoop还包括其他组件,如YARN(资源管理器)和HBase(分布式NoSQL数据库)。 Spark: 1. Spark是一个快速、通用、可扩展的分布式计算框架,用于处理大规模数据集。 2. Spark的核心组件是Spark Core,它提供了分布式任务调度、内存计算和数据处理功能。 3. Spark还包括其他组件,如Spark SQL(用于结构化数据处理)、Spark Streaming(用于实时数据处理)和MLlib(用于机器学习)。 4. Spark使用RDD(弹性分布式数据集)作为其基本数据结构,它是一个可分区、可并行计算和可恢复的数据集合。 5. Spark支持多种编程语言,如Scala、Java、Python和R。 总结: Hadoop和Spark都是用于处理大规模数据集的分布式计算框架,它们有不同的核心组件和特点。Hadoop主要用于存储和处理大规模数据集,而Spark则更加注重数据处理的速度和效率。在实际应用中,可以根据具体需求选择合适的框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值