目录
三、RDD、DataFrame、DataSet之间的互相转换
一、DataFrame是什么
DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame与RDD的主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。
左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得 Spark SQL 可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。
DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待
二、DataSet是什么
DataSet是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda函数的能力)以及Spark SQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作map,flatMap,filter等等)
三、DataFrame
package com.test
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
object Test6 {
def main(args: Array[String]): Unit = {
//准备环境
val conf = new SparkConf().setMaster("local[*]").setAppName("Spark Sql")
val sparksql = SparkSession.builder().config(conf).getOrCreate()
//执行逻辑操作
// DataFrame
val df = sparksql.read.json("Data/user.json")
//df.show()
//DataFrame => Sql
// df.createOrReplaceTempView("user")
//
// sparksql.sql("select * from user").show()
// sparksql.sql("select name,age from user").show()
// sparksql.sql("select avg(age) from user" ).show()
//DataFrame => DSL
//在使用 DataFrame 时如果涉及到转换操作,需要引入转换规则如下
import sparksql.implicits._
df.select("name","age").show
df.select($"age"+1).show
//结束
sparksql.stop()
}
}
三、RDD、DataFrame、DataSet之间的互相转换
package com.test
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
object Test7 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Spark Sql")
val sparksql = SparkSession.builder().config(conf).getOrCreate()
import sparksql.implicits._
//DataSet
//DataFrame 是特定泛型的 DataSet
// val seq = Seq(1, 2, 3, 4)
// val ds = seq.toDS()
// ds.show()
//RDD <=> DataFrame
val rdd = sparksql.sparkContext.makeRDD(List(
(1, "zhangsan", 30),
(2, "lisi", 39),
(3, "wangwu", 22)
))
val df = rdd.toDF("id", "name", "age")
val rowrdd = df.rdd
//DataFrame <=> DataSet
val ds = df.as[User]
val df1 = ds.toDF()
//RDD <=> DataSet
val ds1 = rdd.map {
case (id, name, age) => {
User(id, name, age)
}
}.toDS()
val userrdd = ds1.rdd
sparksql.stop()
}
case class User(id:Int,name:String,age:Int)
}
四、spark读取
1、对于Hive
package com.test
import org.apache.spark
import org.apache.spark.{SparkConf, sql}
import org.apache.spark.sql.SparkSession
object Test8 {
def main(args: Array[String]): Unit = {
System.setProperty("HADOOP_USER_NAME","root")
val spark = new SparkSession.Builder()
.appName("my spark")
.master("local")
.enableHiveSupport()
.getOrCreate()
spark.sparkContext.hadoopConfiguration.set("dfs.client.use.datanode.hostname","true")
//spark.sql("show databases").show()
//spark.sql("show tables").show()
spark.sql("select * from spark.test1")
.write
.format("hive")
.mode("overwrite")
.saveAsTable("spark.test2")
spark.stop()
}
}
2、对文件操作
package com.test
import org.apache.spark
import org.apache.spark.{SparkConf, sql}
import org.apache.spark.sql.SparkSession
object Test8 {
def main(args: Array[String]): Unit = {
val spark = new SparkSession.Builder()
.appName("my spark")
.master("local")
.enableHiveSupport()
.getOrCreate()
//readText(spark)
// readCsv(spark)
// readJson(spark)
// readHDFS(spark)
savaData(spark)
spark.stop()
}
def readText(spark:SparkSession): Unit ={
//两种写法都可以
// spark.read.text("Data/Wcdata").show()
spark.read.format("text").load("Data/Wcdata.txt").show()
}
def readCsv(spark:SparkSession): Unit ={
//分隔符只能是一个,若想多个,需要自己写
spark.read.format("csv").option("header","true").option("seq",",")
.load("Data/Wcdata.txt").show()
}
def readJson(spark:SparkSession): Unit ={
val df = spark.read.format("json")
.load("Data/user.json")
df.select("name","age").show()
}
def readHDFS(spark:SparkSession): Unit ={
spark.sparkContext.hadoopConfiguration.set("dfs.client.use.datanode.hostname","true")
val df = spark.read.format("text")
.load("hdfs://10.0.12.8:9000/spark-test/spark-test.txt").show()
}
def savaData(spark:SparkSession): Unit ={
//分隔符只能是一个,若想多个,需要自己写
val df = spark.read.format("csv").option("header", "true").option("seq", ",")
.load("Data/Wcdata.txt")
df.createOrReplaceTempView("tmp")
spark.sql("select * from tmp ").write.format("csv")
.option("header","true")
.option("seq","$")
.mode("overwrite")
.save("outdata/")
}
}
3、对数据库操作
package com.test
import java.util.Properties
import org.apache.spark
import org.apache.spark.sql
import org.apache.spark.sql.SparkSession
object Test9 {
def main(args: Array[String]): Unit = {
val spark = new SparkSession.Builder()
.appName("my spark")
.master("local[*]")
.getOrCreate()
// 读表
val df = spark.read.format("jdbc")
.option("url", "jdbc:mysql://10.0.12.8:3306?useSSL=true")
.option("user", "root")
.option("password", "123456")
.option("dbtable", "spark.test1")
.load()
df.show()
//写表
df.write.format("jdbc")
.option("url", "jdbc:mysql://10.0.12.8:3306?useSSL=true")
.option("user", "root")
.option("password", "123456")
.option("dbtable", "spark.test2")
.mode("overwrite")
.save()
//读另一种方式
val properties = new Properties()
properties.put("user","root")
properties.put("password", "123456")
spark.read.jdbc("jdbc:mysql://10.0.12.8:3306?useSSL=true","spark.test1",properties)
.show()
spark.stop()
}
}
五、hint与AQE
六、spark sql是如何选择join策略的?
1、SparkSQL支持三种Join算法
shuffle hash join、broadcast hash join以及sort merge join。其中前两者归根到底都属于hash join,只不过在hash join之前需要先shuffle还是先broadcast。
2、Hash Join
SQL语句:select * from order,item where item.id = order.i_id,很简单一个Join节点,参与join的两张表是item和order,join key分别是item.id以及order.i_id。现在假设这个Join采用的是hash join算法,整个过程会经历三步:
(1)确定Build Table以及Probe Table:这个概念比较重要,Build Table使用join key构建Hash Table,而Probe Table使用join key进行探测,探测成功就可以join在一起。通常情况下,小表会作为Build Table,大表作为Probe Table。此事例中item为Build Table,order为Probe Table;
(2)构建Hash Table:依次读取Build Table(item)的数据,对于每一行数据根据join key(item.id)进行hash,hash到对应的Bucket,生成hash table中的一条记录。数据缓存在内存中,如果内存放不下需要dump到外存;
(3)探测:再依次扫描Probe Table(order)的数据,使用相同的hash函数映射Hash Table中的记录,映射成功之后再检查join条件(item.id = order.i_id),如果匹配成功就可以将两者join在一起。
基本流程可以参考上图,这里有两个小问题需要关注:
(1)hash join性能如何?很显然,hash join基本都只扫描两表一次,可以认为o(a+b),较之最极端的笛卡尔集运算a*b,不知甩了多少条街;
(2)为什么Build Table选择小表?道理很简单,因为构建的Hash Table最好能全部加载在内存,效率最高;这也决定了hash join算法只适合至少一个小表的join场景,对于两个大表的join场景并不适用。
上文说过,hash join是传统数据库中的单机join算法,在分布式环境下需要经过一定的分布式改造,说到底就是尽可能利用分布式计算资源进行并行化计算,提高总体效率。hash join分布式改造一般有两种经典方案:
(1)broadcast hash join:将其中一张小表广播分发到另一张大表所在的分区节点上,分别并发地与其上的分区记录进行hash join。broadcast适用于小表很小,可以直接广播的场景;
(1)shuffler hash join:一旦小表数据量较大,此时就不再适合进行广播分发。这种情况下,可以根据join key相同必然分区相同的原理,将两张表分别按照join key进行重新组织分区,这样就可以将join分而治之,划分为很多小join,充分利用集群资源并行化。
3、Broadcast Hash Join
如下图所示,broadcast hash join可以分为两步:
(1)broadcast阶段:将小表广播分发到大表所在的所有主机。广播算法可以有很多,最简单的是先发给driver,driver再统一分发给所有executor;要不就是基于bittorrete的p2p思路;
(2)hash join阶段:在每个executor上执行单机版hash join,小表映射,大表试探;
SparkSQL规定broadcast hash join执行的基本条件为被广播小表必须小于参数
spark.sql.autoBroadcastJoinThreshold,默认为10M。
4、Shuffle Hash Join
大数据条件下如果一张表很小,执行join操作最优的选择无疑是broadcast hash join,效率最高。但是一旦小表数据量增大,广播所需内存、带宽等资源必然就会太大,broadcast hash join就不再是最优方案。此时可以按照join key进行分区,根据key相同必然分区相同的原理,就可以将大表join分而治之,划分为很多小表的join,充分利用集群资源并行化。如下图所示,shuffle hash join也可以分为两步:
(1) shuffle阶段:分别将两个表按照join key进行分区,将相同join key的记录重分布到同一节点,两张表的数据会被重分布到集群中所有节点。这个过程称为shuffle
(2)hash join阶段:每个分区节点上的数据单独执行单机hash join算法。
看到这里,可以初步总结出来如果两张小表join可以直接使用单机版hash join;如果一张大表join一张极小表,可以选择broadcast hash join算法;而如果是一张大表join一张小表,则可以选择shuffle hash join算法;那如果是两张大表进行join呢?
5、Sort-Merge Join
SparkSQL对两张大表join采用了全新的算法-sort-merge join,如下图所示,整个过程分为三个步骤:
(1)shuffle阶段:将两张大表根据join key进行重新分区,两张表数据会分布到整个集群,以便分布式并行处理;
(2)sort阶段:对单个分区节点的两表数据,分别进行排序;
(3)merge阶段:对排好序的两张分区表数据执行join操作。join操作很简单,分别遍历两个有序序列,碰到相同join key就merge输出,否则取更小一边,见下图示意:
6、总结
可以明确每种Join算法都有自己的适用场景,数据仓库设计时最好避免大表与大表的join查询,SparkSQL也可以根据内存资源、带宽资源适量将参数spark.sql.autoBroadcastJoinThreshold调大,让更多join实际执行为broadcast hash join。
七、spark小练习
package com.test
import org.apache.spark.{SparkConf, SparkContext}
/*
* input:
* a,1,2
* a,3,2
* b,1,1
* output:
* a,4,4
* b,1,1
* 分析:
* a b 分组
* (a,(1,2))
* (a,(3,2))
* (b,(1,1))
* 分组内相同key的value相加
* */
object Test10 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("my app")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(
List("a", 1, 2),
List("a", 3, 2),
List("b", 1, 1)
))
rdd.map(x =>{
val key = x(0)
val v1 = x(1).toString.toInt
val v2 = x(2).toString.toInt
(key,(v1,v2))
}).reduceByKey((x,y) =>(x._1+x._1,x._2+x._2)).foreach(println)
sc.stop()
}
}