spark-2-spark SQL

简介:

SparkSQL是Spark中处理结构化数据的一个模块。SparkSQL是sql解析引擎
优点:

  • 易整合:使用SQL/DataFrame API,支持多种语言
  • 统一的数据访问形式,不管什么数据库,都用同样的方式访问
  • 兼容Hive:Hive on spark 将Hive SQL解析成Spark 任务,运行在Spark集群
  • 标准的数据连接:使用BI工具连接数据库

Spark SQL API
Spark SQL的操作有两套api
Spark Core中:操作的是RDD
SparkSQL中:dataFrame DataSet是基于RDD更层次的抽象数据集
dataFrame :spark1.3引入的Spark SQL Api
DataSet:spark1.6引入的Spark SQL Api
spark2.0,统一了这两个api。即:DataFrame = DataSet[Row]

DataFrame编程

DataFrame = RDD + Schema
与RDD相似,DataFrame也是一个不可变的分布式数据集合,但与RDD不同的是,数据都被组织到有名字的列中,就像关系型数据库中的表一样,除了数据之外,还记录着数据的结构信息,即schema。设计dataFrame的目的就是要让对大型数据集的操作变得更简单,它让开发者制定一个模式,进行更高层次的抽象。还提供了特定领域内专用的API来处理分布式数据。
在这里插入图片描述

spark-shell中操作:
scala> val rdd1 = sc.textFile("hdfs://hadoop000/person.txt")
scala> rdd1.collect.foreach(println)
zs 29 20000
fbb 18 6000
fcc 25 2000
ym 32 3000
scala> case class Person(name:String,age:Int,sal:Double)
scala> val rdd2 = rdd1.map(_.split(" "))
scala> val rdd3 = rdd2.map(t=>Person(t(0),t(1).toInt,t(2).toDouble))
scala> val pdf = rdd3.toDF()
scala> pdf.printSchema
root
 |-- name: string (nullable = true)
 |-- age: integer (nullable = false)
 |-- sal: double (nullable = false)

scala> pdf.select("name").show
+----+
|name|
+----+
|  zs|
| fbb|
| fcc|
|  ym|
+----+
scala> pdf.filter("age>20").show
+----+---+-------+
|name|age|    sal|
+----+---+-------+
|  zs| 29|20000.0|
| fcc| 25| 2000.0|
|  ym| 32| 3000.0|
+----+---+-------+
MAVEN

引入POM

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_2.11</artifactId>
    <version>${spark.version}</version>
</dependency>

在SparkCore中创建了sparkContext。在SparkSQL中就得创建SQLContext

获取dataFrame的方式:(旧API)

方式1

通过RDD[类] + case class 利用反射获取schema的信息,toDF生成DataFrame

/**
  * 获取dataFrame的方式:
  * 通过RDD[类] + case class 利用反射获取schema的信息
  */
object DataFrameDemo1 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    //创建SparkSQL实例
    val sqlContext = new SQLContext(sc)
    //导入隐式转换,不导入无法toDF()
    import sqlContext.implicits._
    val rdd1: RDD[String] = sc.textFile("person.txt")
    val pRDD: RDD[Person] = rdd1.map(t => {
      val split: Array[String] = t.split(" ")
      //利用Person 样例类封装数据
      Person(split(0), split(1).toInt, split(2).toDouble)
    })
    //缺少隐式转换爆红
    val pdf: DataFrame = pRDD.toDF()

    pdf.printSchema()
    //println(pdf.schema)

    //基于DataFrame执行操作:SQL / DSL
    //SQL   语法风格:若要使用就需要将DataFrame注册为一张临时表,再调用SQLContext的SQL方法,执行SQL
    pdf.registerTempTable("t_person")
    val sql: DataFrame = sqlContext.sql("select * from t_person order by sal desc")
    //默认展示20行
    sql.show()

    //DSL语法风格操作
    //DSL:领域特定语言    sql中的函数使用方法进行调用
    pdf.select("name","age").show()
    pdf.where("age > 30").show()


    //pdf.select("name").show()
    //pdf.filter("age>20").show()

    sc.stop()
  }
}
case class Person(name:String,age:Int,sal:Double)
方式2

通过自定义schema + RDD[Row]

/**
  * 通过自定义schema + RDD[Row] 实现DataFrame
  */
object DataFrameDemo2 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    val rdd1: RDD[String] = sc.textFile("person.txt")
    val rowRDD: RDD[Row] = rdd1.map(t => {
      val strs: Array[String] = t.split(" ")
      Row(strs(0), strs(1).toInt, strs(2).toDouble)
    })

    val structType = StructType(List(
      StructField("name", StringType, true),
      StructField("age", IntegerType, false),
      StructField("sal", DoubleType, false)
    ))
    
    val pdf: DataFrame = sqlContext.createDataFrame(rowRDD,structType)
    pdf.printSchema()
    pdf.select("name").show()
  }
}
dataFrame两种风格的wordcount
object DataFrameWordCount {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    val file: RDD[String] = sc.textFile("wc.txt")

    //反射方式获取schema
    val wordsRDD: RDD[Word] = file.flatMap(_.split(" ")).map((Word(_)))
    val wdf: DataFrame = wordsRDD.toDF()

    //SQL风格
    wdf.registerTempTable("t_words")
    val sql: DataFrame = sqlContext.sql("select word,count(*) as cnt from t_words group by word order by cnt desc")
    sql.show()

    //通过自定义schema + RDD[Row] 实现DataFrame
    val rowRDD: RDD[Row] = file.flatMap(_.split(" ")).map(Row(_))
    val structType = StructType(List(StructField("word",StringType,true)))
    val dslFrame: DataFrame = sqlContext.createDataFrame(rowRDD,structType)
    
    //DSL风格
    dslFrame.select("word").groupBy("word").count().orderBy("count").show()
    
    sc.stop()
  }
}
case class Word(word:String)
dataFrame编程总结:

在这里插入图片描述

DataSet编程

DataSet也是一个分布式数据集合,并自带schema信息

高级API的两种风格

SparkSession 中已创建好了sparkContext和sqlContext

object DataSetDemo {
  def main(args: Array[String]): Unit = {
    //spark 2.x统一了访问api
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    //一定导入隐式转换
    import spark.implicits._
    val file: Dataset[String] = spark.read.textFile("person.txt")
    //dataSet自带schema
    file.printSchema()
    //root
    //|-- value: string (nullable = true)

    //数据处理:Dataset 元组的schema信息是根据 元组类型来确定的
    val splitDS: Dataset[(String, Int, Double)] = file.map(str => {
      val strs: Array[String] = str.split(" ")
      (strs(0), strs(1).toInt, strs(2).toDouble)
    })
    splitDS.printSchema()
    //看到,元组类型的取值只能用 _1._2._3    因此还要自定义schema
    //root
    //|-- _1: string (nullable = true)
    //|-- _2: integer (nullable = false)
    //|-- _3: double (nullable = false)

    //自定义schema
    val pdf: DataFrame = splitDS.toDF("name","age","sal")

    //SQL风格
    pdf.createTempView("v_person")
    val sql: DataFrame = spark.sql("select * from v_person where age > 30 limit 1")
    sql.show()

    //DSL风格:
    //select 选择
    pdf.select("name","age").show()
    //过滤  filter  where   where 调用了filter,是同一个api
    pdf.filter("age > 30").show()
    //order by     sort 是同一个api    默认升序
    pdf.orderBy($"age"desc).show()
    //分组groupBy
    val cnt: DataFrame = pdf.groupBy("age").count()
    cnt.show()
    val maxdf: DataFrame = pdf.groupBy("age").max("sal")
    maxdf.show()
    //agg聚合需要导入函数包
    import org.apache.spark.sql.functions._
    pdf.groupBy("age").agg(count("*") as "cnts").show()
    pdf.groupBy("age").agg(sum("sal") as "sumSal").show()
    maxdf.withColumnRenamed("max(sal)","maxSal").sort($"age"desc).limit(4).show()

    spark.close()
  }
}
dataSet,dataFrame转RDD

dataSet

val splitDS: Dataset[(String, Int, Double)] = file.map(str => {
  val strs: Array[String] = str.split(" ")
  (strs(0), strs(1).toInt, strs(2).toDouble)
})
val rdd: RDD[(String, Int, Double)] = splitDS.rdd

dataFrame需转换

val pdf: DataFrame = splitDS.toDF("name","age","sal")
val rdd: RDD[Row] = pdf.rdd
val rdd3: RDD[(String, Int, Double)] = rdd.map(row => {
  //row.get(0)
  val name: String = row.getAs[String](0)
  val age: Int = row.getAs[Int](1)
  val sal: Double = row.getDouble(2)
  (name, age, sal)
})
rdd3.foreach(println)
多表关联查询
object JoinDemo {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().master("local[*]").appName(this.getClass.getSimpleName).getOrCreate()
    import spark.implicits._
    //用户信息表
    val ds1: Dataset[String] = spark.createDataset((List("ty 28 hunan","ruhua 40 xg","mr 30 shanxi","yixx 32 beijing","cangls 18 japan")))
    //数据预处理
    val processDS1: DataFrame = ds1.map(str => {
      val split: Array[String] = str.split(" ")
      (split(0), split(1).toInt, split(2))
    }).toDF("name", "age","proCode")

    val province: Dataset[String] = spark.createDataset((List("hunan,湖南省","xg,香港","shanxi,陕西省","beijing,北京","japan,日本省")))
    val processDS2: DataFrame = province.map(str => {
      val split: Array[String] = str.split(",")
      (split(0), split(1))
    }).toDF("proCode", "pName")

    //SQL:
    processDS1.createTempView("user")
    processDS2.createTempView("address")
    val sql1: DataFrame = spark.sql("select user.name as name,user.age,address.pName from user,address where user.proCode = address.proCode")
    val sql2: DataFrame = spark.sql("select u.age as age,u.name as name,a.pName as pName from user u join address a on a.proCode = u.proCode")
    sql1.show()
    sql2.show()

    //DSL
    val sql3: DataFrame = processDS1.join(processDS2,"proCode")
    //默认inner
    val sql4: Dataset[Row] = processDS1.join(processDS2,"left").where(processDS1("proCode") === processDS2("proCode")).orderBy(processDS1("age"))
    sql3.show()
    sql4.show()

    spark.stop()
  }
}
sparkSQL中自定义函数

UDF:user defined functions 使用频繁,输入输出一对一
UDAF:user defined aggregate functions 输入输出多对一
目的是不再做JOIN,单表查询

object UDFDemo {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    import spark.implicits._

    val dataDF: DataFrame = spark.createDataset(List((10, 20, 30), (9, 16, 42), (12, 23, 35))).toDF("age1", "age2", "age3")
    dataDF.show()

    //注册udf函数
    spark.udf.register("operation",(x:Int)=>{
      x.toInt+100
    })
    //创建临时表
    dataDF.createTempView("xx")
    //使用函数查询
    val sql: DataFrame = spark.sql("select age2, operation(age1) as agg from xx")
    sql.show()
    
	//加载默认配置文件的顺序 application.conf--->application.json--->application.properties
	val config: Config = ConfigFactory.load()

	val url = config.getString("db.url")
	val conn = new Properties()
	conn.setProperty("user","root")
	conn.setProperty("password","123456")
	//在集群运行必须加Driver
	conn.setProperty("driver","com.mysql.jdbc.Driver")
	sql.write.mode(SaveMode.Append).jdbc(url,"tableName",conn)

    spark.close()
  }
}
保存数据库saveMode

在这里插入图片描述

sparkSQL任务提交到集群运行

从配置文件中加载各种配置
通过ConfigFactory加载配置文件
导入ConfigFactory所在的jar

<dependency>
    <groupId>com.typesafe</groupId>
    <artifactId>config</artifactId>
    <version>1.2.0</version>
</dependency>

在resource文件夹下new application.conf

//加载默认配置文件的顺序 application.conf--->application.json--->application.properties
val config: Config = ConfigFactory.load()
println(config.getString("db.url"))

config实例能被序列化传输的原因在于,具体的实现类SimpleConfig实现了序列化接口

sparkSQL数据源

指SparkSQL读入数据的格式:
普通文本,jdbc,json,csv,parquet
DataSourceFile

object DataSourceFile {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    import spark.implicits._

    //同一个类型的文件一起读
    val text: Dataset[String] = spark.read.textFile("person.txt","wc.txt")
    val text1: DataFrame = spark.read.text("wc.txt")

    val file: Dataset[String] = spark.read.textFile("person.txt")
    val df2: Dataset[(String, String, String)] = file.map(str => {
      val split: Array[String] = str.split(" ")
      (split(0), split(1), split(2))
    })

    //text API只支持单列,String类型    //报错
    //df2.write.text("output1")
    //save API 数据格式是parquet
    file.write.save("output2")
    //将dataSet  转为  RDD  再写入文件
    df2.rdd.saveAsTextFile("output3")

    spark.stop()
  }
}

DataSourceJDBC

object DataSourceJDBC {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    import spark.implicits._

    val config: Config = ConfigFactory.load()

    val conn = new Properties()
    conn.setProperty("user",config.getString("db.user"))
    conn.setProperty("password",config.getString("db.password"))
    conn.setProperty("driver",config.getString("db.driver"))

    //读取jdbc的API
    val jdbc: DataFrame = spark.read.jdbc(config.getString("db.url"),"bank_a",conn)
    spark.read.format("jdbc").jdbc(config.getString("db.url"),"bank_a",conn)
    jdbc.printSchema()

    val selectDF: DataFrame = jdbc.where("money < 200").select("user")
    selectDF.printSchema()

    selectDF.write.mode(SaveMode.Append).jdbc(config.getString("db.url"),"emp2",conn)
    
    spark.stop()
  }
}

JSON数据源

object JsonDataSource {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    import spark.implicits._

    //数据格式:{"pname":"meinv", "price":200, "amount":10}
    val jsonDF: DataFrame = spark.read.json("log.json")
    jsonDF.printSchema()
    jsonDF.show()

    //数据格式:{"pname":"meinv", "price":200, "amount":10,"province":{"city":"bj"}}
    val json2: DataFrame = spark.read.json("log2.json")
    json2.printSchema()
    json2.show()
    ///DSL获得city
    json2.select("province.city").show()
    //SQL方式
    json2.createTempView("json")
    spark.sql("select province.city from json").show()
    //root
    //|-- amount: long (nullable = true)
    //|-- pname: string (nullable = true)
    //|-- price: double (nullable = true)

    //json格式的数据类型是按照字典顺序写的,注意顺序
    jsonDF.toDF("amount2","pname2","price2").show()
    jsonDF.withColumnRenamed("pname","names").show()

    val whereDS: Dataset[Row] = jsonDF.where("price > 100")
    whereDS.write.json("jsonout")

    spark.stop()
  }
}
CSV格式的数据

csv是python机器学习中非常常见的数据格式,默认,分割 没有schema ,可用execel打开

object CSVDataSource {
  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]").getOrCreate()
    import spark.implicits._

    val csv: DataFrame = spark.read.csv("log.csv")
    csv.printSchema()
    //root
    //|-- _c0: string (nullable = true)
    //|-- _c1: string (nullable = true)
    //|-- _c2: string (nullable = true)
    csv.show()

    val csvDS: DataFrame = csv.map(str => {
      val name: String = str.getString(0)
      val age: Int = str.getString(1).toInt
      val fv: Double = str.getString(2).toDouble
      (name, age, fv)
    }).toDF("name", "age", "fv")

    val filter: Dataset[Row] = csvDS.where("age>30")
    filter.show()
    filter.write.csv("csvout")

    spark.stop()
  }
}
Parquet

Parquet:列式存储,使用时只需要读取需要的列,支持向量运算,能获得更好的扫描行。
压缩编码可降低磁盘存储空间,由于同一列的数据类型是相同的,可以使用不同的压缩编码
可以跳过不符合条件的数据,只读取需要的数据,降低io
通用的,适配多种计算框架,查询引擎(hive,impala,pig等)计算框架(MapReduce,spark等)数据模型(avro,thrift,json等)
压缩比:12:1
缺点,不支持update操作

//读取
val parquet = spark.read.parquet("xxx").show()
//写文件:
parquet.rdd.coalesce(1).saveAsTextFile("xxx")

println(parquet.count())
HiveOnSpark

将hive任务提交到spark
HiveOnSpark跟hive没有太大关系,就是使用了hive的标准(HQL,元数据库,UDF,序列化,反序列化机制)
hive原有的计算模型是MR,速度慢(将中间结果写进HDFS)
HiveOnSpark使用RDD(DataFrame),然后运行在spark集群
mysql存储hive的元数据(保存的是hive表的描述信息,描述了有哪些database,table,以及表有多少列,每一列是什么类型,描述表的数据存在hdfs的什么位置),真正数据存储在HDFS

Hive和MySQL的区别
Hive是一个数据仓库(存储并分析数据,数据量大,一般都是长时间分析)
MySQL是关系型数据库(CRUD低延迟)
hive的元数据库的功能:建立了一种映射关系,执行HQL时,先到mysql元数据库查找描述信息,然后根据描述信息生成任务,然后将任务下发到spark集群执行

HiveOnSpark 使用的仅仅是hive的标准和规范,不需要有hive数据库一样可行

Spark-SQL的交互式命令操作

进行sql的基本操作,创建表,插入的语句。默认的元数据在deby数据库。
真正的元数据存储在本地磁盘中,就在执行spark-sql所在路径下的spark-warehouse中

将deby -> mysql 本地磁盘数据 ->hdfs
利用hive的机制,将hive-site.xml文件放在spark安装目录conf下
放在一台机器(客户端下)(hadoop002)
在这里插入图片描述
解决方式:

spark-sql --jars /mytemp/mysql-connector-java-8.0.13.jar

在mysql中生成了hive数据库,但仍不能将数据写入hdfs,需修改DBS表中的配置:hdfs://hadoop000:8020/user/root/spark-warehouse/

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_2.11</artifactId>
    <version>2.2.0</version>
</dependency>

val spark: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName).master("local[*]")
  .enableHiveSupport().getOrCreate()

将hive-site.xml放在resource文件夹下

总结

DataFrame和DataSet都可以通过 .rdd 转化为RDD
RDD的优缺点:
编译时类型安全:编译时就能检查出错误。
面向对象的编程风格:直接通过类名打点操作数据
缺点:
序列化和反序列化的性能开销,无论是集群中的操作还是IO,都需要对对象的结构和数据进行序列化和反序列化。
GC的性能开销:频繁的创建和销毁对象,势必增加GC

DataFrame优缺点:
DataFrame引入了schema和off-heap
schema:RDD每行的数据,结构都是一样的,这个结构就存储在schema中,Spark中通过schema就能读懂数据,因此在通信和IO时,就只需要序列化和反序列化数据,而结构的部分就能省略。
of-heap:意味着JVM的堆外内存,这些内存直接受操作系统管理(而非JVM)。Spark能够以二进制的形式序列化数据(不包括结构)到off-heap中,当要操作数据时,就直接操作off-heap内存,由于Spark理解schema,所以知道该如何操作。
优点:
通过schema和off-heap,DataFrame解决了RDD的缺点,DataFrame不受JVM的限制,没有GC困扰
缺点:
DataFrame不是类型安全的,API也不是面向对象风格的

DataSet的优缺点:
优点:DataSet结合了RDD和DataFrame的优点(类型安全,面向对象,不受JVM限制,无GC开销)

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

如果你用的是Spark SQL的查询语句,要直到运行时才会发现有语法错误(代价大),而如果你使用了DataSet或DataFrame(dsl),在编译时就可以捕获错误(节省开发者时间)。即当你在DataFrame中调用了API之外的函数时,编译器就可以发现这个错误。不过如果你使用了一个不存在的字段名,那就要到运行时才可发现错误。因为DataSetAPI都是用lambda函数和JVM类型对象表示的,所有不匹配的类型参数都可以在编译时发现。而且在使用DataSet时,你的分析错误也会在编译时会发现,这样就节省了开发者的时间和代价。

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

方便易用的结构化API

虽然结构化可能会限制Spark程序对数据的控制,但它却提供了丰富的语义,和方便易用的特定领域内的操作,后者可以被表示为高级结构。事实上,用DataSet的高级API可以完成大多数的计算。比如,比用RDD数据行的数据字段进行agg,select,sum,avg,map,filter或groupBy等操作更简单。

性能与优化

在这里插入图片描述
在Scala和java中,一个DataFrame所代表的是一个多个Row(行)的DataSet(数据集合)。在ScalaAPI中,DataFrame仅仅是一个DataSet[Row]类型的别名。然而,在JavaAPI中,用户需要使用DataSet<Row>去代表一个DataFrame
在这里插入图片描述

SparkSQL梳理:

两套API:DataSet DataFrame基本操作
两套语法风格:SQL 和 DSL
关联查询:join 及新的解决方案:UDF函数
SparkSQL的数据源:csv, parquet (可能用的最多),jdbc , json读取都是通用,混用的.
Hive On Spark

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值