spark_rdd算子介绍

1.9 算子介绍
1.9.1 RDD概念

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

在之前学习MR的过程中对数据是没有进行抽象的,而在Spark中对数据进行了抽象,提供一些列处理方法也就是说RDD(弹性分布式数据集),Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。

现在开发的过程中都是面向对象的思想,那么我们创建类的时候会对类封装一些属性和方法,那么创建出来的对象就具备着这些属性和方法,类也属于对数据的抽象,而Spark中的RDD就是对操作数据的一个抽象

查看原码可以得知,而且在类中提供了很多方法可以使用

  • 弹性

    1. 存储的弹性:内存与磁盘的自动切换;

    2. 容错的弹性:数据丢失可以自动恢复;

    3. 计算的弹性:计算出错重试机制;

    4. 分片的弹性:可根据需要重新分片。

  • 分布式:数据存储在大数据集群不同节点上

  • 数据集:RDD封装了计算逻辑,并不保存数据

  • 数据抽象:RDD是一个抽象类,需要子类具体实现

  • 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑

  • 可分区、并行计算

总结:

在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含 Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

1.9.2 RDD做了什么

从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。

Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。

RDD是Spark框架中用于数据处理的核心模型,例如在SparkShell,执行如下命令:

sc.textFile(“xx").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).saveAsTextFile(“xx")

spark_rdd算子介绍_第1张图片

总结:

从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,RDD的创建->RDD的转换(转换过程中为了减少数据计算有添加缓存)->RDD的行动(输出数据)

1.9.3 RDD五大特性

RDD源码中提供了说明

spark_rdd算子介绍_第2张图片

1)一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。
#RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
​
2)一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。
#Spark在计算时,是使用分区函数对每一个分区进行计算
​
3)RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
#RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系
​
​
4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
#当数据为KV类型数据时,可以通过设定分区器自定义数据的分区
​
5)一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
#计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

注意 : RDD本身是不存储数据,可以看做RDD本身是一个引用数据

1.9.4 RDD的弹性
  1. 自动进行内存和磁盘数据存储的切换

Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换

  1. 基于血统的高效容错机制

在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。

  1. Task如果失败会自动进行特定次数的重试

RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。

  1. Stage如果失败会自动进行特定次数的重试

如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。

  1. Checkpoint和Persist可主动或被动触发

RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。

  1. 数据调度弹性

Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。

总结:

存储的弹性:内存与磁盘的

自动切换容错的弹性:数据丢失可以

自动恢复计算的弹性:计算出错重试机制

分片的弹性:根据需要重新分片

1.9.5 创建RDD

在Spark中创建RDD的创建方式可以分为四种:

1) 从集合(内存)中创建RDD

从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
​
object CreateSparkRDDDemo {
  def main(args: Array[String]): Unit = {
    //先创建SparkConf和SparkContext对象
    val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
    val sc = new SparkContext(conf)
​
    //从集合(内存)中创建RDD --》 这种创建方式多用于测试使用
      //makeRDD和parallelize是可以指定分区数量的,有第二个参数,默认值是2,也可以指定这个值
    val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6))
    val rdd2: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6))
  }
}

2) 从外部存储(文件)创建RDD

由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
​
object CreateSparkRDDDemo {
  def main(args: Array[String]): Unit = {
    //先创建SparkConf和SparkContext对象
    val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
    val sc = new SparkContext(conf)
​
    //2.从外部存(文件)创建RDD
    val rdd3: RDD[String] = sc.textFile("hdfs://qianfeng01:9820/word.txt")
​
  }
}

3)  从其他RDD创建

主要是通过一个RDD运算完后,再产生新的RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
​
object CreateSparkRDDDemo {
  def main(args: Array[String]): Unit = {
    //先创建SparkConf和SparkContext对象
    val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd3: RDD[String] = sc.textFile("hdfs://qianfeng01:9820/word.txt")
    //3.从其他的RDD创建
    val rdd4: RDD[String] = rdd3.flatMap(_.split(" "))
  }
}

4)直接创建RDD(new)

使用new的方式直接构造RDD,一般由Spark框架自身使用。

1.9.6 RDD分类

RDD的具体实现类有几十种(大概60+),介绍下最常见的几种:

spark_rdd算子介绍_第3张图片

源数据RDD:

spark支持读取不同的数据源,如下例子:

  • 支持hdfs文件读取, HadoopRDD

  • 支持jdbc读取数据库,JdbcRDD

MapPartitionsRDD

MapPartitionsRDD对于父RDD的依赖类型只能是OneToOneDependency,代表将函数应用到每一个分区的计算。

相关transformation:map, flatMap, filter, mapPartitions 等

ShuffledRDD

对于父RDD的依赖类型只能是ShuffleDependency,代表需要改变分区方式进行shuffle的计算。

会创建ShuffledRDD的transformation:reduceByKey, sortByKey 等

默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量可以在构建RDD时指定。记住,这里的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。

1.9.7 Transformation算子概述

RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。

RDD支持两种操作:转换操作(Transformation)和行动操作(Action),RDD的转换操作是返回一个新的RDD的操作,比如map和 flatMap,而行动操作则是向Driver返回结果或将结果写出到外部存在设备,比如,collect和saveAsTextFile

列举部分算子:

转换 含义
map(func) 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
filter(func) 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
flatMap(func) 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
mapPartitions(func) 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func) 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U]
sample(withReplacement, fraction, seed) 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子
union(otherDataset) 对源RDD和参数RDD求并集后返回一个新的RDD
intersection(otherDataset) 对源RDD和参数RDD求交集后返回一个新的RDD
distinct([numTasks])) 对源RDD进行去重后返回一个新的RDD
groupByKey([numTasks]) 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD
reduceByKey(func, [numTasks]) 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) 相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值
sortByKey([ascending], [numTasks]) 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks]) 与sortByKey类似,但是更灵活
join(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
cogroup(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
cartesian(otherDataset) 笛卡尔积
pipe(command, [envVars]) 将一些shell命令用于Spark中生成新的RDD
coalesce(numPartitions) 重新分区
repartition(numPartitions) 重新分区
repartitionAndSortWithinPartitions(partitioner) 重新分区和排序
1.9.8 Action算子概述

在RDD上运行计算,并返回结果给Driver或写入文件系统

动作 含义
reduce(func) 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的
collect() 在驱动程序中,以数组的形式返回数据集的所有元素
count() 返回RDD的元素个数
first() 返回RDD的第一个元素(类似于take(1))
take(n) 返回一个由数据集的前n个元素组成的数组
takeSample(withReplacement,num, [seed]) 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子
takeOrdered(n, [ordering]) takeOrdered和top类似,只不过以和top相反的顺序返回元素
saveAsTextFile(path) 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
saveAsSequenceFile(path) 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
saveAsObjectFile(path)
countByKey() 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func) 在数据集的每一个元素上,运行函数func进行更新。
1.10 算子练习

转换算子实战

给定数据如下:
​
班级ID 姓名 年龄 性别 科目 成绩
12 张三 25 男 chinese 50
​
12 张三 25 男 math 60
​
12 张三 25 男 english 70
​
12 李四 20 男 chinese 50
​
12 李四 20 男 math 50
​
12 李四 20 男 english 50
​
12 王芳 19 女 chinese 70
​
12 王芳 19 女 math 70
​
12 王芳 19 女 english 70
​
13 张大三 25 男 chinese 60
​
13 张大三 25 男 math 60
​
13 张大三 25 男 english 70
​
13 李大四 20 男 chinese 50
​
13 李大四 20 男 math 60
​
13 李大四 20 男 english 50
​
13 王小芳 19 女 chinese 70
​
13 王小芳 19 女 math 80
​
13 王小芳 19 女 english 70
​
需求如下:
​
1. 一共有多少人参加考试?
   1.1 一共有多少个小于20岁的人参加考试?
   1.2 一共有多少个等于20岁的人参加考试?
   1.3 一共有多少个大于20岁的人参加考试?
1. 一共有多个男生参加考试?
   2.1 一共有多少个女生参加考试?
2. 12班有多少人参加考试?
   2.1 13班有多少人参加考试?
3. 语文科目的平均成绩是多少?
   3.1 数学科目的平均成绩是多少?
   3.2 英语科目的平均成绩是多少?
4. 单个人平均成绩是多少?
5. 12班平均成绩是多少?
   5.1 12班男生平均总成绩是多少?
   5.2 12班女生平均总成绩是多少?
   5.3 同理求13班相关成绩
6. 全校语文成绩最高分是多少?
   6.1 12班语文成绩最低分是多少?
   6.2 13班数学最高成绩是多少?

代码实现:

 package com.qianfeng.sparkcore

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

/**
 * 统计学生相关信息
 * 班级ID 姓名 年龄 性别 科目 成绩
12 张三 25 男 chinese 50

12 张三 25 男 math 60
 */
object Demo03_Stu {
  def main(args: Array[String]): Unit = {
    //1、获取spark上下文环境 local[n] : n代表cpu核数,*代表可用的cpu数量;如果打包服务器运行,则需要注释掉.setMaster()
    val conf = new SparkConf().setAppName("spark-rdd").setMaster("local[*]")
    val sc = new SparkContext(conf)
    //初始化数据
    val stuRDD:RDD[Stu] = sc.textFile("/Users/liyadong/data/sparkdata/stu.txt")
      .filter(_.length > 0)
      .map(line => {
        val fields = line.split(" ")
        //封装返回
        Stu(
          fields(0).trim.toInt,
          fields(1).trim,
          fields(2).trim.toInt,
          fields(3).trim,
          fields(4).trim,
          fields(5).trim.toDouble
        )
      })
    //打印原始数据
    stuRDD.foreach(println(_))
    //基于stuRDD统计需求
    //q1:参加考试的人数
    val totalStus = stuRDD
      .map(x => (x.classID, x.stuName))  //提取学生的唯一标识数据
      .distinct()  //去重
      .count()  //统计条数
    println(s"总共 $totalStus 个同学参加考试")

    val totalLess20_Stus = stuRDD
      .filter(x=>x.age <= 20)  //过滤小于等于20岁的同学
      .map(x => (x.classID, x.stuName))
      .distinct()  //去重
      .count()  //统计条数
    println(s"总共 $totalLess20_Stus 个小于20岁的同学参加考试")

    //2、平均成绩
    stuRDD
      .filter(x=>x.subject.equals("chinese"))  //过滤语文科目的数据
      .map(x=>{
        (x.subject,(1,x.score))   //过滤语文科目,累加科目和累加科目成绩的数据
      })
      .reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))   //累加科目次数和科目成绩总数
      .foreach(x=>{
        println(x._1,x._2._2/x._2._1)
      })

    //3、最高最低
    stuRDD
      .filter(x => x.subject.equals("chinese"))  //过滤语文科目的数据
      .map(x => {
        (x.subject, x.score)   //提取科目和成绩数据数据,不封装也可以
      })
      .coalesce(1)  //将并行度减少为1
      .sortBy(x => x._2, false)   //根据成绩进行到排序   ;sortBy默认升序;使用false则是降序
      .foreach(println(_))

    //5、关闭sc对象
    sc.stop()
  }
}

//封装学生考试信息
case class Stu(classID:Int,stuName:String,age:Int,sex:String,subject:String,score:Double) 

Guff_hys_python数据结构,大数据开发学习,python实训项目-CSDN博客

你可能感兴趣的:(分布式,spark,ajax,程序人生,服务器,scala,开发语言)