Spark02-Spark基础知识(RDD详述 Transformation Action及代码实现 pairRDD 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). 是静态类型的。
在Spark中创建RDD的创建方式大概可以分为三种:
(1)、从集合中创建RDD;
(2)、从本地文件创建RDD;
(3)、使用HDFS文件创建RDD。
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统计每个班级的总分
    //实质上只需要在groupByKey上做简单的修改
    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 Action(行动操作)

行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。
常用的行动操作RDD:

函数名 作用
collect() 返回RDD所有元素
count() 获取RDD里元素总数
countByValue() 各元素在RDD中出现次数
reduce() 并行整合所有RDD数据,例如求和操作
foreach(func) 对RDD每个元素都是使用特定函数
take(n) 获取RDD中前n个元素
SaveAsTextFile 将RDD元素保存到文件中,对每个元素调用toString方法

同样写出常用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)
  }
}

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从缓存中移除
  }
}

猜你喜欢

转载自blog.csdn.net/weixin_38073885/article/details/84330860