一、RDD、DataFrame和DataSet的定义
在开始Spark RDD与DataFrame与Dataset之间的比较之前,先让我们看一下Spark中的RDD,DataFrame和Datasets的定义:
Spark RDD:RDD代表弹性分布式数据集。它是记录的只读分区集合。 RDD是Spark的基本数据结构。它允许程序员以容错方式在大型集群上执行内存计算。
Spark Dataframe:与RDD不同,数据以列的形式组织起来,类似于关系数据库中的表。它是一个不可变的分布式数据集合。 Spark中的DataFrame允许开发人员将数据结构(类型)加到分布式数据集合上,从而实现更高级别的抽象。
Spark Dataset:Apache Spark中的Dataset是DataFrame API的扩展,它提供了类型安全(type-safe),面向对象(object-oriented)的编程接口。 Dataset利用Catalyst optimizer可以让用户通过类似于sql的表达式对数据进行查询。
1. 细说DataFrame
DataFrame的前身是SchemaRDD。Spark1.3更名为DataFrame。不继承RDD,自己实现了RDD的大部分功能。
与RDD类似,DataFrame也是一个分布式数据集:
1)DataFrame可以看做分布式 Row 对象的集合,提供了由列组成的详细模式信息,使其可以得到优化。DataFrame 不仅有比RDD更多的算子,还可以进行执行计划的优化。
2)DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。
3)DataFrame也支持嵌套数据类型(struct、array和map)。
4)DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。
5)Dataframe的劣势在于在编译期缺少类型安全检查,导致运行时出错。
2. 细说DataSet
1)DataSet是在Spark1.6中添加的新的接口。
2)与RDD相比,保存了更多的描述信息,概念上等同于关系型数据库中的二维表。
3)与DataFrame相比,保存了类型信息,是强类型的,提供了编译时类型检查。
4)调用Dataset的方法先会生成逻辑计划,然后Spark的优化器进行优化,最终生成物理计划,然后提交到集群中运行。
5)DataSet包含了DataFrame的功能,在Spark2.0中两者得到了统一:DataFrame表示为DataSet[Row],即DataSet的子集。
3. 结构图解:
1)RDD[Person]:
以Person 为类型参数,但不了解其内部结构。
2)DataFrame:
提供了详细的结构信息schema 列的名称和类型。这样看起来就像一张表了。
3)DataSet:
不光有schema 信息,还有类型信息。
4. 数据图解:
假设RDD中的两行数据长这样:RDD[Person]:
那么DataFrame中的数据长这样:
DataFrame = RDD[Row] + Schema;DataFrame 的前身是 SchemaRDD。
那么Dataset中的数据长这样:Dataset[Person] = DataFrame + 泛型
或者长这样(每行数据是个Object):Dataset[Row],即DataFrame = DataSet[Row]
DataSet包含了DataFrame的功能,Spark2.0中两者统一,DataFrame表示为DataSet[Row],即DataSet的子集。
5. 补充说明:Row & Schema
Row是一个泛化的无类型 JVM object,Row
对象表示的是一个行,Row
的操作类似于 Scala
中的 Map
数据类型。
// 一个对象就是一个对象
val p = People(name = "zhangsan", age = 10)
// 同样一个对象, 还可以通过一个 Row 对象来表示
val row = Row("zhangsan", 10)
// 获取 Row 中的内容
println(row.get(1))
println(row(1))
// 获取时可以指定类型
println(row.getAs[Int](1))
// 同时 Row 也是一个样例类, 可以进行 match
row match {
case Row(name, age) => println(name, age)
}
什么是schema?
DataFrame中提供了详细的数据结构信息,从而使得SparkSQL可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么,DataFrame中的数据结构信息,即为schema。
二、三者的共性
1. RDD、DataFrame、DataSet全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利;
2. 三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算;
3. 三者有许多共同的函数,如filter,排序等;
4. 在对DataFrame和Dataset进行操作许多操作都需要这个包:import spark.implicits._(在创建好SparkSession对象后尽量直接导入);
5. 三者都会根据 Spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出;
6. 三者都有partition的概念;
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、DataFrame和DataSet的联系
1. RDD
优点:
- 编译时类型安全
编译时就能检查出类型错误 - 面向对象的编程风格
直接通过类名点的方式来操作数据
缺点:
- 序列化和反序列化的性能开销
无论是集群间的通信, 还是IO操作都需要对对象的结构和数据进行序列化和反序列化 - GC的性能开销
频繁的创建和销毁对象, 势必会增加GC
2. DataFrame
DataFrame引入了schema和off-heap:
- schema : RDD每一行的数据, 结构都是一样的。这个结构就存储在schema中。 Spark通过schame就能够读懂数据, 因此在通信和IO时就只需要序列化和反序列化数据, 而结构的部分就可以省略了。
- off-heap : 意味着JVM堆以外的内存, 这些内存直接受操作系统管理(而不是JVM)。Spark能够以二进制的形式序列化数据(不包括结构)到off-heap中, 当要操作数据时, 就直接操作off-heap内存。由于Spark理解schema, 所以知道该如何操作。
off-heap就像地盘, schema就像地图, Spark有地图又有自己地盘了, 就可以自己说了算了, 不再受JVM的限制, 也就不再收GC的困扰了。
通过schema和off-heap, DataFrame解决了RDD的缺点, 但是却丢了RDD的优点。 DataFrame不是类型安全的, API也不是面向对象风格的。
DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段。
优点:
DataFrame 内部有明确 Scheme 结构,即列名、列字段类型都是已知的,这带来的好处是可以减少数据读取以及更好地优化执行计划,从而保证查询效率。
缺点:
(1)Dataframe的劣势在于在编译期缺少类型安全检查,导致运行时出错。
(2)DataFrame虽然是结构化的,但是其所含的值并没有对应一个class,所以spark就定义了一个class名为Row,作为DataFrame的数据的数据结构。所以DataFrame等价于Dataset[Row]。但是Row又没有定义field,具体包含哪些字段,没法直接取出来,所以只能通过Row的各种方法比如getAs[Int](xxx)来获取属性xxx的内容。而Dataset每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息。所以DataFrame在获取内部数据的时候,方法数据的属性没有Dataset方便。
3. DataSet
DataSet结合了RDD和DataFrame的优点, 并带来的一个新的概念Encoder。
当序列化数据时, Encoder产生字节码与off-heap进行交互, 能够达到按需访问数据的效果, 而不用反序列化整个对象。
四、DataFrame和DataSet的区别
第一点: DataFrame
表达的含义是一个支持函数式操作的 表
, 而 Dataset
表达是是一个类似 RDD
的东西, Dataset
可以处理任何对象。
第二点: DataFrame
中所存放的是 Row
对象, 而 Dataset
中可以存放任何类型的对象。
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate()
import spark.implicits._
val df: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()
val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
DataFrame 就是 Dataset[Row]
Dataset 的范型可以是任意类型
第三点: DataFrame
的操作方式和 Dataset
是一样的, 但是对于强类型操作而言, 它们处理的类型不同。
DataFrame
在进行强类型操作时候, 例如 map
算子, 其所处理的数据类型永远是 Row:
df.map( (row: Row) => Row(row.get(0), row.getAs[Int](1) * 10) )(RowEncoder.apply(df.schema)).show()
但是对于 Dataset
来讲, 其中是什么类型, 它就处理什么类型:
ds.map( (item: People) => People(item.name, item.age * 10) ).show()
第四点: DataFrame
只能做到运行时类型检查, Dataset
能做到编译和运行时都有类型检查。
DataFrame
中存放的数据以Row
表示, 一个Row
代表一行数据, 这和关系型数据库类似;DataFrame
在进行map
等操作的时候,DataFrame
不能直接使用Person
这样的Scala
对象, 所以无法做到编译时检查;Dataset
表示的具体的某一类对象, 例如Person
, 所以再进行map
等操作的时候, 传入的是具体的某个Scala
对象, 如果调用错了方法, 编译时就会被检查出来。
val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
//这行代码明显报错, 无法通过编译
ds.map(person => person.hello)