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)
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的特点
- 一系列分区
- 每个切片上会有对应的函数
- 依赖关系
- kv类型shuffle也会有分区器
- 如果读取hdfs中的数据会感知最优位置
- 会优化执行计划
- 支持更加智能的数据源
在DataSet上的操作,也分为transformations和actions。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)
- 创建sparkContext,然后再创建SQLContext
- 先创建RDD,对数据进行整理,然后关联case class,将非结构化数据转换成结构化数据
- 显式地的调用toDF方法将RDD转换成DataFrame
- 注册临时表
- 执行SQL(Transformation,lazy)
- 执行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)
- 创建sparkContext,然后再创建SQLContext
- 先创建RDD,对数据进行整理,然后关联Row,将非结构化数据转换成结构化数据
- 定义schema
- 调用sqlContext的createDataFrame方法
- 注册临时表
- 执行SQL(Transformation,lazy)
- 执行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
总结起来就是
- Broadcast Hash Join 适用于一张大表和极小表;大表会有多个分区在多个executor中,此时将小表广播到各executor中,缓存到内存中(hash 表),之后进行hash join;避免了大量shuffle,但牺牲了executor的内存空间;
- Shuffle Hash Join 适用于一张大表和小表;两张表一开始也是有很多分区,此时对两张表分别按照join keys进行重分区,即shuffle,这样相同join keys值的记录分到对应的分区中;然后对对应分区中的数据进行join,此处先将小表分区构造为一张hash表,然后根据大表分区中记录的join keys值拿出来进行匹配;
- Sort Merge Join适用于两张大表;两张表一开始也是有很多分区,此时将两张表按照join keys进行了重新shuffle,保证join keys值相同的记录会被分在相应的分区;分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接;Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用即取即丢。