在Spark 2.0 Release中,官方提供了3种数据抽象结构供使用:RDD,DataFrame and DataSet。
对于新手来说,可能会对理解三种结构间的关系和决定使用不使用哪一种感到迷惑。本文会详细介绍区别。
简要综述
在分开讨论前,来一个简述,三种结构产生顺序如下:
RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
RDD是最老的从1.0版本就可用的,Dataset是最新的从1.6版本可用的。给定同样数据,三种抽象结构都会计算并得出相同的结果给用户。但是他们在性能上和计算方式上有所不同。
RDD让我们决定如何做,这限制了Spark在底层处理时的优化,而在处理底层时,dataframe/dataset让我们决定做什么,而把如何计算全部交给了Spark。
让我们花两分钟理解如何做和做什么的区别。
相比RDD,Dataframe带来了一个主要的性能提升,同时也带来了一些缺点,这导致Dataset的发展,它是RDD和Dataframe最好的统一。未来,Dataset最终会替代RDD和Dataframe成为Spark用户在编写代码时唯一要考虑的API。让我们逐个来理解。
RDD:
Spark编译block。不管我们使用Dataframe还是Dataset,内部最终会使用RDD计算。
RDD是延迟评估(加载)的可以暴露给lambda函数的不可变并行对象的集合。
RDD最好的部分是它是简单的。它提供了类似面向对象风格的API。我们可以从数据源加载任何数据,将其转化为RDD并且存进内存中计算结果。RDD可以被轻易缓存如果相同数据集需要被重复计算。
但是RDD的缺点是性能瓶颈。作为缓存至jvm内存的对象,RDD包含GC和Java序列化,这在数据量巨大时会带来昂贵开销。
Dataframe:
Dataframe是一个抽象结构,它提供了数据的schema视图。这意味着它给我们提供了像带有列和类型信息的数据视图,我们可以认为在Dataframe中的数据就像是数据库中的表一样。
与RDD类似,Dataframe的执行也是延迟触发的。
比RDD提供了巨大的性能提升,因为有以下两个强有力的特性:
-
- 1. 自定义内存管理 (又名 Project Tungsten)
数据会用二进制格式存储在非堆内存。这节省了大量内存。并且在这里没有垃圾回收。由于已经提前知晓数据的schema并且用二进制存储也很高效,昂贵的Java序列化开销也可以避免。
- 2.优化的执行计划(又名 Catalyst Optimizer)
查询计划是在用Spark Catalyst Optimizer执行时候生成的。优化过的执行计划最终的执行只会在RDD内部,而且对用户完全隐藏。
在上面的查询中,filter是在join上层调用的,这是个有代价的shuffle操作。逻辑上的执行计划可以看到,而在优化的执行计划中,执行计划是被下推到join之前做的,它可以平衡datasource的负载能力,并且将过滤器下推至datasource就可以将过滤器应用在磁盘层面而不是在内存中做过滤操作(这在直接使用RDD时是不可能的)。所以filter方法可以高效执行就像数据库查询的WHERE子句一样。并且,在优化的数据源如parquet,如果Spark发现你只需要读少量列就可以计算出结果的话,它只会去读和获取parquet的这些列,同时节省了磁盘IO和内存。
Dataframe缺点:缺乏类型安全。作为开发者,不会使用Dataframe因为它看起来对开发者不是那么友好。通过string的name去引用attribute意味着编译时不安全。在运行可能会失败。另外其API看起来结构不好。
Dataframe实例: 2种方式定义: 1. Expression BuilderStyle 2. SQL Style[图片上传失败...(image-e6d1b-1533828571323)] 如上所述,如果我们尝试使用一些schema中没有出现的列,我们会在运行时候出错。例如,我们尝试访问salary,而schema中只有name和age。
Dataset:
它是Dataframe的扩展,是尝试去提供RDD和Dataframe二者结合最好的抽象结构。
面向对象的风格,开发者友好,像RDD一样编译时安全,并且性能又像Dataframe一样有Catalyst optimizer 和 custom memory management。
Dataset是如何超过Dataframe的?一个新特性:Encoders。
Encoders是一个在JVM对象和非堆自定义内存二进制数据之间的接口。
Encoders生成二进制代码来跟非堆数据交互,并且提供有需访问给独立的参数而不是对整个对象反序列化。
case class用于定义Dataset中的数据schema结构,使用case class,可以很轻易用Dataset。case class中不同参数的名字是跟Dataset中的filed有映射关系的。它给人一种使用RDD但是底层使用的是Dataframe的感觉。
-
Dataframe实际上是被当作通用row对象的dataset来看待的。DataFrame=Dataset[Row] 。所以我们可以在任何时候把Dataframe转化成Dataset通过调用“as”方法。如:
df.as[MyClass]
重点记得无论是Dataset还是Dataframe,内部执行都是用RDD的,但不同的是,用户不用写代码来创建RDD集合也不用控制这些RDD。RDD会在执行计划的最后一步,在优化以后生成。这是文章开头说的,RDD让我们决定如何做,dataframe/dataset让我们决定做什么。
所有这些优化都是可行的因为数据是结构化的并且Spark事先知道数据的schema。所以它可以使用所有很棒的特性如tungsten非堆二进制内存自定义管理,catalyst优化器和encoders来获取性能提升,在在只使用RDD的情况下是不可能实现的。
结论:
简而言之,Spark将非结构化计算移至结构化计算因为这样可以允许多种性能优化。Dataframe是结构化计算的第一步,但是缺乏友好。最终Dataset统一了Dataframe和RDD,带来了最好的抽象结构。走在前面的开发者只需要考虑Dataset因为Dataframe和RDD不推荐使用。