Spark SQL的基本概念与用法

1. Spark SQL的作用

Hive,它是将Hive SQL转换成MapReduce,然后提交到集群上执行的,大大简化了编写MapReduce程序的复杂性,但MapReduce这种计算模型执行效率比较慢。

类比Hive,SparkSQL是Spark上的高级模块,SparkSQL是一个SQL解析引擎,将SQL解析成特殊的RDD(DataFrame),然后在Spark集群中运行Spark SQL,执行效率非常快!

SparkSQL是用来处理结构化数据的(先将非结构化的数据转换成结构化数据)

SparkSQL支持两种编程API:

  • SQL方式
  • DataFrame的方式(DSL)

2. RDD与DataSet(DataFrame)

Spark SQL的基本概念与用法_第1张图片

RDD与DataSet区别

  • Dateset是spark1.6以后推出的新的API,也是一个分布式数据集,于RDD相比,保存了跟多的描述信息,概念上等同于关系型数据库中的二维表,基于保存了更多的描述信息,spark在运行时可以被优化。例如我们在编写代码时先对user表和events表进行join后再过滤(筛选有用的字段或符合条件的event),会被优化成先筛选有用的字段或符合条件的event再join,这样就更快。
  • Dateset里面对应的的数据是强类型的,并且可以使用功能更加丰富的lambda表达式,弥补了函数式编程的一些缺点,使用起来更方便。
  • 调用Dataset的方法先会生成逻辑计划,然后被spark的优化器进行优化,最终生成物理计划,然后提交到集群中运行!

DatFrame

在scala中,DataFrame其实就是Dateset[Row]
DataFrame里面存放的结构化数据的描述信息,DataFrame要有表头(表的描述信息),描述了有多少列,每一列数叫什么名字、什么类型、能不能为空?

DataFrame是特殊的RDD(RDD+Schema信息就变成了DataFrame)

Dataset的特点

  1. 一系列分区
  2. 每个切片上会有对应的函数
  3. 依赖关系
  4. kv类型shuffle也会有分区器
  5. 如果读取hdfs中的数据会感知最优位置
  6. 会优化执行计划
  7. 支持更加智能的数据源

在DataSet上的操作,也分为transformationsactions。transformations会产生新的DataSet,而actions则是触发计算并产生结果。transformations包括:map,filter,select和aggregate等操作。而actions包括:count,show或把数据写入到文件系统中。

3. SQL编程

SparkSQL 1.x和2.x的编程API有一些变化

3.1 Spark SQL 1.x 方式

第一种方法(SQL API)
  1. 创建sparkContext,然后再创建SQLContext
  2. 先创建RDD,对数据进行整理,然后关联case class,将非结构化数据转换成结构化数据
  3. 显式地的调用toDF方法将RDD转换成DataFrame
  4. 注册临时表
  5. 执行SQL(Transformation,lazy)
  6. 执行Action
package spark_SQL

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Spark1.x 版本的 spark SQL
 */
object SQL1Demo1 {

  def main(args: Array[String]): Unit = {

    //提交的这个程序可以连接到Spark集群中
    val conf = new SparkConf().setAppName("SQL1Demo1").setMaster("local[2]")

    //创建SparkSQL的连接(程序执行的入口)
    val sc = new SparkContext(conf)
    //sparkContext不能创建特殊的RDD(DataFrame)
    //将SparkContext包装进而增强
    val sqlContext = new SQLContext(sc)
    //创建特殊的RDD(DataFrame),就是有schema信息的RDD

    //先有一个普通的RDD,然后在关联上schema,进而转成DataFrame

    val lines = sc.textFile("hdfs://hadoop100:9000/person")
    //将数据进行整理
    val boyRDD: RDD[Boy] = lines.map(line => {
      val fields = line.split(",")
      val id = fields(0).toLong
      val name = fields(1)
      val age = fields(2).toInt
      val fv = fields(3).toDouble
      Boy(id, name, age, fv)
    })
    //该RDD装的是Boy类型的数据,有了shcma信息,但是还是一个RDD
    //将RDD转换成DataFrame
    //导入隐式转换
    import sqlContext.implicits._
    val bdf: DataFrame = boyRDD.toDF

    //变成DF后就可以使用两种API进行编程了
    //把DataFrame先注册临时表
    bdf.registerTempTable("t_boy")

    //书写SQL(SQL方法应其实是Transformation)
    val result: DataFrame = sqlContext.sql("SELECT * FROM t_boy ORDER BY fv desc, age asc")

    //查看结果(触发Action)
    result.show()

    sc.stop()



  }
}

case class Boy(id: Long, name: String, age: Int, fv: Double)

第二种方法(SQL API)
  1. 创建sparkContext,然后再创建SQLContext
  2. 先创建RDD,对数据进行整理,然后关联Row,将非结构化数据转换成结构化数据
  3. 定义schema
  4. 调用sqlContext的createDataFrame方法
  5. 注册临时表
  6. 执行SQL(Transformation,lazy)
  7. 执行Action
package spark_SQL

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types._
import org.apache.spark.sql.{DataFrame, Row, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Spark1.x 版本的 spark SQL
 */
object SQL1Demo2 {

  def main(args: Array[String]): Unit = {

    //提交的这个程序可以连接到Spark集群中
    val conf = new SparkConf().setAppName("SQLDemo2").setMaster("local[2]")

    //创建SparkSQL的连接(程序执行的入口)
    val sc = new SparkContext(conf)
    //sparkContext不能创建特殊的RDD(DataFrame)
    //将SparkContext包装进而增强
    val sqlContext = new SQLContext(sc)
    //创建特殊的RDD(DataFrame),就是有schema信息的RDD

    //先有一个普通的RDD,然后在关联上schema,进而转成DataFrame

    val lines = sc.textFile("hdfs://node-4:9000/person")
    //将数据进行整理
    val rowRDD: RDD[Row] = lines.map(line => {
      val fields = line.split(",")
      val id = fields(0).toLong
      val name = fields(1)
      val age = fields(2).toInt
      val fv = fields(3).toDouble
      Row(id, name, age, fv)
    })

    //结果类型,其实就是表头,用于描述DataFrame
    val sch: StructType = StructType(List(
      StructField("id", LongType, true),
      StructField("name", StringType, true),
      StructField("age", IntegerType, true),
      StructField("fv", DoubleType, true)
    ))

    //将RowRDD关联schema
    val bdf: DataFrame = sqlContext.createDataFrame(rowRDD, sch)


    //变成DF后就可以使用两种API进行编程了
    //把DataFrame先注册临时表
    bdf.registerTempTable("t_boy")

    //书写SQL(SQL方法应其实是Transformation)
    val result: DataFrame = sqlContext.sql("SELECT * FROM t_boy ORDER BY fv desc, age asc")

    //查看结果(触发Action)
    result.show()

    sc.stop()



  }
}
第三种方法(DSL(DatFrame API))
package spark_SQL

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types._
import org.apache.spark.sql.{DataFrame, Row, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Spark1.x 版本的 spark SQL
 */
object SQL1Demo3 {

  def main(args: Array[String]): Unit = {

    //提交的这个程序可以连接到Spark集群中
    val conf = new SparkConf().setAppName("SQLDemo3").setMaster("local[2]")

    //创建SparkSQL的连接(程序执行的入口)
    val sc = new SparkContext(conf)
    //sparkContext不能创建特殊的RDD(DataFrame)
    //将SparkContext包装进而增强
    val sqlContext = new SQLContext(sc)
    //创建特殊的RDD(DataFrame),就是有schema信息的RDD

    //先有一个普通的RDD,然后在关联上schema,进而转成DataFrame

    val lines = sc.textFile("hdfs://node-4:9000/person")
    //将数据进行整理
    val rowRDD: RDD[Row] = lines.map(line => {
      val fields = line.split(",")
      val id = fields(0).toLong
      val name = fields(1)
      val age = fields(2).toInt
      val fv = fields(3).toDouble
      Row(id, name, age, fv)
    })

    //结果类型,其实就是表头,用于描述DataFrame
    val sch: StructType = StructType(List(
      StructField("id", LongType, true),
      StructField("name", StringType, true),
      StructField("age", IntegerType, true),
      StructField("fv", DoubleType, true)
    ))

    //将RowRDD关联schema
    val bdf: DataFrame = sqlContext.createDataFrame(rowRDD, sch)

    //不使用SQL的方式,就不用注册临时表了
    val df1: DataFrame = bdf.select("name", "age", "fv")

    import sqlContext.implicits._

    val df2: DataFrame = df1.orderBy($"fv" desc, $"age" asc)

    df2.show()
    sc.stop()
  }
}

3.2 Spark SQL 2.x 方式

创建SparkSession

package spark_SQL

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{DoubleType, IntegerType, LongType, StringType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object SQL2Demo1 {
  def main(args: Array[String]): Unit = {
    // spark2.x SQL的编程API(SparkSession) 是spark2.x SQL执行的入口
    val spark = SparkSession.builder()
      .appName("SQL2Demo1")
      .master("local[*]")
      .getOrCreate()

    // 创建RDD
    val lines: RDD[String] = spark.sparkContext.textFile("hdfs://hadoop100:9000/person")

    // 数据整理
    val rowRDD: RDD[Row] = lines.map(line => {
      val fields = line.split(",")
      val id = fields(0).toLong
      val name = fields(1)
      val age = fields(2).toInt
      val fv = fields(3).toDouble
      Row(id, name, age, fv)
    })

    // 创建表头,用于描述DataFrame
    val schema: StructType = StructType(List(
      StructField("id", LongType, true), // 名字,类型,是否可以为空
      StructField("name", StringType, true),
      StructField("age", IntegerType, true),
      StructField("fv", DoubleType, true)
    ))

    // 创建DataFrame
    val df: DataFrame = spark.createDataFrame(rowRDD, schema)

	// 以上数据整理、创建表头、创建DataFrame可以合成以下一步
	// 需要导入隐式转换
	//import spark.implicits._
//    val rowDataFrame: DataFrame = lines.map(line => {
//      val fields = line.split(",")
//      val id = fields(0).toLong
//      val name = fields(1)
//      val age = fields(2).toInt
//      val fv = fields(3).toDouble
//      (id, name, age, fv)
//    }).toDf("id","name","age","fv")

    import spark.implicits._
    val df2: Dataset[Row] = df.where($"fv" > 98).orderBy($"fv" desc, $"age" asc)

    df2.show()

    spark.stop()
  }
}

4. 数据源

支持多种数据源读写例如JDBC、CSV、JSON、parquet

package spark_SQL

import java.util.Properties

import org.apache.spark.sql.{DataFrame, SparkSession}

object DataSource {
  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().appName("DataSource")
      .master("local[*]")
      .getOrCreate()

    import spark.implicits._
    /**
     * 读
     */

    /********** 读取csv格式数据 **********/
    val csv: DataFrame = spark.read.csv("/Users/zx/Desktop/csv")
    csv.printSchema()
    val pdf: DataFrame = csv.toDF("id", "name", "age")
    pdf.show()

    /********** 读取jdbc数据 **********/
    //load这个方法会读取真正mysql的数据吗? 不会,但会连接数据库读取schema信息
    val logs: DataFrame = spark.read.format("jdbc").options(
      Map("url" -> "jdbc:mysql://localhost:3306/bigdata",
        "driver" -> "com.mysql.jdbc.Driver",
        "dbtable" -> "logs",
        "user" -> "root",
        "password" -> "123568")
    ).load()

    logs.printSchema()
    //lambda表达式
    val r = logs.filter($"age" <= 13)
    //val r = logs.where($"age" <= 13)
    val reslut: DataFrame = r.select($"id", $"name", $"age" * 10 as "age")


    /********** 读取json数据 **********/
    //指定以后读取json类型的数据(有表头)
    val jsons: DataFrame = spark.read.json("/Users/zx/Desktop/json")
    val filtered: DataFrame = jsons.where($"age" <=500)
    filtered.printSchema()


    /********** 读取parquet数据 **********/
    val parquetLine: DataFrame = spark.read.parquet("/Users/zx/Desktop/parquet")
    //val parquetLine: DataFrame = spark.read.format("parquet").load("/Users/zx/Desktop/pq")
    parquetLine.printSchema()
    //show是Action
    parquetLine.show()


    /**
     * 写
     */
    /********** 写入jdbc **********/
    val props = new Properties()
    props.put("user","root")
    props.put("password","123568")
    reslut.write.mode("ignore").jdbc("jdbc:mysql://localhost:3306/bigdata", "logs1", props)

    /********** 写成text **********/
    //DataFrame保存成text时出错(只能保存一列)
    reslut.write.text("/Users/zx/Desktop/text")

    /********** 写成json **********/
    reslut.write.json("/Users/zx/Desktop/json")

    /********** 写成csv **********/
    reslut.write.csv("/Users/zx/Desktop/csv")

    /********** 写成parquet **********/
    reslut.write.parquet("hdfs://node-4:9000/parquet")


    spark.stop()
  }
}

5. SparkSQL 的3种join

Broadcast Hash Join
Shuffle Hash Join
Sort Merge Join
参考:https://www.cnblogs.com/JP6907/p/10721436.html

总结起来就是

  1. Broadcast Hash Join 适用于一张大表和极小表;大表会有多个分区在多个executor中,此时将小表广播到各executor中,缓存到内存中(hash 表),之后进行hash join;避免了大量shuffle,但牺牲了executor的内存空间;
  2. Shuffle Hash Join 适用于一张大表和小表;两张表一开始也是有很多分区,此时对两张表分别按照join keys进行重分区,即shuffle,这样相同join keys值的记录分到对应的分区中;然后对对应分区中的数据进行join,此处先将小表分区构造为一张hash表,然后根据大表分区中记录的join keys值拿出来进行匹配;
  3. Sort Merge Join适用于两张大表;两张表一开始也是有很多分区,此时将两张表按照join keys进行了重新shuffle,保证join keys值相同的记录会被分在相应的分区;分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接;Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用即取即丢。

你可能感兴趣的:(大数据)