Spark RDD详解

spark 系列

Spark 核心原理及运行架构

Spark RDD详解

Spark 常用算子大全



前言

看了前面的一篇 Spark 博客,相信大家对于 Spark 的基本概念、运行框架以及工作原理已经搞明白了。本篇博客将为大家详细介绍了 Spark 程序的核心,也就是弹性分布式数据集(RDD)。但到底什么是 RDD,它是做什么用的呢?请听我下面详细说来。


RDD概述

什么是 RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象。代码中是一个抽象类,它代表一个不可变可分区、里面的元素可并行计算的集合。

简单的来说,RDD是将数据项拆分为多个分区的集合,存储在集群的工作节点上的内存和磁盘中,并执行正确的操作;复杂的来说,RDD是用于数据转换的接口,RDD指向了存储在HDFS、Cassandra、HBase等、或缓存(内存、内存+磁盘、仅磁盘等),或在故障或缓存收回时重新计算其他RDD分区中的数据。

RDD是只读的、分区记录的集合,每个分区分布在集群的不同节点上;RDD默认存放在内存中,当内存不足,Spark自动将RDD写入磁盘RDD并不存储真正的数据,只是对数据和操作的描述。

RDD 的属性

在这里插入图片描述

在 Spark 官网对 RDD 的描述主要有如下5大点:

A list of partitions

一个分区列表,一个rdd有多个分区,后期spark任务计算是以分区为单位,一个分区就对应上一个task线程。 通过val rdd1=sc.textFile(文件) 。如果这个文件大小的block个数小于等于2,它产生的rdd的分区数就是2 ;如果这个文件大小的block个数大于2,它产生的rdd的分区数跟文件的block相同。

A function for computing each split

由一个函数计算每一个分片 比如:rdd2=rdd1.map(x=>(x,1)) ,这里指的就是每个单词计为1的函数。

A list of dependencies on other RDDs

一个rdd会依赖于其他多个rdd,这里就涉及到rdd与rdd之间的依赖关系,后期spark任务的容错机制就是根据这个特性而来。 比如: rdd2=rdd1.map(x=>(x,1)), rdd2的结果是通过rdd1调用了map方法生成,那么rdd2就依赖于rdd1的结果对其他RDD的依赖列表,依赖还具体分为宽依赖和窄依赖,但并不是所有的RDD都有依赖。

Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)

(可选项) 对于kv类型的rdd才会有分区函数(必须要产生shuffle),也叫做分区器,对于不是kv类型的rdd分区函数是None。 分区函数的作用:它是决定了原始rdd的数据会流入到下面rdd的哪些分区中。 spark的分区函数有2种:第一种hashPartitioner(默认值), 通过key.hashcode % 分区数=分区号 ;第二种RangePartitioner,是基于一定的范围进行分区,这也是默认的分区方式。此外,用户可以根据自己的需求自定义分区函数来自定义分区。

Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

(可选项) 一组最优的数据块的位置,这里涉及到数据的本地性和数据位置最优 spark后期在进行任务调度的时候,会优先考虑存有数据的worker节点来进行任务的计算,也就是说将计算任务分派到其所在处理数据块的存储位置。大大减少数据的网络传输,提升性能。这里就运用到了大数据中移动数据不如移动计算理念

RDD的特点

RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

分区

RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。

如下图所示,分区是RDD被拆分并发送到节点的不同块之一。

  • 我们拥有的分区越多,得到的并行性就越强
  • 每个分区都是被分发到不同Worker Node的候选者
  • 每个分区对应一个Task
  • 每个分区上都有compute函数,计算该分区中的数据

在这里插入图片描述
对于如何分区,分区算法下面会详述。

只读

RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。

由一个RDD转换到另一个RDD,可以通过丰富的操作算子实现,不再像MapReduce那样只能写map和reduce了。 RDD的操作算子包括两类,一类叫做transformations转化,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions动作,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。这在我的下一篇博客 spark RDD算法大全 会详细介绍。

依赖

RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。这个下面 RDD依赖关系 会详细说明。

持久化缓存

如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。这个下面 RDD持久化 会详细说明。

RDD编程

在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。

要使用Spark,开发者需要编写一个Driver程序,它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。
在这里插入图片描述

RDD 创建方式

在Spark中创建RDD的创建方式可以分为三种:从集合中创建RDD;从外部存储创建RDD;从其他RDD创建。

1. 从集合中创建

从集合中创建RDD,Spark主要提供了两种函数:parallelizemakeRDD

1) 使用parallelize()从集合创建
scala> val rdd = sc.parallelize(Array(1,2,3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24

2) 使用makeRDD()从集合创建
scala> val rdd1 = sc.makeRDD(Array(1,2,3))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24

2. 由外部存储系统的数据集创建

包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等

scala> val rdd2= sc.textFile("hdfs://hadoop102:9000/RELEASE")
rdd2: org.apache.spark.rdd.RDD[String] = hdfs:// hadoop102:9000/RELEASE MapPartitionsRDD[4] at textFile at <console>:24

3. 从其他RDD创建

第三种方式是通过对现有RDD的转换来创建RDD,一般情况下通过RDD 动作算子得到的新的RDD来进行创建的。

RDD 算子操作

RDD的操作算子包括两类,一类叫做transformations转化,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions动作,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。这在我的下一篇博客 spark RDD算法大全 会详细介绍。

在这里插入图片描述
如上图所示,Transformation返回值还是一个RDD。它使用了链式调用的设计模式,对一个RDD进行计算后,变换成另外一个RDD,然后这个RDD又可以进行另外一次转换。这个过程是分布式的。 Action返回值不是一个RDD。它要么是一个Scala的普通集合,要么是一个值,要么是空,最终或返回到Driver程序,或把RDD写入到文件系统中。

Action是返回值返回给driver或者存储到文件,是RDD到result的变换,Transformation是RDD到RDD的变换。

只有action执行时,rdd才会被计算生成,这是rdd懒惰执行的根本所在。

RDD 函数传递

在实际开发中我们往往需要自己定义一些对于RDD的操作,那么此时需要注意的是,初始化工作是在Driver端进行的,而实际运行程序是在Executor端进行的这就涉及到了跨进程通信,是需要序列化的。

例如:

package com.hubert.test

import org.apache.spark.{
   SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

class Search(query:String) extends Serializable{
   
  //过滤出包含字符串的数据
  def isMatch(s: String): Boolean = {
   
    s.contains(query)
  }

  //过滤出包含字符串的RDD
  def getMatch1 (rdd: RDD[String]): RDD[String] = {
   
    rdd.filter(isMatch)
  }

  //过滤出包含字符串的RDD
  def getMatch2(rdd: RDD[String]): RDD[String] = {
   
    //将类变量赋值给局部变量
    val query_ : String = this.query
    rdd.filter(x => x.contains(query_))
  }
}

object Test03 {
   
  def main(args: Array[String]): Unit = {
   
    //1.初始化配置信息及SparkContext
    val sparkConf: SparkConf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值