前面已经给大家深入的讲解过RDD、DataSet、DataFrame的概念已经用法,那么到底这三者有什么关联呢?各自都有什么优劣?带着这这样的问题,今天小编就给大家详细的概述下。
在spark中,基本每次都会用到这三个类型,因为这是spark最常用的数据类型。
相同点
1、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利
2、三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action算子如foreach时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在Action中使用对应的结果,在执行时会被直接跳过,比如:
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))
3、三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
4、三者有许多共同的函数,如filter,排序等
5、在对DataFrame和Dataset进行隐式转换操作都需要这个包进行支持
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下推、裁剪等。
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等列式存储格式的优势,仅扫描查询真正涉及的列,忽略其余列的数据。
为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个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的要好很多。
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
优点:
1、rdd编译时类型安全,类型错误在编译期就会检查出来,rdd通过 . 操作数据,有点儿面向对象的风格。
2、内置很多函数操作,group,map,filter 等,方便处理结构化或非结构化数据。
缺点:
性能开销和GC性能开销(存储的是一个个对象,集群不同节点之间的通信、io过程等都会进行序列化和反序列化,会造成性能上的开销,频繁的创建和销毁对象会增加GC性能的开销(垃圾回收))
DataFrame
优点:
1、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的问题了。
2、结构化数据处理非常方便,支持 Avro, CSV, elastic search, and Cassandra 等 kv 数据,也支持HIVE tables, MySQL 等传统数据表
缺点:
1、和rdd有很好的对比,解决了rdd的缺点,但是丢弃了rdd的优点。
2、DataFrame编译时类型不安全,类型问题在运行期间才会检查出来。
3、对于对象支持不友好,rdd 内部数据直接以 java 对象存储,DataFrame 内存存储的是 row对象而不能是自定义对象
DataSet
优点:
1、DataSet结合了rdd和DataFrame的优点,类型安全,引入了一个新的概念——Encoder,序列化数据时Encoder生成字节码和off-head交互,按需访问数据;
2、和 RDD 一样,支持自定义对象存储
3、采用堆外内存存储,gc 友好
4、DataSet在访问某个字段是很方便的,不用序列化整个对象,节省开销,而DataFrame只能通过调用getAS方法和模式匹配才能获得特定字段。
5、和 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 开发者们带来许多方面的好处。
1、静态类型与运行时类型安全
例如,在用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,前者提供低级别的功能和更多的控制,后者允许自定义视图和结构,提供高级和特定领域的操作,节省空间,并能够以极高的速度执行。
后面会详细及讲解三者之间是如何相互转换的!
来源:oschina
链接:https://my.oschina.net/u/4346575/blog/4500810