RDD、DataFrame、DataSet​​​​互相转换+spark读取操作+sparkjoin选择策略

目录

一、DataFrame是什么

二、DataSet是什么

三、DataFrame

三、RDD、DataFrame、DataSet之间​​​​​​​的互相转换

四、spark读取

1、对于Hive

2、对文件操作

3、对数据库操作

五、hint与AQE

六、spark sql是如何选择join策略的?

1、SparkSQL支持三种Join算法

2、Hash Join

3、Broadcast Hash Join

 4、Shuffle Hash Join

5、Sort-Merge Join 

6、总结

七、spark小练习


一、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()
  }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值