详谈Spark最重要的模块——Spark SQL

首先认识Spark SQL

1.Spark SQL is Apache Spark’s module for working with structured data.
2.There are several ways to interact with Spark SQL including SQL and the Dataset API.
3.hive on spark大体与SparkSQL结构类似,只是SQL引擎不同,但是计算引擎都是spark。
hive on spark:这个项目叫shark,Hive的语法解析和查询优化等模块本身针对的是MapReduce,限制了在Spark系统上的深度优化和维护,hive on mr是进程级别,而hive on spark是线程级别。所以项目gg了。

1)集成性:在Spark编程中无缝对接多种复杂的SQL
2)统一的数据访问方式:以类似的方式访问多种不同的数据源,而且可以进行相关操作
    spark.read.format("json").load(path)
    spark.read.format("text").load(path)
    spark.read.format("parquet").load(path)
    spark.read.format("json").option("...","...").load(path)
3) 兼容Hive
    allowing you to access existing Hive warehouses
    Spark SQL在Hive兼容层面仅依赖HQL Parser、Hive Metastore和Hive SerDes。也就是说,从HQL被解析成抽象语法树(AST)起,就全部由Spark SQL接管了,执行计划生成和优化都由Catalyst负责。
4)标准的数据连接:提供标准的JDBC/ODBC连接方式   Server

提供标准的JDBC连接方式:就像写Java代码远程连结Mysql一样,这里Spark也相当于远程的Server,提交SQL到Server。
Spark SQL架构:

    Frontend(前端)
        Hive AST   : SQL语句(字符串)==> 抽象语法树
        Spark Program : DF/DS API
        Streaming SQL
    Catalyst(催化剂)
        Unresolved LogicPlan
            select empno, ename from emp
        Schema Catalog
            和MetaStore
        LogicPlan
        Optimized LogicPlan
            select * from (select ... from xxx limit 10) limit 5;
            将我们的SQL作用上很多内置的Rule,使得我们拿到的逻辑执行计划是比较好的
        Physical Plan
    Backend(后端)

SQL分析过程:形成抽象语法树,语法分析,查询优化,物理计划生成。

官网名词解释

1.RDD:Spark revolves around the concept of a **resilient distributed dataset **(RDD), which is a fault-tolerant collection of elements that can be operated on in parallel. There are two ways to create RDDs: parallelizing an existing collection in your driver program, or referencing a dataset in an external storage system, such as a shared filesystem, HDFS, HBase, or any data source offering a Hadoop InputFormat.
重点:弹性分布式数据集,可并行操作的元素的容错集合
2.DataFrame:A DataFrame is a Dataset organized into named columns. It is conceptually equivalent to a table in a relational database or a data frame in R/Python, but with richer optimizations under the hood. DataFrames can be constructed from a wide array of sources such as: structured data files, tables in Hive, external databases, or existing RDDs. The DataFrame API is available in Scala, Java, Python, and R. In Scala and Java, a DataFrame is represented by a Dataset of Rows. In the Scala API, DataFrame is simply a type alias of Dataset[Row]. While, in Java API, users need to use Dataset to represent a DataFrame.
重点:DataFrame是组织成指定列的数据集,相当于关系数据库中的表,与RDD相似,DataFrame也是数据的一个不可变分布式集合
3.Dataset:A Dataset is a distributed collection of data.Dataset is a new interface added in Spark 1.6 that provides the benefits of RDDs (strong typing, ability to use powerful lambda functions) with the benefits of Spark SQL’s optimized execution engine. A Dataset can be constructed from JVM objects and then manipulated using functional transformations (map, flatMap, filter, etc.). The Dataset API is available in Scala and Java.
重点:数据的分布式集合,提供RDDs(强类型、使用强大lambda函数的能力)的优点,以及Spark SQL优化的执行引擎的优点
4.Throughout this document, we will often refer to Scala/Java Datasets of Rows as DataFrames.
重点:DataFrame = Dataset[Row],DataFrame相当于没定义类型的 Dataset

Spark shell,Spark sql,Spark submit脚本

Spark shell:

cygwin=false
case "$(uname)" in      #判断当前系统是否为CYGWIN系统。
  CYGWIN*) cygwin=true;;
esac

if [ -z "${SPARK_HOME}" ]; then      #${SPARK_HOME}的长度是否为0
  source "$(dirname "$0")"/find-spark-home    #dirname $0 :取得当前执行脚本文件的父目录
fi

function main() {
  if $cygwin; then
    stty -icanon min 1 -echo > /dev/null 2>&1
    export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Djline.terminal=unix"
    "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"
    stty icanon echo > /dev/null 2>&1
  else
    export SPARK_SUBMIT_OPTS
    "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"    
  fi   # 底层调用的是spark-submit --class org.apache.spark.repl.Main

main "$@"    # "$@"是shell命令外部传进的参数。
}

Spark sql:

exec "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver "$@"

Spark submit:

exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

Spark shell底层调用的是spark-submit --class org.apache.spark.repl.Main
Spark sql底层调用的是spark-submit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver
Spark submit底层调用的是spark-class org.apache.spark.deploy.SparkSubmit

DataFrame,DataSet相关操作

DataFrame:

val df: DataFrame = spark.read.json("D:\\study\\workspace\\spark-sql-train\\data\\zips.json")
df.printSchema()
df.show()                                    //JSON文件会显示列名,如果是txt的纯文本,列名为value

df.select("name").show()
import spark.implicits._
df.select($"name",$"age"+10).show()        //此种写法要导入隐式转换

df.filter("age>20").show()
df.filter(df.col("age")>20).show()               //两种写法
import org.apache.spark.sql.functions._
df.filter("age>0").orderBy(desc("name")).show()         //desc需要导入内置函数

df.groupBy("age").count().show()
df.head(3).foreach(println)
df.filter("pop>40000 ").withColumnRenamed("_id","new_id").show(10,false)     //换列名

df.createOrReplaceTempView("people")                               //创造表,执行SQL语句
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()

DataSet:

import spark.implicits._                                                                 //.toDS() 需要隐式转换

val ds: Dataset[Person] = Seq(Person("PK","30")).toDS()           //Seq序列直接转为DS
ds.show()
val primitiveDS: Dataset[Int] = Seq(1,2,3).toDS()                       //Seq序列直接转为DS
primitiveDS.map(x => x+1).collect().foreach(println)                  //DS进行算子操作

val peopleDF: DataFrame = spark.read.json("D:\\study\\workspace\\sparksqltrain\\data\\people.json")
val peopleDS: Dataset[Person] = peopleDF.as[Person]             //DF直接转为DS
peopleDS.show(false)

//peopleDF.select("naem")                                                        // select是在运行期报错
//peopleDF.map(x => x.name).show()                                       //无法识别列名
peopleDF.map(x => x.getAs[String]("name")).show()             //无法识别列名的解决办法
peopleDS.map(x => x.name).show()                                       //编译期报错

spark.stop()

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

interoperating with RDDS

 /**
    * 第一种方式:反射
    * 1)定义case class
    * 2)RDD map,map中每一行数据转成case class
    */
  def runInferSchema(spark: SparkSession): Unit = {
    import spark.implicits._

    val peopleRDD: RDD[String] = spark.sparkContext.textFile("D:\\study\\workspace\\sparksqltrain\\data\\people.txt")

    //TODO... RDD => DF
    val peopleDF: DataFrame = peopleRDD.map(_.split(",")) //RDD
      .map(x => People(x(0), x(1).trim.toInt)) //RDD
      .toDF()
    //peopleDF.show(false)

    peopleDF.createOrReplaceTempView("people")
    val queryDF: DataFrame = spark.sql("select name,age from people where age between 19 and 29")
    //queryDF.show()

    //queryDF.map(x => "Name:" + x(0)).show()                                                 // from index
    queryDF.map(x => "Name:" + x.getAs[String]("name")).show                      // from field
  }
  
  case class People(name:String, age:Int)
  
/**
    * 第二种方式:自定义编程
    */
def runProgrammaticSchema(spark:SparkSession): Unit = {
    import spark.implicits._
    // step1
    val peopleRDD: RDD[String] = spark.sparkContext.textFile("D:\\study\\workspace\\spark-sql-train\\data\\people.txt")
    val peopleRowRDD: RDD[Row] = peopleRDD.map(_.split(",")) // RDD
      .map(x => Row(x(0), x(1).trim.toInt))

    // step2
    val struct =
      types.StructType(
        StructField("name", StringType, true) ::
          StructField("age", IntegerType, false) ::Nil)

    // step3
    val peopleDF: DataFrame = spark.createDataFrame(peopleRowRDD, struct)

    peopleDF.show()

    peopleRowRDD
  }

区别:
在这里插入图片描述

我的理解是:
RDD的优势是函数式编程和强类型(编译就能发现,是JVM对象类型)。
DF的优势是关系型数据库一样各种优化,内存优化。
DS结合了两者的优势。

静态类型与运行时类型安全

如果你用的是Spark SQL的查询语句,要直到运行时你才会发现有语法错误(这样做代价很大),而如果你用的是DataFrame和Dataset,你在编译时就可以捕获错误(这样就节省了开发者的时间和整体代价)。也就是说,当你在DataFrame中调用了API之外的函数时,编译器就可以发现这个错。不过,如果你使用了一个不存在的字段名字,那就要到运行时才能发现错误了。

图谱的另一端是最严格的Dataset。因为Dataset API都是用lambda函数和JVM类型对象表示的,所有不匹配的类型参数都可以在编译时发现。而且在使用Dataset时,你的分析错误也会在编译时被发现,这样就节省了开发者的时间和代价。

我的理解是自己定义的类(如Person),在编译时是要符合JVM规范的,而DF中的Row类型是不使用JVM类型的,而是在自己内部的spark数据类型上运行,编译无法检测到。

所有这些最终都被解释成关于类型安全的图谱,内容就是你的Spark代码里的语法和分析错误。在图谱中,Dataset是最严格的一端,但对于开发者来说也是效率最高的。
在这里插入图片描述

RDD、DataFrame和Dataset 如何选择

1.使用RDD的情况:

  • 你希望可以对你的数据集进行最基本的转换、处理和控制;
  • 你的数据是非结构化的,比如流媒体或者字符流;
  • 你想通过函数式编程而不是特定领域内的表达来处理你的数据;
  • 你不希望像进行列式处理一样定义一个模式,通过名字或字段来处理或访问数据属性;
  • 你并不在意通过DataFrame和Dataset进行结构化和半结构化数据处理所能获得的一些优化和性能上的好处;

2.使用DataFrame或Dataset的情况:

  • 如果你需要丰富的语义、高级抽象和特定领域专用的API,那就使用DataFrame或Dataset;
  • 如果你的处理需要对半结构化数据进行高级处理,如filter、map、aggregation、average、sum、SQL查询、列式访问或使用lambda函数,那就使用DataFrame或Dataset;
    (比如说半结构化的JSON文本,可以处理成结构化的表,操作更方便)
  • 如果你想在编译时就有高度的类型安全,想要有类型的JVM对象,用上Catalyst优化,并得益于Tungsten生成的高效代码,那就使用Dataset;
  • 如果你想在不同的Spark库之间使用一致和简化的API,那就使用DataFrame或Dataset;
  • 如果你是R语言使用者,就用DataFrame;
  • 如果你是Python语言使用者,就用DataFrame,在需要更细致的控制时就退回去使用RDD;

参考文章
Spark SQL, DataFrames and Datasets Guide
RDD、DataFrame和Dataset 怎么选择才好?
Hive,Hive on Spark和SparkSQL区别
RDD和DataFrame和DataSet三者间的区别
Spark DataFrame理解和使用之概念理解-Schemas\Columns\Rows\Spark Types

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值