论RDD、DataSet、DataFrame关系及优劣

前面已经给你们深刻的讲解过RDD、DataSet、DataFrame的概念已经用法,那么到底这三者有什么关联呢?各自都有什么优劣?带着这这样的问题,今天小编就给你们详细的概述下。
在spark中,基本每次都会用到这三个类型,由于这是spark最经常使用的数据类型。javascript

相同点

一、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利
二、三者都有惰性机制,在进行建立、转换,如map方法时,不会当即执行,只有在遇到Action算子如foreach时,三者才会开始遍历运算,极端状况下,若是代码里面有建立、转换,可是后面没有在Action中使用对应的结果,在执行时会被直接跳过,好比:java

val conf = new SparkConf().setMaster("local[*]").setAppName("test").set("spark.port.maxRetries","1000")
val sc = SparkContext.getOrCreate(conf)

或者
val spark = SparkSession.builder().master("local[*]").appName("test02").getOrCreate()
val sc = spark.sparkContext
  import spark.implicits._
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7))

三、三者都会根据spark的内存状况自动缓存运算,这样即便数据量很大,也不用担忧会内存溢出
四、三者有许多共同的函数,如filter,排序等
五、在对DataFrame和Dataset进行隐式转换操做都须要这个包进行支持python

import spark.implicits._
//这里的spark是SparkSession的变量名

RDD和DataFrame的区别

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

DataFrame提高执行效率

RDD API是函数式的,强调不变性,在大部分场景下倾向于建立新对象而不是修改老对象。这一特色虽然带来了干净整洁的API,却也使得Spark应用程序在运行期倾向于建立大量临时对象,对GC形成压力。
在现有RDD API的基础之上,咱们当然能够利用mapPartitions方法来重载RDD单个分片内的数据建立方式,用复用可变对象的方式来减少对象分配和GC的开销,但这牺牲了代码的可读性,并且要求开发者对Spark运行时机制有必定的了解,门槛较高。
另外一方面,Spark SQL在框架内部已经在各类可能的状况下尽可能重用对象,这样作虽然在内部会打破了不变性,但在将数据返回给用户时,还会从新转为不可变数据。利用 DataFrame API进行开发,能够免费地享受到这些优化效果。算法

减小数据读取

Spark SQL还能够根据数据文件中附带的统计信息来进行剪枝。简单来讲,在这类数据格式中,数据是分段保存的,每段数据都带有最大值、最小值、null值数量等 一些基本的统计信息。当统计信息表名某一数据段确定不包括符合查询条件的目标数据时,该数据段就能够直接跳过(例如某整数列a某段的最大值为100,而查询条件要求a > 200)。
此外,Spark SQL也能够充分利用RCFile、ORC、Parquet等列式存储格式的优点,仅扫描查询真正涉及的列,忽略其他列的数据。
在这里插入图片描述sql

为了说明查询优化,咱们来看上图展现的人口数据分析的示例。图中构造了两个DataFrame,将它们join以后又作了一次filter操做。若是原封不动地执行这个执行计划,最终的执行效率是不高的。由于join是一个代价较大的操做,也可能会产生一个较大的数据集。
若是咱们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,即可以有效缩短执行时间。
而Spark SQL的查询优化器正是这样作的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操做替换为低成本操做的过程。
获得的优化执行计划在转换成物 理执行计划的过程当中,还能够根据具体的数据源的特性将过滤条件下推至数据源内。最右侧的物理执行计划中Filter之因此消失不见,就是由于溶入了用于执行最终的读取操做的表扫描节点内。编程

RDD和DataSet的区别

DataSet以Catalyst逻辑执行计划表示,而且数据以编码的二进制形式被存储,不须要反序列化就能够执行sorting、shuffle等操做。
DataSet创立须要一个显式的Encoder,把对象序列化为二进制,能够把对象的scheme映射为Spark
SQl类型,然而RDD依赖于运行时反射机制。
经过上面两点,DataSet的性能比RDD的要好不少。json

DataFrame和DataSet的区别

Dataset能够认为是DataFrame的一个特例,主要区别是Dataset每个record存储的是一个强类型值而不是一个Row。
DataFrame=Dataset[Row]
是dataset的一种row类型,dataframe只关心数据的结构,主要是schema 中,字段的名称以及类型,其它的并不关心。而dataset不只能够存row对象,还能够存自定义对象。缓存

Dataset 开始具备两种不一样类型的 API 特征:有明确类型的 API 和无类型的 API。从概念上来讲,你能够把 DataFrame 看成一些通用对象 Dataset[Row] 的集合的一个别名,而一行就是一个通用的无类型的 JVM 对象。
与之造成对比,Dataset 就是一些有明确类型定义的 JVM 对象的集合,经过你在 Scala 中定义的 Case Class 或者 Java 中的 Class 来指定。安全

三者各自的区别:

1.DataFrame与Dataset均支持sparksql的操做,好比select,groupby之类,还能注册临时表/视窗,进行sql语句操做,但RDD不支持。

2.RDD不支持sparksql操做。

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

testDF.foreach{ 
 
  
line =>
val test1=line.getAs[String]("test1")
val test=line.getAs[String]("test2")
}

每一列的值无法直接访问.

4.DataFrame与Dataset支持一些特别方便的保存方式,好比保存成csv,能够带上表头,这样每一列的字段名一目了然.
利用这样的保存方式,能够方便的得到字段名和列的对应,并且分隔符能够自由指定.

5.这里主要对比Dataset和DataFrame,由于Dataset和DataFrame拥有彻底相同的成员函数,区别只是每一行的数据类型不一样.
DataFrame也能够叫Dataset[Row],每一行的类型是Row,不解析,就不知道有哪些字段,也拿不到字段的类型;
而Dataset中,每一行是什么类型是不必定的,在自定义了case class以后能够很自由的得到每一行的信息。

因此,Dataset在须要访问列中的某个字段时是很是方便的,然而,若是要写一些适配性很强的函数时,若是使用Dataset,行的类型又不肯定,多是各类case class,没法实现适配,这时候用DataFrame即Dataset[Row]就能比较好的解决问题。

在什么状况下使用 RDD?

  • 但愿能够对你的数据集进行最基本的转换、处理和控制;
  • 数据是非结构化的,好比流媒体或者字符流;
  • 想经过函数式编程而不是特定领域内的表达来处理你的数据;
  • 不但愿像进行列式处理同样定义一个模式,经过名字或字段来处理或访问数据属性;
  • 并不在乎经过 DataFrame 和 Dataset 进行结构化和半结构化数据处理所能得到的一些优化和性能上的好处;

何时使用DataFrame 或 Dataset ?

  • 若是你须要丰富的语义、高级抽象和特定领域专用的 API,那就使用 DataFrame 或 Dataset;
  • 若是你的处理须要对半结构化数据进行高级处理,如 filter、map、aggregation、average、sum、SQL 查询、列式访问或使用 lambda 函数,那就使用 DataFrame 或 Dataset;
  • 若是你想在编译时就有高度的类型安全,想要有类型的 JVM 对象,用上 Catalyst 优化,并得益于 Tungsten 生成的高效代码,那就使用 Dataset;
  • 若是你想在不一样的 Spark 库之间使用一致和简化的 API,那就使用 DataFrame 或 Dataset;
  • 若是你是 R 语言使用者,就用 DataFrame;
  • 若是你是 Python 语言使用者,就用 DataFrame,在须要更细致的控制时就退回去使用 RDD;

各自的优缺点:

RDD

优势:
一、rdd编译时类型安全,类型错误在编译期就会检查出来,rdd经过 . 操做数据,有点儿面向对象的风格。
二、内置不少函数操做,group,map,filter 等,方便处理结构化或非结构化数据。

缺点:
性能开销和GC性能开销(存储的是一个个对象,集群不一样节点之间的通讯、io过程等都会进行序列化和反序列化,会形成性能上的开销,频繁的建立和销毁对象会增长GC性能的开销(垃圾回收))

DataFrame

优势:
一、DataFrame和DataSet可使用诸如select、groupby等spark sql操做,能够注册成视图后用sql编写执行逻辑,底层有Catalyst优化机制。
DataFrame中引入了schama和off-heap,数据结构存储在schama中,spark经过schama去读懂数据,序列化和反序列化时只需序列化数据便可,把结构省去了。
off-heap是jvm堆外内存,受操做系统管理,Spark能够把数据序列化到off-heap中,用的时候操做off-heap中的内存便可,这样不受jvm的限制,不用担忧GC的问题了。
二、结构化数据处理很是方便,支持 Avro, CSV, elastic search, and Cassandra 等 kv 数据,也支持HIVE tables, MySQL 等传统数据表
缺点:
一、和rdd有很好的对比,解决了rdd的缺点,可是丢弃了rdd的优势。
二、DataFrame编译时类型不安全,类型问题在运行期间才会检查出来。
三、对于对象支持不友好,rdd 内部数据直接以 java 对象存储,DataFrame 内存存储的是 row对象而不能是自定义对象

DataSet

优势:
一、DataSet结合了rdd和DataFrame的优势,类型安全,引入了一个新的概念——Encoder,序列化数据时Encoder生成字节码和off-head交互,按需访问数据;
二、和 RDD 同样,支持自定义对象存储
三、采用堆外内存存储,gc 友好
四、DataSet在访问某个字段是很方便的,不用序列化整个对象,节省开销,而DataFrame只能经过调用getAS方法和模式匹配才能得到特定字段。
五、和 DataFrame 同样,支持结构化数据的 sql查询

缺点:
在一些类型不肯定的场景下,使用DataSet没法进行有效的适配,这时使用DataSet[Row]——DataFrame能够解决这类问题。
不少状况下,Dataset 的性能其实是会比 DataFrame 要来得差的,由于 Dataset 会涉及到额外的数据格式转换成本。

new DateSet

自Spark2.0以后,DataFrame和DataSet合并为更高级的DataSet,新的DataSet具备两个不一样的API特性:1.非强类型(untyped),DataSet[Row]是泛型对象的集合,它的别名是DataFrame;2.强类型(strongly-typed),DataSet[T]是具体对象的集合,如scala和java中定义的类。
在 Spark 2.0 里,DataFrame 和 Dataset 的统一 API 会为 Spark 开发者们带来许多方面的好处。

一、静态类型与运行时类型安全

例如,在用Spark SQL进行查询时,直到运行时才会发现(syntax error)语法错误(这样成本过高),而采用DataFrame和DataSet时,能够在编译时就能够发现错误(从而节省开发时间和成本)。
换句话说,若是你在DataFrame中调用的一个函数不是API的一部分,编译器会捕获这个错误。可是,对于一个不存在的列名,在编译期是检测不出来的,那就要到运行时才能发现错误了。

由于 Dataset API 都是用 lambda 函数和 JVM 类型对象表示的,全部不匹配的类型参数均可以在编译时发现。并且在使用 Dataset 时,你的分析错误也会在编译时被发现,这样就节省了开发者的时间和代价。

2.结构化和半结构化数据的高级抽象和自定义视图

把 DataFrame 当成 Dataset[Row] 的集合,就能够对你的半结构化数据有了一个结构化的定制视图。好比,假如你有个很是大量的用 JSON 格式表示的物联网设备事件数据集。
由于 JSON 是半结构化的格式,那它就很是适合采用 Dataset 来做为强类型化的Dataset[DeviceIoTData] 的集合。

JSON串:{ 
 
  "device_id": 432465, "device_name": "sensor-pad-198164owomcJZ", "ip": "80.55.20.25"}

能够经过Scala定义一个样例类-DeviceIoTData:

case class DeviceIoTData (device_id: Long, device_name: String,ip: String)

这样,咱们就能够从JSON文件中读取数据了:

// read the json file and create the dataset from the case class DeviceIoTData
// ds is now a collection of JVM Scala objects DeviceIoTData
val ds = spark.read.json(/databricks-public-datasets/data/iot/iot_devices.json”).as[DeviceIoTData]

上述代码,经历了三段过程:
1.Spark读取了JSON文件,并根据定义的结构,建立了一个DataFrame的数据集
2.在这个DataFrame的数据集,即Dataset[Row]中,实际是一个个的行对象,由于它并不知道各自的类型
3.最后,spark将Dataset[Row]转换为Dataset[DeviceIoTData],每一行数据被转化为了一个个的实例对象

许多和结构化数据打过交道的人都习惯于用列的模式查看和处理数据,或者访问对象中的某个特定属性。将 Dataset 做为一个有类型的 Dataset[ElementType] 对象的集合,你就能够很是天然地又获得编译时安全的特性,又为强类型的 JVM 对象得到定制的视图。
并且你用上面的代码得到的强类型的 Dataset[T] 也能够很是容易地用高级方法展现或处理。

3.API结构的易用性

虽然结构化可能会限制你的 Spark 程序对数据的控制,但它却提供了丰富的语义,和方便易用的特定领域内的操做,后者能够被表示为高级结构.
事实上,用 Dataset 的高级 API 能够完成大多数的计算。好比,它比用 RDD 数据行的数据字段进行 agg、select、sum、avg、map、filter 或 groupBy 等操做简单得多,只须要处理 Dataset 类型的 DeviceIoTData 对象便可。
用一套特定领域内的 API 来表达你的算法,比用 RDD 来进行关系代数运算简单得多.

4.性能和优化

由于DataFrame和DataSet的API是创建在Spark SQL引擎之上的,不管是java、scala仍是python,全部涉及到关系型查询的语句,都会经历相同的逻辑优化和执行计划。
不一样的是, Dataset[T]类的API更适合数据工程任务,Dataset[Row](即DataFrame)类的API则更适合交互式分析。
并且,spark做为一种编译器能够理解DataSet中的JVM对象,能够经过Tungsten编码,将这些对象进行快速的序列化和反序列化,同时生成压缩字节码,这样执行效率就很是高了。

总结
何时使用RDD、DataFrame和DataSet,前者提供低级别的功能和更多的控制,后者容许自定义视图和结构,提供高级和特定领域的操做,节省空间,并可以以极高的速度执行。

后面会详细及讲解三者之间是如何相互转换的!