RDD、DataFrame、DataSet介绍

在 SparkSQL 中 Spark 为我们提供了两个新的抽象,分别是 DataFrame 和DataSet。他们和 RDD 有什么区别呢?首先从版本的产生上来看:

RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)

如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。在后期的 Spark 版本中,DataSet 会逐步取代 RDD 和 DataFrame 成为唯一的 API 接口。

RDD

RDD 是一个懒执行的不可变的可以支持 Lambda 表达式的并行数据集
合。
1.RDD 的最大好处就是简单,API 的人性化程度很高。
2.RDD 的劣势是性能限制,它是一个 JVM 驻内存对象,这也就决定了存

3.在 GC 的限制和数据增加时 Java 序列化成本的升高。


DataFrame

与 RDD 类似,DataFrame 也是一个分布式数据容器。然而 DataFrame 更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即 schema。同时,与 Hive 类似,DataFrame 也支持嵌套数据类型(struct、array 和 map)。从 API 易用性的角度上看,DataFrame API 提供的是一套高层的关系操作,比函数式的 RDD API 要更加友好,门槛更低。由于与 R 和 Pandas 的 DataFrame类似,Spark DataFrame 很好地继承了传统单机数据分析的开发体验。


                  RDD[Person]                                          DataFrame

上图直观地体现了 DataFrame 和 RDD 的区别。左侧的 RDD[Person]虽然以 Person 为类型参数,但 Spark 框架本身不了解 Person 类的内部结构。而右侧的 DataFrame 却提供了详细的结构信息,使得 Spark SQL 可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。DataFrame 多了数据的结构信息,即 schema。RDD 是分布式的 Java 对象的集合。DataFrame 是分布式的 Row 对象的集合。DataFrame 除了提供了比 RDD 更丰富的算子以外,更重要的特点是提升执行效率、减少数据读取以及执行计划的优化,比如 filter 下推、裁剪等。

DataFrame 是为数据提供了 Schema 的视图。可以把它当做数据库中的一张表来对待。DataFrame 也是懒执行的。

性能上比 RDD 要高,主要有两方面原因:

定制化内存管理:数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了 GC的限制。


优化的执行计划:查询计划通过 Spark catalyst optimiser 进行优化.


逻辑查询计划:优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。得到的优化执行计划在转换成物 理执行计划的过程中,还可以根据具体的数据源的特性将过滤条件下推至数据源内。最右侧的物理执行计划中 Filter 之所以消失不见,就是因为溶入了用于执行最终的读取操作的表扫描节点内。对于普通开发者而言,查询优化器的意义在于,即便是经验并不丰富的程序员写出的次优的查询,也可以被尽量转换为高效的形式予以执行。Dataframe 的劣势在于在编译期缺少类型安全检查,导致运行时出错.

DataSet

1.是 Dataframe API 的一个扩展,是 Spark 最新的数据抽象
2.用户友好的 API 风格,既具有类型安全检查也具有 Dataframe 的查询优化特性。
3.Dataset 支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率。
4.样例类被用来在 Dataset 中定义数据的结构信息,样例类中每个属性的名称直接映射到 DataSet 中的字段名称。
5.Dataframe 是 Dataset 的特列,DataFrame=Dataset[Row] ,所以可以通过 as 方法将 Dataframe 转换为 Dataset。Row 是一个类型,跟 Car、Person 这些的类型一样,所有的表结构信息我都用 Row 来表示。

6.DataSet 是强类型的。比如可以有 Dataset[Car],Dataset[Person].

DataFrame 只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个 String进行减法操作,在执行的时候才报错,而 DataSet 不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟 JSON对象和类对象之间的类比。


RDD 让我们能够决定怎么做,而 DataFrame 和 DataSet 让我们决定做什么,控制的粒度不一样。

三者的共性

1.RDD、DataFrame、Dataset 全都是 spark 平台下的分布式弹性数据集,为处理超大型数据提供便利
2.三者都有惰性机制,在进行创建、转换,如 map 方法时,不会立即执行,只有在遇到 Action 如 foreach 时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在 Action 中使用对应的结果,在执行时会被直接跳过.
3.三者都会根据 spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出

4.三者都有 partition 的概念
5.三者有许多共同的函数,如 filter,排序等
6.在对 DataFrame 和 Dataset 进行操作许多操作都需要这个包进行支持
    import spark.implicits._
7.DataFrame 和 Dataset 均可使用模式匹配获取各个字段的值和类型
   DataFrame:

testDF.map{
    case Row(col1:String,col2:Int)=>
      println(col1);println(col2)
      col1
    case _=>
      ""
}

Dataset:

case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
testDS.map{
    case Coltest(col1:String,col2:Int)=>
      println(col1);println(col2)
      col1
    case _=>
      ""
}

三者的区别

RDD

1、RDD 一般和 spark mlib 同时使用

2、RDD 不支持 sparksql 操作

DataFrame:

1、与 RDD 和 Dataset 不同,DataFrame 每一行的类型固定为 Row,只有通过解析才能获取各个字段的值,如

testDF.foreach{
line =>
    val col1=line.getAs[String]("col1")
    val col2=line.getAs[String]("col2")
}
每一列的值没法直接访问
2、DataFrame 与 Dataset 一般不与 spark ml 同时使用
3、DataFrame 与 Dataset 均支持 sparksql 的操作,比如 select,groupby 之类,还能注册临时表/视窗,进行 sql 语句操作,如

dataDF.createOrReplaceTempView("tmp")
spark.sql("select ROW,DATE from tmp where DATE is not null order by DATE").show(100,false)
4、DataFrame 与 Dataset 支持一些特别方便的保存方式,比如保存成 csv,可以带上表头,这样每一列的字段名一目了然

//保存
val saveoptions = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://master01:9000/test")
datawDF.write.format("com.atguigu.spark.csv").mode(SaveMode.Overwrite).options(saveoptions).save()
//读取
val options = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://master01:9000/test")
val datarDF= spark.read.options(options).format("com.atguigu.spark.csv").load()

利用这样的保存方式,可以方便的获得字段名和列的对应,而且分隔符(delimiter)可以自由指定.

Dataset:

Dataset 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同。

DataFrame 也可以叫 Dataset[Row],每一行的类型是 Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的 getAS方法或者共性中的第七条提到的模式匹配拿出特定字段,而 Dataset 中,每一行是什么类型是不一定的,在自定义了 case class 之后可以很自由的获得每一行的信息

case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
/**
rdd
("a", 1)
("b", 1)
("a", 1)
**/
val test: Dataset[Coltest]=rdd.map{line=> Coltest(line._1,line._2)}.toDS
test.map{ line=>
    println(line.col1)
    println(line.col2)

}

可以看出,Dataset 在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,如果使用 Dataset,行的类型又不确定,可能是各种 case class,无法实现适配,这时候用 DataFrame 即 Dataset[Row]就能比较好的解决问题

猜你喜欢

转载自blog.csdn.net/liangzelei/article/details/80604331