SparkCore——RDD详解

下图从宏观上 简要概述spark的简单应用:
在这里插入图片描述

RDD详解

下图从宏观上 简要描述RDD:
在这里插入图片描述
Spark中,RDD是基础 ,全称是Resilient Distributed Dataset(弹性分布式数据集)
RDD的特点:

  1. 它是在集群节点上的不可变的、已分区的集合对象。RDD在抽象上来说是一种元素集合,包含了数据。它是被分区的,分为多个分区,每个分区分布在集群中的不同节点上,从而让RDD中的数据可以被并行操作。(分布式数据集)
  2. 通过并行转换的方式来创建如(map, filter, join,etc)。
  3. 容错性,失败自动重建。即如果某个节点上的RDD partition,因为节点故障,导致数据丢了,那么RDD会自动通过自己的数据来源重新计算该partition。这一切对使用者是透明的。
  4. 可以控制存储级别(内存、磁盘等)来进行重用。RDD的数据默认情况下存放在内存中的,但是在内存资源不足时,Spark会自动将RDD数据写入磁盘。(弹性)
  5. 必须是可序列化的。
  6. 是静态类型的。

RDD的创建

在Spark中创建RDD的创建方式大概可以分为两种:

  1. 从集合中创建RDD;
    spark中提供了parallelize和makeRDD两类函数来实现从集合中创建RDD。二者功能相似,不同的是makeRDD还提供了一个可以指定每一个分区perferredLocation参数的实现版本,这可以使得makeRDD将集合按顺序平均分片,另外还可以指定每一个RDD分区的优先位置,以便在后续的运行中优化调度。
    在这里插入图片描述
  2. 从外部存储中创建RDD;
    外部存储指的是操作系统本地或者HDFS,常用的方法是textFile(),sequenceFile()等。

Spark的RDD操作最重要的是转化操作(transformation)行动操作(action),两者的区别在于:
转换操作(Transasformation): 基于现有的数据集 创建一个新的数据集
行动(Action): 在数据集上进行运算,返回计算值。

RDD Transformation(转换操作)

对于RDD而言,每一次转换操作都会产生不同的RDD,供给下一个“转换”使用。转换得到的RDD是惰性求值的(lazy特性),也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作。
Transformation(转换操作)更多的是一个引用,没有真正的运算。
常用的转换操作RDD:

函数名作用
map()参数是函数,函数应用于RDD每一个元素,返回值是新的RDD
flatMap()参数是函数,函数应用于RDD每一个元素,将元素数据进行拆分,变成迭代器,返回值是新的RDD
filter()参数是函数,函数会过滤掉不符合条件的元素,返回值是新的RDD
distinct()没有参数,将RDD里的元素进行去重操作
union()参数是RDD,生成包含两个RDD所有元素的新RDD
intersection()参数是RDD,求出两个RDD的共同元素
subtract()参数是RDD,将原RDD里和参数RDD里相同的元素去掉
reduceByKey对每个key对应的value进行reduce操作

这里结合一些小案例,写出几个常用转换操作实现的代码:

object Transformation {
  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setAppName("Transformation").setMaster("local")
    val sc=new SparkContext(conf)
    val numbers=Array(1,2,3,4,5,6,7,8,9,10)

    //1.利用map实现集合中所有元素*2
    val n=numbers.map(num=>num*2)
    for (i <- n)
      print(i+" ")
    println()

    //2.利用filter过滤得到其中的偶数
    val evenNum=numbers.filter(num=>num%2==0)
    for (i<-evenNum)
      print(i+" ")

    //3.利用groupByKey进行分组
    val scoreList=Array(Tuple2("class1",90),Tuple2("class2",75),
      Tuple2("class1",92),Tuple2("class2",60))
    val scores=sc.parallelize(scoreList,4)
    val groupScores=scores.groupByKey()
    groupScores.foreach(score=>{
      println(score._1)
      score._2.foreach(singgleScore=>println(singgleScore))
      println("============================================")
    })

    //4.;利用reduceByKey统计每个班级的总分
    val totalScore=scores.reduceByKey(_+_)
    totalScore.foreach(classScore=>println(classScore._1+": "+classScore._2))

    //5.利用sortByKey按照成绩及进行排序
    val scoreL=Array(Tuple2(65,"leo"),Tuple2(72,"xjh"),Tuple2(60,"mayy"),
      Tuple2(86,"lexo"),Tuple2(92,"xjhpo"),Tuple2(56,"nbvmayy"))
    val scores=sc.parallelize(scoreL,4)
    val scored=scores.sortByKey(false)  //默认升序,加false变为降序
    scored.foreach(classScore=>println(classScore._1+": "+classScore._2))

    //利用join(关联)打印每个人的成绩
    val studentList=Array(
      Tuple2(1,"leo"),Tuple2(2,"xjh"),Tuple2(3,"kobe")
    )
    val scoreList=Array(
      Tuple2(1,100),Tuple2(2,90),Tuple2(3,80)
    )
    val student=sc.parallelize(studentList)
    val score=sc.parallelize(scoreList)
    val studentScores=student.join(score)
    studentScores.foreach(studentScore=>{
      println("student id:"+studentScore._1)
      println("student name: "+studentScore._2._1)
      println("student score: "+studentScore._2._2)
      println("===================")
    })
  }
}

看完上面的测试代码,相信你对RDD的transformation操作有了一个简单的认识。如果相对RDD算子有更深的学习和认识,可以看源代码 或者看南国推荐的这篇博客Spark核心编程-RDD操作原理分析

(2020.2.6更新)
这里我们补充说明一下map算子和mapPartition算子的区别和联系。
首先:我们看到两个算子的源代码定义:

map[U:ClassTag](f:T=>U):RDD[U]
mapPartition[U:ClassTag](f:Iterator[T]=>Iterator[U],perservesPartitioning:Boolean=false):RDD[U]

map函数将RDD中类型为T的元素,一对一地映射为类型为U的元素。
mapPartition与map转换操作类似,只不过映射函数的输入参数有RDD中的每一个元素变成了RDD中每一个分区的迭代器。这样的好处是,如果在映射放入过程中需要频繁创建额外对象,map就不显得那么高效了,RDD中的每个分区可共享同一个对象以便提供性能。例如,将RDD中的所有数据通过JDBC连接写入数据库中,如果使用map函数可能需要为每一个元素都创建一个connection,这时的开销是很大的,如果利用mapPartition接口,可以针对每一个分区创建一个connection。参数perservesPartitioning指明mapPartitionRDD是否保留父RDD的partition分区信息。

RDD Action(行动操作)

行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果
根据spark提供的api来看,行动操作可分为以下两种类型:
在这里插入图片描述
集合标量行动操作:

函数名作用
first返回RDD中第一个元素
collect返回RDD所有元素
count获取RDD里元素总数
countByValue各元素在RDD中出现次数
reduce()并行整合所有RDD数据,例如求和操作
foreach(func)对RDD每个元素都是使用特定函数
take(n)获取RDD中前n个元素
top(num)按照默认降序排序的或者指定规则排序返回前num个元素
takeOrdered(num)和top类似,只不过它是以升序排序,返回前num个元素

存储行动操作RDD:
在这里插入图片描述
在这里插入图片描述

同样写出常用ActionRDD的代码实现:

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

/**
  * action常用操作 reduce collect count take
  * @author xjh 2018.11.20
  */
object Action {
  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setAppName("Action").setMaster("local")
    val sc=new SparkContext(conf)
    val numArray=Array(1,2,3,4,5,6,7,8,9)
    val numbers=sc.parallelize(numArray)
    val sum=numbers.reduce(_+_) //利用reduce操作实现累加
    println("sum: "+sum)

    val doubleNum=numbers.map(num=>num*2)
    val doubleNumArray=doubleNum.collect()  //通过collect返回得到所有元素
    for (num<-doubleNumArray)
      print(num+" ")

    val count=numbers.count() //利用count得到数量
    println("count: "+count)

    val top3Num=numbers.take(3)   //take取前3个
    for (num<-top3Num)
      print(num+" ")

    val studentList=Array(
      Tuple2("class1","xjh"),Tuple2("class2","cdsacd"),Tuple2("class3","jiahao"),
      Tuple2("class1","kobe"),Tuple2("class2","James"),Tuple2("class1","KD")
    )
    val students=sc.parallelize(studentList)
    val studentCount=students.countByKey()
    println(studentCount)   //输出结果:Map(class3 -> 1, class1 -> 3, class2 -> 2)
  }
}

还可以使用Spark-shell:

val rdd =sc.makeRDD(List(("E",5),("B",2),("A",1),("D",4),("C",3),("H",7)),2)
 
scala> rdd.first
res0: (String, Int) = (E,5)
 
scala> rdd.take(3)
res1: Array[(String, Int)] = Array((E,5), (B,2),(A,1))
 
scala> rdd.top(3)
res2: Array[(String, Int)] = Array((H,7), (E,5),(D,4))
 
scala> rdd.takeOrdered(3)
res3: Array[(String, Int)] = Array((A,1), (B,2),(C,3))
 
scala> rdd.count
res4: Long = 6
 
scala> rdd.collect
res5: Array[(String, Int)] = Array((E,5), (B,2),(A,1), (D,4), (C,3), (H,7))
 
scala> rdd.reduce((x,y) => (x._1 + y._1, x._2+ y._2))
res6: (String, Int) = (DCHEBA,22)

val rdd1 = sc.makeRDD(List(5,1,6,9,2))
val rdd2 =sc.makeRDD(List("hadoop","spark","hive","endeca","storm"))
val rdd3 = rdd1.zip(rdd2)
 
rdd3.sortByKey().collect
Array((1,spark), (2,storm), (5,hadoop), (6,hive),(9,endeca))
 
rdd3.sortByKey(false).collect
Array((9,endeca), (6,hive), (5,hadoop), (2,storm),(1,spark))

pairRDD(键值对RDD)

1.创建Pair RDD

在saprk中有很多种创建pairRDD的方式,很多存储键值对的数据格式会在读取时直接返回由其键值对数据组成的pair RDD,此外需要把一个普通的RDD转化为pair RDD时,可以调用map函数来实现,传递的函数需要返回键值对。
例如,我们再上篇博客中写的wordcount

val pairs=words.map((_,1))

你也可以把它写为:

val pairs=words.map(s=>(s,1))
2.Pair RDD的transformation(转换操作)

(1)reduceByKey(func):合并具有相同key的value值(对每个key对应的value进行reduce操作)
(2)groupByKey():对具有相同键的进行分组 [数据分组]
(3)mapValues(func):对pairRDD中的每个值应用func 键不改变
(4)keys:返回一个仅包含键的RDD
(5)values:返回一个仅包含value的RDD
(6)sortByKey():返回一个根据键排序的RDD

3.针对两个pair RDD的transformation(转换操作)
函数名目的示例结果
substractByKey删掉RDD中键与other RDD中的键相同的元素rdd.subtractByKey(other){(1,2)}
join对两个RDD进行内连接rdd.join(other){(3,(4,9)),(3,(6,9))}
rightOuterJoin对两个RDD进行连接操作,右外连接rdd.rightOuterJoin(other){(3,(4,9)),(3,(6,9))}
leftOuterJoin对两个RDD进行连接操作,左外连接rdd.rightOuterJoin(other){(1,(2,None)),(3,(4,9)),(3,(6,9))}
cogroup将两个RDD中拥有相同键的数据分组rdd.cogroup(other){1,([2],[]),(3,[4,6],[9])}

假设这里:rdd={(1,2),(3,4),(3,6)} other={(3,9)}

4.过滤操作

对value做控制,key不加限制条件:例如,val result = rdd.filter{case(x,y)=>y%3==0}
对key做控制,value不控制:例如,val result = rdd.filter{case(x,y)=>x<3}

5.聚合操作

使用reduceByKey()和mapValues()计算每个键对应的平均值,也可使用countByValue

RDD持久化

Spark非常重要的一个功能特性就是可以将RDD持久化在内存中。当对RDD执行持久化操作时,每个节点都会将自己操作的RDD的partition持久化到内存中,并且在之后对该RDD的反复使用中,直接使用内存缓存的partition。这样的话,对于针对一个RDD反复执行多个操作的场景,就只要对RDD计算一次即可,后面直接使用该RDD,而不需要反复计算多次该RDD。
要持久化一个RDD,只要调用其cache()或者persist()方法即可。在该RDD第一次被计算出来时,就会直接缓存在每个节点中。而且Spark的持久化机制还是自动容错的,如果持久化的RDD的任何partition丢失了,那么Spark会自动通过其源RDD,使用transformation操作重新计算该partition。
cache()和persist()的区别在于,cache()是persist()的一种简化方式,cache()的底层就是调用的persist()的无参版本,也就是调用persist(MEMORY_ONLY),将数据持久化到内存中。如果需要从内存中清除缓存,那么可以使用unpersist()方法。
persist()的圆括号中包含的是持久化级别参数,比如,persist(MEMORY_ONLY)表示将RDD作为反序列化的对象存储于JVM中,如果内存不足,就要按照LRU原则替换缓存中的内容。persist(MEMORY_AND_DISK)表示将RDD作为反序列化的对象存储在JVM中,如果内存不足,超出的分区将会被存放在硬盘上。
Spark自己也会在shuffle操作时,进行数据的持久化,比如写入磁盘,主要是为了在节点失败时,避免需要重新计算整个过程。
以下为RDD持久化的简单案例:

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

/**
  * RDD持久化
  * @author xjh 2018.11.20
  */
object Persist {
  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setAppName("Persist").setMaster("local")
    val sc=new SparkContext(conf)
    val list=List("Hadoop","Hive","HBase","Spark")
    val l=sc.parallelize(list)
    l.cache() //cache()方法调用persist(MEMORY_ONLY) 语句执行到这里 并不会缓存该rdd 因为它是transformation操作
    println(l.count())  //执行action操作 触发一次从头到尾的计算,这时会执行上面的rdd持久化操作
    println(l.collect().mkString(","))  //执行第二次action操作时,就不需要从头到尾进行计算操作,只需要重复使用上面放入缓存中的rdd即可
    l.unpersist() //手动将持久化的rdd从缓存中移除
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值