Spark——Spark RDD、Dataset、DataFrame及区别

RDD

RDD(Resilient Distributed Dataset,弹性分布式数据集)是Spark中的基本抽象。RDD代表一种可并行操作的不可变的分区元素集合,它有3个特性:

  1. RDD是不可变的
  2. RDD是分区的
  3. RDD是可以并行操作的

1. 不可变性

RDD是不可变的,只能在其他的RDD上通过Transformation算子(map、filter等)转换得到。RDD创建方式:

  1. 通过Hadoop文件系统中的文件(HDFS)创建

    val rdd = sparkContext.textFile("hdfs://master-1:9000/file/test.txt", 3)
    
  2. 通过在已存在的Scala集合上创建

    val rdd = sparkContext.parallelize(Seq(1, 2, 3, 4, 5), 4)
    
  3. 通过在其他RDD转换得到

    val rdd = ...
    val rdd1 = rdd.map(m => m + 1)
    

我们知道Spark是借鉴参考了MapReduce的理念思想转化而来的。在MapReduce中,每一步的计算结果都要保存到磁盘上,在计算下一步的时候又要从磁盘中将上一步保存的结果读出来,这样反复的磁盘I/O读写,再加上磁盘读写速度低效,从而就导致了MapReduce的计算性能低下。

而Spark正是使用RDD的抽象加上内存计算,使得计算效率得到了大幅度的提升。Spark通过RDD之间的依赖关系构建有向无环图,只有在遇到Action算子的时候,才会真正触发计算,并且将计算结果优先存储在内存中,从而避免了MapReduce中哪些低效的操作。

2. 分区性

分区性是指RDD这种抽象数据集中的数据是分布在集群中不同的节点上的,正是有了分区的特性,才使得RDD可以被并行地操作。

比如下面代码中,通过第二个参数,我们创建了一个具有4个分区的RDD:

val rdd = sparkContext.parallelize(Seq(1, 2, 3, 4, 5), 4)

3. 并行操作

基于RDD的分区个数,Spark便可以创建对应分区个数的task来进行并行计算(Spark数据集每个分区对应一个task来处理)。

4. RDD内部结构

RDD有五个主要的属性:

  1. RDD分区列表
  2. 计算每个分片数据的函数(iterator()函数和compute()函数)
  3. 对其他RDD依赖关系
  4. 如果是key-value型的RDD,还会有一个Partitioner分区器(比如,HashPartitioner和RangePartitioner)
  5. 要计算的分片数据的首选位置(例如,HDFS文件块的位置)

5. RDD宽依赖、窄依赖

Spark是通过RDD的依赖关系来进行失败恢复的。

1. 窄依赖

下图中每个大的圆角矩形代表一个RDD,蓝色的小的圆角矩形代表RDD的分区。

在窄依赖中,子RDD中最多有一个partition依赖其父RDD的一个或多个partition。例如:map,flatMap,filter,mapPartitions,union等算子。
在这里插入图片描述

2. 宽依赖

在宽依赖中,子RDD中有多个partition依赖其父RDD的一个partition。例如:cogroup,join,groupyByKey,reduceByKey,combineByKey,distinct,repartition等算子。
在这里插入图片描述

宽窄依赖也是Spark划分Stage的标准。另外窄依赖是允许流水线式执行的,因为窄依赖中,子RDD中的分区和父RDD中的分区是一一对应的。

6. RDD的重用

对于被使用多次的RDD进行内存缓存,减少重复计算,提升计算效率。Spark RDD一共有7种缓存等级:

  1. MEMORY_ONLY:将RDD作为反序列的Java对象存储在JVM中,如果RDD 分区不能全部存储到内存中,那么某些分区将不能被缓存,这些分区每次在用到的时候都会被重新计算。
  2. MEMORY_AND_DISK:将RDD作为反序列的Java对象存储在JVM中,如果RDD 分区不能全部存储到内存中,那么某些分区将被存储到磁盘,这些分区在用到的时候会从磁盘读取。
  3. MEMORY_ONLY_SER:将RDD作为序列化的Java对象(每个分区为一个字节数组)进行存储。通常情况下,这种方式比存储反序列化的Java对象会更节省空间,特别是在使用更加高效的序列化器的时候。但是这种方式在读取数据的时候由于要将数据反序列化为Java对象,会使用较多的CPU资源。
  4. MEMORY_AND_DISK_SER:和MEMORY_ONLY_SER相似,只是将不能存在内存的分区保存到磁盘。
  5. DISK_ONLY:将RDD分区全都存储到磁盘。
  6. MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc:跟以上存储等级类似,只是将每个分区存储两份。
  7. OFF_HEAP:与MEMORY_ONLY_SER类似,只不过是将数据缓存在非堆内存中。这种方式的前提是,非堆内存off-memory必须被启用。

RDD重用代码示例:

val rdd = spark.sparkContext.parallelize(Seq(1, 2, ..., 4, 100000000), 4)
        .persist(StorageLevel.MEMORY_AND_DISK)
val rdd1 = rdd.map(m => m + 1)
val rdd2 = rdd.filter(f => f % 2 == 0)

Dataset

Dataset在很多方面和RDD都是类似的,它是特定领域的强类型集合,可以使用函数或者关系型操作对其进行并行处理。我们可以认为RDD和Dataset中都是一行行的数据,但是RDD中每一个行都是一个对象,内部结构是无法被Spark知晓的。而Dataset就像是一张数据库中的表,Spark不仅知道Dataset中每行数据的情况,也知道每行数据中每一列的数据类型,正是基于这种结构,在查询Dataset中的数据时,Spark SQL才能通过查询优化器取出需要查询的列,而不是每次都把整行记录都读出来。
在这里插入图片描述

Dataset上的操作也是分为transformation操作(map、filter、select等)和action操作(count、show等)。同样的,Dataset也是lazy的,只有在遇到action操作的时候才会触发计算。当action操作被调用的时候,Spark的查询优化器会优化逻辑计划,并生成物理计划进行高效的执行。

1. Encoder

由于Dataset是一个强类型的数据集,那么在生成Dataset的时候,为了高效的支持特定领域的对象,必须要使用Encoder(org.apache.spark.sql.Encoder)。Encoder的作用就是将JVM对象类型转化成Spark SQL的内部类型,或者是将Spark SQL的内部类型转成JVM对象类型。例如,类Person有两个字段name(string)和age(int),encode的作用就是告知Spark在运行时生成代码将Person对象序列化成二进制结构,这种二进制结构通常具有更低的内存査勇,并且对数据处理的效率进行了优化。

Encoder的使用,其实就是导入隐式转换:

import spark.implicits._
val ds = Seq(1, 2, 3).toDS() //需要先导入隐式转换,编译才能通过。这里隐式类型其实就是spark.implicits.newIntEncoder

2. Dataset的创建

  1. 通过读取文件系统中的文件来创建

    //spark是一个SparkSession实例
    import spark.implicits._
    val people = spark.read.parquet("...").as[Person]
    
    case class Person(name: String, age: String)
    
  2. 通过在已存在的Dataset上转换而来

    val names = people.map(_.name)
    
  3. 通过Scala集合创建

    import spark.implicits._
    val ds: Dataset[Int] = Seq(1, 2, 3, 4, 5).toDS()
    

DataFrame

每个Dataset都有一个无类型的视图,被称为DataFrame,它其实就是一个Row类型的Dataset(Dataset[Row])。DataFrame同样可以认为是一张表,DataFrame并不存储每行(Row)中每一列的类型,那么在编译期就不能像Dataset那样可以检查每一列的类型信息,只能在运行时通过解析才能获取每一列的类型。

Dataset和DataFrame都可以像操作数据库表那样进行SQL查询。

RDD、Dataset和DataFrame三者区别

  1. 三者都是分区、不可变并能进行并行操作的数据集
  2. Dataset和DataFrame内部都是结构化的数据,而RDD是无结构的
  3. RDD和Dataset都是在编译期检查类型、语法错误,而DataFrame是在运行时解析之后检查
  4. 在对Dataset和DataFrame进行操作时,Spark会利用Spark SQL中的查询优化器进行优化,而Spark并不会对RDD进行优化

参考

  1. https://spark.apache.org/docs/latest/sql-programming-guide.html
  2. https://github.com/apache/spark/tree/master/core/src/main/scala/org/apache/spark/rdd
  3. https://github.com/apache/spark/blob/master/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值