Spark SQL 基础

一、为什么要用SQL

  为什么大数据需要SQL。为什么SQL已经诞生这么长时间还在使用。为什么说一个框架不能落地到SQL上就不是一个好的框架。其实这个和SQL有关。

  • SQL是用来统计数据信息的一种方式。比如统计一个班有多少男生,多少人上课迟到。大数据也需要处理这些统计信息,所以需要SQL。
  • MySQL,Oracle等都是单机文件存储,因为数据量逐渐变大,性能跟不上,所以云化成大数据,以前的业务也需要随之云化,所以需要SQL。
  • 转变一个传统RDBMS工程师成为一个大数据工程师的成本比业务SQL化高太多,同时也会增加社会不定因数,所以需要SQL。
  • 方便,不需要重新编译执行,SQL方便维护。

  等等,都是一个好的SQL框架万众期待的原因。但是Spark SQL并不只是一个SQL。

二、SQL on Hadoop

  有哪些SQL框架可以跑在Hadoop生态之上。

  • Hive:facebook开源的,可以运行MapReduce/Tez/Spark引擎之上。
  • Impala:cloudare开源的,使用CM安装会非常方便。
  • Presto:JD正在使用
  • Drill:可以混合Hbase,Hadoop的编程
  • Phonenix:使得Hbase可以用SQL查询
  • Spark SQL:Spark处理结构化数据的模块

三、Saprk SQL

  Spark SQl不仅仅是写SQL,而是Spark处理结构化数据的一个模块。所以Spark SQL中不仅写SQL,还有DF(DataFrames)API、DS(Datasets)API。
  Spark RDD的入口是SparkContext,SparkSQL的入口是SparkSession。
  Spark SQL架构:
在这里插入图片描述

四、Spark SQL操作

4.1 配置和启动

  常常听到说Spark SQL操作Hive,这里说的并不是要启动Hive然后Spark去连接Hive,操作SQL。而是说Hive在HDFS上建立的一些表等可以Spark SQL连接meta得到信息,并且操作和配置起来像Hive一样。这都要归功于通用meta。
  既然需要Hive的meta信息,所以配置的时候需要将hive的hive-site.xml拷贝到${SPARK_HOME/conf/}下。因为Hive的meta连接到的是MySQL,所以还需要配置MySQL的驱动。添加这个驱动放到${SPARK_HOME/jars}下面,或者可以在手动添加:

./spark-shell --master local[2] --jars  ~/mysql-connector-java-5.1.46-bin.jar

  有一个小小的意外,–jars 不一定能包含到classpath中,这样在启动任务的时候会各种报错,所以可以使用 --driver-class-path来修正。
  测试一下:

spark.sql("show databases").show

  使用spark-shell编程,需要加如SQL的一些API接口,如spqrk.sql等,不是很方便,也可以使用spark-sql,后面带的参数和spark-shell一样。

4.2 catch

  在业务场景中需要缓存一张表到内存怎样缓存呢。可以使用 spark.catalog.catchTable(" table name") 或者 dataframe.catch( )。如果在spark-sql中,使用catch table tableName。
  Spark Core中catch是一个lazy的一个过程,但是在Spark SQL中这是一个实时过程。

五、Spark SQL外部数据源

  什么是外部数据源。就是在不同的文件系统(HDFS/S3/jdbc等)的文件(json、parquet等)或者有带压缩需要加载到spark内做计算。Spark SQL需要知道这些格式获取、写入数据,这就是外部数据源。
  简单使用,比如读取parquet的文件然后转化成json数据:

val parquetDF = sparkContext.read.format("parquet").load("file:///home/a.parquet")
parquetDF.write.format("json").save("file:///home/")

  大体上分为两类内建的外部数据源API(如HDFS、OSS、S3等)、第三方数据源(如redis、MongoDB等,可以访问spark-packages.org查找)。
  如果需要自定义外部数据源,需要自定义类,其中关键的接口如下:

BaseRelation

  这个类作用就是装schema的,这个是基础,通过继承这个类,重写获取schema的方法。DataFrame = RDD + schema,所以schema获取这个还是比较重要的。这个类中带有过滤,实现过滤条件的功能。

TableScan

  trait类,全表扫描外部数据源的方式。

PrunedScan

  trait类,列裁剪方式扫描外部数据源。

PrunedFilteredScan

  trait类,这个是filter+pruned,行过滤和列裁剪方法扫描外部数据源方式。

InsertableRelation

  trait类,写回的功能,这个方法将DF写入外部数据源,上诉的那几个都是将该数据源读出来转化成DF。

RelationProvider

  trait类,用来创建BaseRelation,他的名字可以看出,这是个Relation(或者说schema)的提供者。

六、DF与DS

  DS(DataSet)是一个分布式数据集;DF(DataFrame)是DataSet[Row],可以理解成RDBMS中的一张表。所以对于DF来说schema变得尤为重要。
  如何创建DF。DF是Spark SQL的编程基础,所以先要创建Spark SQL入口。Spark SQL的入口点就是SparkSession,创建的方式

val spark = SparkSession.builder().appName().master().getOrCreate();
//读数据生成DF
val df = spark.read.format("json").load(path)
//将DF数据写出到文件
val df = spark.write.format("text").save(path)

6.1 DF一些简单方法

//查看Schema信息
df.printSchema()
//显示df里面数据
df.show(); //这个方法默认显示前20行的数据
//仅显示某个列的数据
df.select("name","age").show()
df.select($"name",$"age"+1).show()
df.select('name).show()
//筛选
df.filter($"age" > 21).show()
//分组
df.groupBy("age").count().show()
//排序
df.sort($"name".desc)
//join
df1.join(df2, df1.col("id") === df2.col("id"), "left")

6.2 RDD转化成DF

  因为RDD只是一个数据集没有Schema,所以要导入Schema到DF。一共两种方式。

//1、利用java的反射得到
import spark.implicits._
val df = spark.sparkContext.textFile("file:///d:/a.txt").map(x=> x.splite(","))
   .map(x => student(x(0).toInt,x(1),x(2).toInt)).toDF()

case class student(id:Int, name:String, age:Int)
//2、利用代码编程,推荐这种
val info = spark.sparkContext.textFile("file:///d:/a.txt").map(x=> x.splite(","))
val df = info.map(x=> Row(x(0).toInt,x(1),x(2).toInt))

//得到Schema
val schema = StructType(Array(
   StructField("id",IntegerType,true),
   StructField("name",StringType,true),
   StructField("id",IntegerType,true)
))
   
//注册在一起
val df = spark.createDataFrame(info,schema)

6.3 DF高级用法

//1、将DF按照age分组输出
df.write.format("json").mode("overwirte").option("comparession","none").partitionBy("age").save("/a/b/")
/*
输出按照每个年龄分组输出到不同的文件夹下。
*/

七、Function

  Spark SQL中也有像Hive一样的函数,应该说是像SQL一样的函数。因为没有Function的SQL很多功能是做不了的,比如时间截断,字符串的截断等。Hive的Function在我的其他的博文中有提到,下面来说明一下Spark SQL的Function。

7.1 内建函数

  内建函数都在 org.apache.spark.sql.functions._ 这个包下面。

val Rdd = spark.sparkContext.parallelize(a.txt)
import spark.implicits._
val Df = Rdd.map(_.split(",")).map(x => Data(x(0),x(1))).toDF

import org.apache.spark.sql.functions._
df.groupBy($"col1").agg(count("col2").as("otherName")).select("col1","otherName").show(false)

  上面这个agg的功能就是去取分组后的数据进行聚合内的运算(agg是聚合函数的英文缩写),业务上比如取分组后的平均分,最大分,就可写成:

df.groupBy($"score").agg(Map("score"->"avg","score"->"max"))

7.2 自建函数

  内置的函数只能完成一部分通用的业务逻辑,如果想要更好的完成业务功能,肯定是需要自定义的函数。

//注册一个自定义函数
spark.udf.register("functionName", UDFFunction)
spark.sql("select functionName() from xxx").show(false)

  看着简单,就是向spark中注册一个UDF的函数,然后之后的Spark SQL就可以运行此函数。有两个问题,第一:如果不用spark.sql方式去调用,用API的方式如何运行? 第二:这种方式是否是临时的,只能在某个spark任务中使用,如何使之成为永久函数,就如同内置函数一样(重新编译?注册jar包?)? 这些问题日后再解答。

八、Spark SQL调优

spark.sql.shuffle.partitions

  在Spark SQL中如果产生了shuffle,那么在Spark SQL的shuffle 的partition的数量是由spark.sql.shuffle.partitions决定,这个值默认是200,所以小数据量的shuffle的就会产生大量的无用partition,所以有实力的公司就会根据数据量自动适配这个shuffle partition数量。

九、Spark SQL愿景

  更少代码、更少数据量、优化交给底层。比如json的外部数据源读取,很好的兼容了字段多少,字段的数据类型的匹配。上层应用只需要read就可以了。

十、catalog

  Spark中的DataSet和Dataframe API支持结构化分析。结构化分析的一个重要的方面是管理元数据。这些元数据可能是一些临时元数据(比如临时表)、SQLContext上注册的UDF以及持久化的元数据(比如Hivemeta store或者HCatalog)。
  通过使用catalog的API的使用,可以访问元数据信息。

十一、DF与DS区别

  上面有说过DF=DS[Row]。DF和DS都可以使用高级API,这些功能都是基于他有schema。所以DF,DS可以说在这方面运用是一样的。但是两者是有差别的。
  DF=DS[Row],Row数据类型是一个弱类型,就是他不会在编译的时候检查column的正确性,但是DS的一个强类型(强类型就是指列类型是一个明确类型的数据),他的column必须是输入正确,否则就报错。

//df获取列,只能在运行时检查col1名字对不对
val df = spark.read.format("json").load("xxxx")
df.select("col1","col2").show(false)

//ds方式获取的两种方式
val ds = spark.read.format("json").load("xxxx").as[People]
ds.select("col1","col2").show(false)
ds.map(_.col1).show(false)

  所以说DF是DS的一个特例,就是将DS弱类型化,两者的高级的API基本相同。

发布了63 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/myt0929/article/details/83958072
今日推荐