Spark知识点(RDD)

环境的配置

val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("test")
    val sc = new SparkContext(sparkConf)

其中“local[*]”中的 星号 代表你的机器上处理器的核数

RDD的创建

通常来说有两种创建方法 “内存、文件

//内存
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4)2)
//文件
val line: RDD[String] = sc.textFile("datas/apache.log",2)
//后面的2,代表分区的数量,如果省略则按照处理器核数分配
//我们可以用saveAsTextFile将处理的数据保存多个分区的文件
rdd.saveAsTextFile("output")

1、rdd计算 一个分区 内的数据是 一个一个 的执行的,上一数据全部逻辑执行完毕再到下一个
2、有多个分区的话,计算是无序的

在这里插入图片描述

value类型

1. map算子和mapPartitions算子

区别:map他是一个一个数据处理,mapPartitions是批处理,一次性从分区中拿取数据进行处理

在这里插入图片描述

上图可以看到,我们划分了两个分区,也打印了两次“>>>>>>>”

需要注意的是!
mapPartitions是将整个分区的数据进行计算,而数据会存在内存中,如果你的内存小,数据大的情况,有可能会造成内存溢出

一个小demo
“获取每个分区中的最大值”
在这里插入图片描述
因为mapPartitions需要传入一个迭代器,返回一个迭代器,所以我们iter.max拿到的结果并不是迭代器只是一个数,通过List中的iterator方法返回迭代器

2.mapPartitionsWithIndex算子

可以操作分区
小demo
“获取到1号分区的数据,如果不是1号分区就为空”
在这里插入图片描述
我想看看每个分区里面有什么数据
在这里插入图片描述

3.flatMap算子

压扁,拆分!分成一个一个数据
我想要一个一个的单词
在这里插入图片描述
在这里插入图片描述
上图的这样数据,我需要“压”两次才可以得到每一个单词
1、得到的是“hello scala”“good byb”
2、得到的是每一个单词了

如果存在是不同的数据结构,可以使用模式匹配
在这里插入图片描述

4.glom函数

将同一个分区的数据转换为同类型的内存数组进行处理
在这里插入图片描述
一个小demo
在这里插入图片描述

5.groupBy算子

按照指定的规则进行分组,数据会被打乱,也就是Shuffle
例如:

本来数据“1,2,3,4”,
第0分区是1,2
第1分区是3,4
我用groupBy(_%2)(也就是通过取2的余数进行分组)后分区变成了
第0分区是1,3
第1 分区是2,4

在这里插入图片描述

6.filter算子

在这里插入图片描述
符合规则的数据保留,不符合的过滤掉(
1模2后等于1不等于0,符合规则,保留!
2模2后等于0,不符合规则,过滤!
3模2后等于1不等于0,符合规则,保留!
4模2后等于0,不符合规则,过滤!

但是!
这个算子由于是过滤操作,所以可能出现数据倾斜
例如:

我分区“1”和分区“2”的数据都是1000条
经过filter操作后
分区“1”中的数据还剩下999条,过滤了1条
分区“2”中的数据还剩下110条,过滤了好多条
一个999,一个110,这个是不是造成了数据的一个不平衡
这种不平衡我们就叫做数据倾斜

7.sample算子

根据指定的规则从数据集中抽取数据

在这里插入图片描述
作用?
可以抽取分区中的数据,看是否数据倾斜,例如我抽了很多次,某一个数据出现了多次,我是不是有理由怀疑这个数据有问题

8.distinct算子

去重
在这里插入图片描述

9.coalesce算子

缩减分区,合并分区

coalesce算子默认情况下不会将数据打乱重新组合,不会shuffle
这就会出现数据倾斜

在这里插入图片描述

这里将1,2,3,4,5,6分成3个分区后,进行合并分区操作,最终变成两个分区。
分区0是1,2
分区1是3,4,5,6
这是因为没有进行shuffle操作,单纯的合并分区,会有数据倾斜的情况

如果想要避免合并分区后,没有数据倾斜的情况,可以使用第二个参数,true

在这里插入图片描述

设置为true后,数据会进行shuffle操作,分区会变为
分区0是1,4,5
分区1是2,3,6

扩大分区怎么弄?
在这里插入图片描述
我们可以理解,如果不将数据打乱,怎么扩大分区呢?所以理所当然的要使用第二个参数true
repatition算子,其实底层就是coalesce操作,而且采用的是shuffle操作,无论缩减分区还是扩大分区,都是shuffle
在这里插入图片描述

10.sortBy算子

根据给定的规则进行排序
在这里插入图片描述
根据List中元组的第一个数据的Int类型进行排序

第二个参数false就变成降序了,默认升序

排序后的分区数量还是一样的,但是中间会进行shuffle操作,这里不难理解,因为本来一个没有顺序的数据,变成了有顺序的,那是不是得进行shuffle操作?

双value类型(交集,并集,差异,拉链)

交集 intersection
并集 union
差集 subtract
拉链 zip
在这里插入图片描述
zip需要注意的是,无论是分区数量,还是分区中的数据数量要保持一致。
在这里插入图片描述

Key-Value类型

1.partitionBy算子

按照指定的partition重新进行分区

val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"aaa"),(2,"bbb"),(3,"ccc")),3)
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
2.reduceByKey算子

相同的key的数据,进行value数据的聚合操作
在这里插入图片描述
根据相同的key 也就是“a”放一组,“b”放一组,然后各自的value进行聚合(此处是累加)。
“a”中的数据为1,2,3,reduceByKey后就是,1 + 2 =3,然后在用1+2=3的结果再加下一个数3+3=6
存在shuffle操作

reduceByKey支持分区内进行预处理聚合功能,可以减少shuffle落盘的数据量,提升shuffle性能
因为reduceByKey是两两聚合,只要有两个相同的Key就可以进行预聚合了!

reduceByKey分区内和分区间计算规则是相同的

在这里插入图片描述

3.groupBykey算子

相同的key进行分组
在这里插入图片描述
groupByKey和之前的groupBy的区别?
首先,groupByKey是用Key进行分组,而groupBy是不确定的
其次,返回的类型不一样

groupByKey会被打乱,shuffle

这里我抛出了一个问题,那就是我想要分组后得到累加的值呢?
我是不是得map?
但是啊!我要map的时候我是不是得等待,等待分组,什么意思?

分区0(a,1)(a,1)(a,1)(b,1)
分区1(b,1)(b,1)(b,1)(a,1)

我得变成
( a,1)(a,1)(a,1)(a,1)
(b,1)(b,1)(b,1)(b,1)

这样我才可以进行map操作变成(a,4)(b,4)
但是我怎么知道我什么时候完成分组呢,这就需要落盘了也就是磁盘

shuffle操作必须落盘,不能在内存中数据等待,会导致内存溢出,shuffle性能非常低
在这里插入图片描述
reduceBykey和groupByKey都存在Shuffle

因为reduceByKey有预聚合功能,所以落盘数据较少,而groupByKey并不会减少数据量,因此reduceByKey的性能高

reduceByKey包含了分组和聚合,groupBykey只能分组

4.aggregateByKey算子

根据不同的规则进行分区内和分区间的计算

// TODO : 取出每个分区内相同 key 的最大值然后分区间相加
// aggregateByKey 算子是函数柯里化,存在两个参数列表
// 1. 第一个参数列表中的参数表示初始值
// 2. 第二个参数列表中含有两个参数
// 2.1 第一个参数表示分区内的计算规则
// 2.2 第二个参数表示分区间的计算规则
val rdd =
 sc.makeRDD(List(
 ("a",1),("a",2),("c",3),
 ("b",4),("c",5),("c",6)
 ),2)
// 0:("a",1),("a",2),("c",3) => (a,10)(c,10)
// => (a,10)(b,10)(c,20)
// 1:("b",4),("c",5),("c",6) => (b,10)(c,10)
val resultRDD =
 rdd.aggregateByKey(10)(
 (x, y) => math.max(x,y),
 (x, y) => x + y
 )
resultRDD.collect().foreach(println)

来一个小demo
计算相同Key的平均值
在这里插入图片描述
数据是这样的
(“a”, 1), (“a”, 2),(“b”, 3)
(“b”, 4), (“b”, 5), (“a”, 6)
因为我们要计算相同key的平均值,数值和次数都要记录,最后一除就能得到了

1.我们设置了初始值,所以会先把初始值传进来
(“a”,(0,0)) …

2.分区内的计算规则
上图中 t 是一个元组,(0,0),v是相同key的一个值(‘a’,1)中的1
元组中的第一个数我们用来记录累加值,所以可以用t._1+v来表示
元组中的第二个数我们用来记录次数,所以用t._2+1来表示

3.分区间的计算规则
分区内的计算得到的结果是
分区0 (“a”,(3,2)),(“b”,(3,1))
分区1 (“a”,(6,1)),(“b”,(9,2))
我们要把相同的Key里面的累加值和次数都要进行累加
得到 (“a”,(9,3)),(“b”,(12,3))

4.将相同的key的值相除
mapValue可以取出value的值
9/3=3
12/3=4
最后的结果就是 (“a”,3),(“b”,4)

5.combineByKey算子

功能和上面的aggregateByKey相同,只是没有初始值的概念,将第一个数据进行格式的一个转换就能得到初始值
在这里插入图片描述
将(“a”,1)变成了(“a”,(1,1))
因为没有初始值,所以后面的t要加上类型

6.flodByKey算子

分区内和分区间相同的计算规则
(其实就是aggregateByKey的第二个参数列表写了两个相同的参数)
同样的,第一个参数列表写入的是初始值,第二个参数列表传的是计算规格

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.foldByKey(0)(_+_)

在这里插入图片描述

上面说到的reduceByKey,flodByKey,CombineByKey,aggregateByKey有什么区别呢?
reduceByKey:第一个数据不进行处理,分区内和分区间的数据进行 同样 的计算
flodBykey:传入一个 初始值 ,分区内和分区间的数据进行 同样 的计算
aggregateBykey:传入一个 初始值 ,分区内和分区间的数据进行 不一样 的计算
combineByKey:将第一个数据进行格式的转换,分区内和分区间的数据进行 不一样 的计算

上原码
在这里插入图片描述

在这里插入图片描述
上面这个就是combineByKeyWithClassTag里面的

7.join,leftOuterJon算子

相同的Key进行join
在这里插入图片描述
在这里插入图片描述
返回的是一个tuple

8.cogroup

其实就是connect+group

rdd1.cogroup(rdd2)

在这里插入图片描述

行动算子

1.reduce算子

在这里插入图片描述

2.collect算子

将不同分区的数据按照分区顺序采集到Driver端的内存中,形成数组

在这里插入图片描述

3.count算子
4.first算子
5.take算子
6.takeOrdered算子

数据排序后,取n个数据在这里插入图片描述
在这里插入图片描述

7.aggregate算子

用法和aggregateByKey类似
在这里插入图片描述
aggregateByKey:初始值只会参与分区内计算
aggregate:初始值会产于分区内和分区间计算

8.flod算子

分区内和分区间计算规则一样的时候使用
在这里插入图片描述

9.countByKey,contByValue

统计次数
在这里插入图片描述

save算子
// 保存成 Text 文件
rdd.saveAsTextFile("output")
// 序列化成对象保存到文件
rdd.saveAsObjectFile("output1")
// 保存成 Sequencefile 文件,要求数据格式必须是K-V类型
rdd.map((_,1)).saveAsSequenceFile("output2")

foreach算子

分布式遍历RDD中的每一个元素
他和collect算子有什么区别呢?
collect是把数据采集回来,在Driver进行打印
在这里插入图片描述

而foreach算子是在executor端进行打印,并不能确定先打印的顺序

八种不同的WordCount方式

wordCount练习
都去敲一下!!!

血缘

在这里插入图片描述

依赖(窄依赖,宽依赖)

窄依赖:一对一,父的RDD的分区被一个子RDD分区依赖(独生子女)
在这里插入图片描述

宽依赖:一个父的RDD的分区被多个子RDD分区依赖(多生)(Shuffle依赖)
在这里插入图片描述

持久化操作cache(),persist(),checkpoint()

为什么要持久化?
先看这段代码
在这里插入图片描述
同样是用的上一个RDD进行的操作,但是底层却不是的,而是全部再执行一次,因为spark并不保存数据,当上一个RDD(也就是mapRDD)被reduceByKey使用后,它就会消失,所以groupByKey使用mapRDD并不是直接拿过来用,而是重新进行的。
在这里插入图片描述
事实上
在这里插入图片描述
但是当我们持久化后,就可以了(mapRDD.cache)
在这里插入图片描述
在这里插入图片描述
cache默认持久化操作,只能保存在 内存 中,如果想要保存到磁盘文件,需要更改存储级别

默认情况下 cache()=persist() 这两个方法是相同的
我们可以更改为

mapRDD.persist(StorageLevel.DISK_ONLY)

需要注意的!!!

持久化操作必须在行动算子执行时完成

checkpoint() 需要落盘,传入指定的保存点的路径
所以checkpoint()和persist()的落盘操作的不同点就是,persist()的落盘是保存为一个临时文件,当程序执行完成后,会删除文件的。而checkpoint()执行完成后文件并不会删除

cache(),persist(),checkpoint()这三者有什么不同?
1.cache(),将数据临时存储在内存中进行数据的重用,数据会不安全,当内存溢出时会造成数据的丢失。在血缘关系中添加新的依赖,一旦出现问题可以重头读取数据。
2.persist(),将数据临时存储在磁盘文件中进行数据重用,涉及到磁盘IO,性能较低,但是数据安全
3.checkpoint(),将数据长久的存储在文件中,涉及到磁盘IO,性能较低,数据安全。一般情况下回独立执行作业,为了提高效率,需要和cache一起使用。执行过程中,会切断血缘关系,建立新的血缘关系,等同于改变数据源。

mapRDD.cache()
mapRdd.checkpoint()

自定义分区

只有RDD Key-Value类型的才有分区器
在这里插入图片描述
在这里插入图片描述

累加器

累加器就是把Executor端的变量信息聚合到Driver端。

val rdd = sc.makeRDD(List(1,2,3,4,5))
// 声明累加器,longAccumulator就是累加器的声明了
var sum = sc.longAccumulator("sum");
rdd.foreach(
 num => {
 // 使用累加器
 sum.add(num)
 }
)
// 获取累加器的值
println("sum = " + sum.value)

如果不定义累加器,就是这种情况
在这里插入图片描述
结果打印出了sum的值还是0,因为Executor并没有把sum累加后的值传回给Driver端。
因此需要使用累加器。
在这里插入图片描述

在 Driver 程序中定义的变量,在Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后,传回 Driver 端进行 merge。

他会出现 少加 或者 多加 的情况
一般的话,累加器会放置在行动算子中进行操作。

自定义累加器
import org.apache.spark.rdd.RDD
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

object test2 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("test2")
    val sc = new SparkContext(sparkConf)
    val rdd: RDD[String] = sc.makeRDD(
      List("Hello", "Hello", "Hello", "Hello", "Spark", "Spark", "Hive", "Hive"), 2)
//创建累加器
    val wcAcc = new MyAccumulator
    //注册累加器
    sc.register(wcAcc,"wordCount")
    rdd.foreach(
          word => {
            //累加,调用wcACC对象中的add方法
            wcAcc.add(word)
          }
        )

    println(wcAcc.value)
  }

  class MyAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
   // 定义输出数据集合,一个可变的Map
    var wcMap: mutable.Map[String, Long] = mutable.Map[String, Long]()
 // 是否为初始化状态,如果集合数据为空,即为初始化状态
    override def isZero: Boolean = wcMap.isEmpty

  // 复制累加器
    override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = new MyAccumulator()
  // 重置累加器
    override def reset(): Unit = wcMap.clear()

  // 增加数据(分区内的计算)
    override def add(word: String): Unit = {
    //传入word 也就是每个单词,看看如果map中存在word就返回word对应的值,不存在就返回0
    //当第一个单词“Hello”传进来时候,map中是没有的,所以返回0,然后加1,newCnt=1
    //更新wcMap(Hello,1)
    //当第而个单词“Hello”传进来时候,map中是有的,所以返回它的值为 1,然后加1,newCnt=2
    //更新wcMap(Hello,2)
      val newCnt: Long = wcMap.getOrElse(word, 0L) + 1
      wcMap.update(word,newCnt)
    }

  // 合并累加器(分区间的计算)
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
      val map1: mutable.Map[String, Long] = this.wcMap
      val map2: mutable.Map[String, Long] = other.value

      map2.foreach{
        case (word,count) =>{
          val newCount: Long = map1.getOrElse(word, 0L) + count
          map1.update(word,newCount)
        }
      }
    }

  //累加器的值
    override def value: mutable.Map[String, Long] = {
      wcMap
    }
  }

}

广播变量

    val conf = new SparkConf().setAppName("WordCount").setMaster("local")
    val sc = new SparkContext(conf)
    val pairRdd = sc.parallelize(List((1,1), (5,10), (5,9), (2,4), (3,5), (3,6),(4,7), (4,8),(2,3), (1,2)),4)
    //将字段收集到Driver端,并广播发送到Executor
    val allIpAndProvince: Array[(Int, Int)] = pairRdd.reduceByKey(_+_).collect()
    //广播发送  返回引用
    val broadCast: Broadcast[Array[(Int, Int)]] = sc.broadcast(allIpAndProvince)
    val value24: RDD[(Int, Int)] = pairRdd.map(x => {
      //使用广播变量 进行优化
      val value: Array[(Int, Int)] = broadCast.value
      val map: Map[Int, Int] = value.toMap
      (x._1, x._2 + map.get(x._1).get)
    })
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值