SparkCore初步

文章目录


一、RDD是什么?

RDD(Resilient Distributed Dataset)弹性分布式数据集,是Spark中最基本的数据抽象。
特点:

  • 弹性:储存的弹性,内存与磁盘的自动切换
    容错的弹性:数据丢失可以自动恢复
    计算的弹性:计算出错重试机制
    分片的弹性:可以根据需要重新分片

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

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

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

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

  • 可分区、并行计算

二、RDD编程

2.1 RDD的创建

(1) 通过已有的集合得到RDD

  • parallelize
object CreateADD {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("createRDD")
    val sc = new SparkContext(conf)
    val list = List(10,1,9,2)
    val rdd = sc.parallelize(list)
    rdd.collect().foreach(print)
  }
}
  • makeRDD
object CreateADD {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("createRDD")
    val sc = new SparkContext(conf)
    val list = List(10,1,9,2)
    val rdd = sc.makeRDD(list)
    rdd.collect().foreach(print)
  }
}

(2) 通过外部存储文件获取

object CreateADD01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("createAdd").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val add = sc.textFile("E:\\bbb.txt")
    add.map(_.split(" ")).collect().foreach(println)
  }
}

运行结果:

aa
hello
spark
hadoop
flume
kafka
hadoop
hadoop

(3) 一个RDD运算完成之后,再产生新的RDD

2.2 分区规则

(1) RDD数据从集合中创建,不指定分区数

默认情况下是:分区数=总cpu核数

object CreateADD {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("createRDD")
    val sc = new SparkContext(conf)
    val list = List(10,1,9,2)
    val rdd = sc.makeRDD(list)
    rdd.mapPartitions(it=>{
      println("partition")
      it
    }).collect().foreach(println)
  }
}

输出结果:

//每一个分区打印一个partition,cup为12核,默认分区为12
partition
partition
partition
partition
partition
partition
partition
partition
partition
partition
partition
partition
10
1
9
2

(2) RDD数据从集合中创建,指定分区

object CreateADD {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("createRDD")
    val sc = new SparkContext(conf)
    val list = List(10,1,9,2,10,12,1,0,15)
    //设置分区数位4:numSlices=4
    val rdd = sc.makeRDD(list,4)
    rdd.mapPartitions(it=>{
      val partitionList = it.toList
      println(partitionList)
      it
    }).collect().foreach(println)
    sc.stop()
  }
}

结果输出:

List(10, 1)
List(10, 12)
List(9, 2)
List(1, 0, 15)

那么分区的规则是什么呢?

建立分区索引:
分区的起始索引:集合长度 *分区号/分区数
分区的终止索引:集合长度 *(分区号+1)/分区数

源码:

//length为集合长度,numSlices为分区数
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
      (0 until numSlices).iterator.map { i =>
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
    }

(3) RDD数据从文件中读取,默认分区

读取的是单个文件:

  1. 默认分区数:默认并行度和2的最小值
    ① 在spark中有对默认并行度(defaultParallelism)进行设定的话,默认并行度就等于默认值
    ② 如果没有设置,那么就等于总核数
    什么是总核数?
    local => 单核 =>1
    local[4] => 4核 => 4
    local[ * ] => 最大核数 => 我的电脑为12核
  2. 如果设定了分区数则取设定的分区数
  3. 什么是最小分区数?
    ①所谓的最小分区数,取决于总的字节数是否能够整除分区数,并且还要看剩下的字节数/每个分区的字节数是否大于10%,如果大于10%,则剩余的字节数会产生一个新的分区
  4. 实际的分区数是多少呢?
    ① 实际分区数>=默认最小分区数
    ② 算法:文件总字节数/分区数
    如果刚好整除 或者 余数/分区大小<0.1 则等于默认分区数
    如果余数/分区大小>0.1 则实际分区数等于默认分区数+1

在没有指定分区器的情况下,数据依据什么规则进入不同的分区?

通过源码发现,spark读取文件采用的是hadoop的读取原则
①切片:以字节的方式进行切分数据
②读取数据的方式:按行进行读取

  1. 那么数据到底切成几片呢?
    根据分区的数量来定
  2. 分区的数据如何存储的呢?
    ① 换行符等于两个字节
    ② 以行为单位,一行的数据不会被拆分
  3. 分区规则:二者结合使用
    ① 数据起始偏移量和字节数
    ② 数据偏移量offset

通过下面案例一起来认识:

举例1--word1数据:
        12@@
        234
        说明:换行符(用@@代替)2个字节,Spark是按照行进行读取数据。

    -- 第一步:建议生成RDD的最小分区数: => 2
    -- 第二步:计算实际的分区数:=> 3
               1.计算文件的字节数:=> 7个字节
               2.计算整除的结果和余数: 7/2=3 ..1
               3.计算余数和每个分区字节数的比率:1/3 > 10% => 生成一个新的分区  => 3
    -- 第三步:计算每个分区的数据起始偏移量和每个分区的字节数
               分区0(起始偏移量,字节数) =>(0,3) =>(0,3)
               分区1(起始偏移量,字节数) =>(3,3) =>(3,6)
               分区2(起始偏移量,字节数) =>(6,1) =>(6,7)
    -- 第四步:计算每行数据的偏移量
               12@@    => 0 1 2 3
               234     => 4 5 6
    -- 第五步:数据的分配:按行读取,数据只会被读取一次
               分区0 => 读取索引为0/1/2/3的数据,读取12
               分区1 => 读取索引为3/4/5/6的数据,发现3已经被读取了,所以读取4/5/6索引的数据,读取:234
               分区2 => 读取索引为6/7的数据,发现6已经被读取,索引7无数据,所以分区2没有数据。
     */
     
	  	val rdd1: RDD[String] = sc.textFile("input/word1",2)
  		rdd1.saveAsTextFile("output")

结果输出:
在这里插入图片描述
part-00000文件内容为:1 2
part-00001文件内容为:2 3 4
part-00002文件为空

案例二:

 举例2--word2数据:
        1@@
        2@@
        3@@
        4
        说明:换行符(用@@代替)2个字节,Spark是按照行进行读取数据。

    -- 第一步:建议生成RDD的最小分区数: => 3
    -- 第二步:计算实际的分区数:=> 4
               1.计算文件的字节数:=> 10个字节
               2.计算整除的结果和余数: 10/3=3 ..1
               3.计算余数和每个分区字节数的比率:1/3 > 10% => 生成一个新的分区
    -- 第三步:计算每个分区的数据起始偏移量和每个分区的字节数
               分区0(起始偏移量,字节数)=(0,3) =>(0,3)
               分区1(起始偏移量,字节数)=(3,3) =>(3,6)
               分区2(起始偏移量,字节数)=(6,3) =>(6,9)
               分区3(起始偏移量,字节数)=(9,1) =>(9,10)
    -- 第四步:计算数据的偏移量
               1@@  => 0 1 2
               2@@  => 3 4 5
               3@@  => 6 7 8
               4    => 9
    -- 第五步:数据的分配:按行读取,数据只会被读取一次
               分区0 => 读取索引为0/1/2/3的数据,因为第一行数据不够,所以第二行也被读取,
                       所以实际读取了索引为0/1/2/3/4/5的数据,读取1 2
               分区1 => 读取索引为3/4/5/6的数据,发现3/4/5已经被读取了,所以只能读取索引为6的数据,
                        所以读取6索引所在行,最后读取了索引为6/7/8的数据,读取:3
               分区2 => 读取索引为6/7/8/9的数据,发现6/7/8已经被读取,所以只能读取索引为9的数据,
                        所以读取索引9所在行,最后读取了索引为9的数据,读取:4。
               分区3 =>  读取索引为9/10的数据,发现9已经被读取,索引10无数据,所以分区2没有数据。
    

    val rdd2: RDD[String] = sc.textFile("input/word2",3)
    rdd2.saveAsTextFile("output")

结果输出:
在这里插入图片描述
part-00000:数据为1 2
part-00001:数据为3
part-00002:数据为4
part-00003:文件为空

object CreateADD01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("createAdd").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd = sc.textFile("E:\\bbb.txt")
    val res = rdd.flatMap(_.split(" "))
    res.mapPartitions(it=>{
      val str = it.toList
      println(str)
      it
    }).collect()
    sc.stop()
  }
}

输出结果:

//分区数位2,cpu核数与2的最小值,cpu核数肯定不止2
List(hadoop, hadoop)
List(aa, hello, spark, hadoop, flume, kafka)

源码:

def defaultMinPartitions: Int = math.min(defaultParallelism, 2)

override def defaultParallelism(): Int = scheduler.conf.getInt("spark.default.parallelism", totalCores)

***读取的是一个目录:***例如input目录,里面有多个文件a.txt、b.txt、c.txt等
分区规则
案例:

object partitions {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("partition").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val res = sc.textFile("E:\\E:\\test_input",3)
    res.saveAsTextFile("output1127_02")
  }
}
defaultMinPartitions=2
//用来保存所有文件的总大小
long totalsize = 800
long totalsize/(numSplits==0? 1:numSplits); numSplits通常情况下就是2,这里总长度一半400

long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.FileInputFormat.SPLIT_MINSIZE,1),minSplitSize)  //默认minSize为1

long blockSize = file.getBlockSize()  //本地模式32M,集群128M

computeSplitSize = Math.max(minSize,Math.min(goalSize,blockSize))   //minSize=1,goalSize=400,blockSize=32M;所以最终为400

(4) RDD数据从文件中读取,指定分区

待续

2.3 分区器

分区器:只有(K,V)形式的才有可能有分区器
rdd=sc.makeRDD(List(true->1,true->10,true->20,false->9,true->10))
rdd.map((_._2+10))它也没有分区器,因为他是一对一的。
Spark目前支持Hash分区,Range分区和用户自定义分区。Hash分区为当前的默认分区。分区器直接决定了RDD中分区的个数,RDD中每条数据经过Shuffle后进入哪个分区以及Reduce的个数。

(1) hash分区器

HashPartitioner的分区机制:
源码:

//key为null时直接在第0分区,key不为null时,对取key的hashCode值
  def getPartition(key: Any): Int = key match {
    case null => 0
    case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
  }
//key不为null时,分区号=key的hashCode值的绝对值%分区数
  def nonNegativeMod(x: Int, mod: Int): Int = {
    val rawMod = x % mod
    rawMod + (if (rawMod < 0) mod else 0)
  }

(2) range分区器

rangePartitioner的作用是将一定范围的数映射到一个分区内,尽量保证每个分区中数据量均匀,而且分区与分区之间是有序的。一个分区内的元素肯定都是比另一个分区内的元素小或者大,但是分区内元素不能保证顺序。

注意:该分区器要求RDD中key类型必须是可以排序的。同时在做水塘抽样的时候,底层是存在行动算子collect的;比如在使用sortByKey的时候,看下面代码:

object sort{
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("SortDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20).map((_, 1))
    val rdd1= sc.parallelize(list1, 2).map(x => {
      println("map....")
      x
    })
    rdd1.sortByKey()
    Thread.sleep(10000000)
    sc.stop()
  }
}

执行结果:

map....
map....
map....
map....
map....
map....

实现过程:

  1. 从整个RDD中采用水塘抽样的算法,抽取出样本数据,将样本数据进行排序,计算出每个分区的最大key值,形成Array[KEY]类型的数组变量rangeBounds。
  2. 判断key在rangeBrounds中所处的范围,给出该key值在下一个RDD中的分区id下标。

(3) 自定义分区器

见3.3中的partitionBy

三、转换算子

3.1 Value类型

(1)map算子

函数签名:map[U:ClassTag] (f:T=>U):RDD[U]

一次处理一个分区里面的一个元素

object map {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("map")
    val sc = new SparkContext(conf)
    val add = sc.parallelize(List(1,10,2,4,6,8))
    add.map(_*2).collect().foreach(println)
  }
}

执行结果:

2
20
4
8
12
16

(2)mapPartitions算子

一次处理一个分区中的数据
内存较大时建议使用MapPartitions

函数签名:mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
只是批量处理而已,并不是把每个分区的处理结果放进一个集合中

object MapPartitions {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1)
    add.mapPartitions(t=>{
      //一次处理一个分区的数据
      //tolist小心oom内存溢出
      val str = t.toList
      println(str)
      //非容器式集合:集合中元素只能使用一次,eg:Iterator
      if(t.size!=2){
        println(t.toList)
      }
      t
    }).collect()
  }
}

执行结果:

List(60, 10, 20)
List(30, 50, 70)
List()
List()

(3)mapPartitionsWithIndex算子

类似mapPartitions,比他多了一个分区号

object mapPartitionsWithIndex {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1,2)
    add.mapPartitionsWithIndex((index,it)=>{
      it.map((index,_))
    }).collect().foreach(println)
  }
}

结果输出;

(0,30)
(0,50)
(0,70)
(1,60)
(1,10)
(1,20)

(4)flatMap算子

def flatMap[U: ClassTag] (f: T => TraversableOnce [U]) : RDD[U]

object flatMap {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1,2)
    add.flatMap(x=>{
      List(x,x*x,x*2)
    }).collect().foreach(println)
    sc.stop()
  }
}

执行结果:

30
900
60
50
2500
100
70
4900
140
60
3600
120
10
100
20
20
400
40

(5)glom算子

将每一个分区变成一个数组

object glom {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1,2)
    add.glom().map(_.max).collect().foreach(println)
    sc.stop()
  }
}

结果输出:

70
60

(6)groupBy算子

分组,中间会有shuffle过程,shuffle一定会落盘

object groupBY {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3.使用parallelize()创建rdd
    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8))
    val rdd2 = rdd.groupBy(x=>{if(x%2==0)1 else 2})
    rdd2.collect().foreach(println)
    sc.stop()
  }
}

结果输出:

(1,CompactBuffer(2, 4, 6, 8))
(2,CompactBuffer(1, 3, 5, 7))

WordCount案例:

object groupByWC {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val add = sc.parallelize(List("hello world","spark hadoop","spark hadoop"))
    add.flatMap(_.split(" ")).groupBy(x=>{
      x
    }).map(x=>{
      (x._1,x._2.size)
    }).collect().foreach(println)
  }
}

执行结果:

(hadoop,2)
(spark,2)
(world,1)
(hello,1)

(7)filter算子

过滤

object filter {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1,2)
    add.filter(x=>{
      x>=60
    }).collect().foreach(println)
    sc.stop()
  }
}

执行结果:

70
60

(8)sample算子

抽样

object sample {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20)
    val add = sc.parallelize(list1,2)
    //第一个参数表示抽出后是否放回,“true”的话,第二个参数可以大于1
    add.sample(false,0.5).collect().foreach(println)
    sc.stop()
  }
}

结果输出(结果个数不定):

30
50
70
60
10
20

(9)distinct算子

去重

object distinct {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,30,50)
    val add = sc.parallelize(list1,2)
    add.distinct().collect().foreach(println)
    sc.stop()
  }
}

执行结果:

30
50
70
20
60
10

案例二:

/*
case class User(age: Int, name: String){
    override def hashCode(): Int = {
        println("hashCode")
        age
    }
    
    override def equals(obj: Any): Boolean = {
        println("equals...")
        obj match {
            case User(age1, name1) =>
                age1 == age
        }
    }
}
*/
case class Person(name:String,age:Int)
object distinctDemon {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("Person")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(Person("song",10),Person("xiao",10),Person("kang",20)))
    //需求按按年龄去重
    val group = rdd.groupBy(_.age)
    group.map(x=>{
      (x._1,x._2.toList.head)
    }).collect().foreach(println)
  }
}

结果输出:

(20,Person(kang,20))
(10,Person(song,10))

(10)coalesce算子

合并分区
Coalesce算子包括:配置执行Shuffle和配置不执行Shuffle两种方式

coalesce(numPartitions: Int, shuffle: Boolean = false, partitionCoalescer: Option[PartitionCoalescer] = Option.empty) (implicit ord: Ordering[T] = null): RDD[T]

源码:

for (i <- 0 until maxPartitions) {
          val rangeStart = ((i.toLong * prev.partitions.length) / maxPartitions).toInt
          val rangeEnd = (((i.toLong + 1) * prev.partitions.length) / maxPartitions).toInt
          (rangeStart until rangeEnd).foreach{ j => groupArr(i).partitions += prev.partitions(j) }
        }
//maxPartitions通常为numPartitions,prev.partitions.length为上一个RDD分区数

案例一:

object coalesce {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,30,50)
    val add = sc.parallelize(list1,4)
    add.glom().map(_.toList).collect().foreach(println)
    println("="*50)
    
    val add1 = add.coalesce(2)
    val partitions = add1.getNumPartitions
    println(partitions)
    add1.glom().map(_.toList).collect().foreach(println)
    sc.stop()
  }
}

结果输出:

List(30, 50)
List(70, 60)
List(10, 20)
List(20, 30, 50)
==================================================
2
List(30, 50, 70, 60)
List(10, 20, 20, 30, 50)

案例二
执行shuffle

object coalesce {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,30,50)
    val add = sc.parallelize(list1,4)
    add.glom().map(_.toList).collect().foreach(println)
    println("="*50)
    val add1 = add.coalesce(2,true)
    val partitions = add1.getNumPartitions
    println(partitions)
    add1.glom().map(_.toList).collect().foreach(println)
    sc.stop()
  }
}

执行结果:

List(30, 50)
List(70, 60)
List(10, 20)
List(20, 30, 50)
==================================================
2
List(30, 60, 10, 20, 50)
List(50, 70, 20, 30)

(11)repartition算子

重新分区(增减分区都会执行shuffle)

object repartition {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("repartition").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,30,50)
    val add = sc.parallelize(list1,4)
    val add2 = add.repartition(5)
    add2.glom().map(_.toList)
      .collect().foreach(println)
    sc.stop()
  }
}

执行结果:

List()
List(30)
List(50, 10, 20)
List(70, 20, 30)
List(60, 50)

(12)sortBy算子

排序:

object sortBy {
  case class Person(name:String,age:Int,address:String)
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("sortBy").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(Person("lily",10,"beijing"),Person("bob",19,"shanghai"),Person("marry",13,"guangzhou"),Person("maven",7,"wuhan")),2)
    rdd.sortBy(_.age).collect().foreach(println)
    sc.stop()
  }
}

执行结果:

Person(maven,7,wuhan)
Person(lily,10,beijing)
Person(marry,13,guangzhou)
Person(bob,19,shanghai)

(13)pipe算子

调用脚本:

3.2 DoubleValue类型

(1)intersection算子

交集:对交集结果去重
与scala中的interest不同,scala中是对结果不去重

object intersection {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,20,30,50)
    val list2 = List(40,50,70,20,20,2,4,7)
    val add = sc.parallelize(list1,4)
    val add2 = sc.parallelize(list2,3)
    //两个集合不同,默认分区数取最大
    add.intersection(add2).collect().foreach(println)
  }
}

结果输出:

20
50
70

(2)union算子

并集:不去重展示所有结果,与scala中的union一致

object union {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,20,20,30,50)
    val list2 = List(40,50,70,20,20,2,4,7)
    val add = sc.parallelize(list1,4)
    val add2 = sc.parallelize(list2,3)
    add.union(add2).collect().foreach(println)
  }
}

执行结果:

30
50
70
60
10
20
20
20
30
50
40
50
70
20
20
2
4
7

(3)subtract算子

差集:多个重复元素都会去掉,例如:例中的20,在RDD2中出现两次,而RDD1中只有一次,RDD2.subtract(rdd1)时会把两个20都过滤掉,而scala中只会过滤一个

object subtract {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 60, 10, 20,30,50)
    val list2 = List(40,50,70,20,20,2,4,7,7)
    val add = sc.parallelize(list1,4)
    val add2 = sc.parallelize(list2,3)
    add2.subtract(add).collect().foreach(println)
    sc.stop()
  }
}
4
7
7
40
2

(4)zip算子

zip:拉链

zip限制:

  1. 对应的分区的元素个数必须一样
  2. rdd的分区个数必须相等

对比scala中的zip,对两个集合的长度没有要求

object zip {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30,50,70,60,10,20,30)
    val list2 = List(40,70,20,20,2,4,7)
    val add = sc.parallelize(list1,3)
    val add2 = sc.parallelize(list2,3)
    add.zip(add2).collect().foreach(println)
    sc.stop()
  }
}

输出结果:

(30,40)
(50,70)
(70,20)
(60,20)
(10,2)
(20,4)
(30,7)

zipPartitions:
限制:只要两个rdd的分区数一样即可
从案例中很好的体现了zipPartitions是分区内部进行的zip

案例一:

object test01 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("zipPartitions").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30,50,10,70,60,40,10,20,30)
    val list2 = List(40,70,20,20,2,4)
    val add = sc.parallelize(list1,3)
    val add2 = sc.parallelize(list2,3)
    //每个分区内部进行zip
    //f: (Iterator[T], Iterator[B]) => Iterator[V]
    add.zipPartitions(add2)((it1,it2)=>{
      it1.zip(it2)
    }).collect().foreach(println)
    sc.stop()
  }
}

结果输出:

(30,40)
(50,70)
(70,20)
(60,20)
(10,2)
(20,4)

案例二:
zipAll(that: Iterator[B], thisElem: A1, thatElem: B1)
thisElem element thisElem is used to fill up the resulting iterator if the self iterator is shorter than that
thatElem element thatElem is used to fill up the resulting iterator if that is shorter than the self iterator

object zipPartitions {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30,50,70,60,10,20,30)
    val list2 = List(40,70,20,20,2,4)
    val add = sc.parallelize(list1,3)
    val add2 = sc.parallelize(list2,3)
    add.zipPartitions(add2)((it1,it2)=>{
      it1.zipAll(it2,1000,2000)
    }).collect().foreach(println)
    sc.stop()
  }
}

输出结果:

(30,40)
(50,70)
(70,20)
(60,20)
(10,2)
(20,4)
(30,2000)

zipWithIndex:

各元素与自身索引进行拉链

object zipWithIndex{
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("MapDemo").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30,50,70,60,10,20,30)
    val add = sc.parallelize(list1,3)
    add.zipWithIndex().collect().foreach(println)
    sc.stop()
  }
}

执行结果:

(30,0)
(50,1)
(70,2)
(60,3)
(10,4)
(20,5)
(30,6)

3.3 Key-Value类型

(1)partitionBy算子

将RDD(K,V)中的K按照指定Partitioner重新进行分区

HashPartitioner的分区机制:
源码:

//key为null时直接在第0分区,key不为null时,对取key的hashCode值
  def getPartition(key: Any): Int = key match {
    case null => 0
    case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
  }
//key不为null时,分区号=key的hashCode值的绝对值%分区数
  def nonNegativeMod(x: Int, mod: Int): Int = {
    val rawMod = x % mod
    rawMod + (if (rawMod < 0) mod else 0)
  }

案例一:

object partitionBy {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("partitionBy")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(10->"xiaobai",1->"xiaohong",11->"xiaohei"),2)
    //通过结果发现,重新分了4个分区
    rdd.partitionBy(new HashPartitioner(4)).mapPartitionsWithIndex{
      case (index,it)=>it.map(x=>{(index,x)})
    }.collect().foreach(println)
  }
}

结果输出:

(1,(1,xiaohong))
(2,(10,xiaobai))
(3,(11,xiaohei))

自定义Partitioner:

object MyPartitioner {
  def main(args: Array[String]): Unit = {
      val conf = new SparkConf().setMaster("local[*]").setAppName("MyPartitioner")
      val sc = new SparkContext(conf)
      val rdd = sc.parallelize(List(10 -> "xiaobai", 1 -> "xiaohong", 11 -> "xiaohei"), 3)
      rdd.partitionBy(new MyPartitioner(2)).mapPartitionsWithIndex{
        case (index,it)=>it.map(x=>{(index,x)})
      }.collect().foreach(println)

  }
}
class MyPartitioner(val num:Int) extends Partitioner{
  //重写分区数方法
  override def numPartitions: Int = num
  //重写分区规则方法
  override def getPartition(key: Any): Int = {if(key.hashCode() >5) 0 else 1}

  //防止多次调用同一分区器,造成多次shuffle,影响效率
  //override def hashCode(): Int = num
  override def equals(obj: Any): Boolean = {
    obj match {
      case other:MyPartitioner=>this.num==other.num
      case _ => false
    }
  }
}

结果输出:

(0,(10,xiaobai))
(0,(11,xiaohei))
(1,(1,xiaohong))

(2)reduceByKey算子

按照相同的key对value进行聚合

注意:reduceByKey(func: (V, V) => V): RDD[(K, V)]
传入函数的返回值类型与value类型一致,而scala中的reduce则没有这个限制

object reduceByKey {
  def main(args: Array[String]): Unit = {
      val conf = new SparkConf().setMaster("local[*]").setAppName("ReduceByKey")
      val sc = new SparkContext(conf)
      val rdd = sc.parallelize(List(1 -> "xiaobai", 1 -> "xiaohong", 11 -> "xiaohei",11 -> "xiao"), 2)
      val rdd2 = rdd.reduceByKey((x,y)=>x+"  "+y)
      rdd2.collect().foreach(println)
      sc.stop()
  }
}

结果输出:

(11,xiaohei  xiao)
(1,xiaobai  xiaohong)

(3)groupByKey算子

对每个key进行操作,但只生成一个seq,并不进行聚合;
可指定分区器或者分区数(默认使用HashPartitioner)

与reduceByKey的区别:
reduceByKey是按照key进行聚合,在shuffle之前有combin(预聚合)操作,返回结果是RDD[K,V]
groupByKey是按照key进行分组,直接进行shuffle,返回结果是:RDD[(K, Iterable[V])]

object groupByKey {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("GroupByKey")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(1 -> "xiaobai", 1 -> "xiaohong", 11 -> "xiaohei",11 -> "xiao"), 2)
    val rdd2 = rdd.groupByKey().collect().foreach(println)
  }
}

执行结果:

(11,CompactBuffer(xiaohei, xiao))
(1,CompactBuffer(xiaobai, xiaohong))

(4)aggregateByKey算子

按照K处理分区内与分区间逻辑
aggregateByKey[U: ClassTag] (zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U): RDD[(K, U)]

zeroValue:给每一个分区中的每一种key一个初始值
seqOp:函数用于每一个分区中初始值逐步迭代value
combOp:函数用于合并每个分区的结果
注意:初始值只在分区内给每个key赋初始值,分区间不会赋初始值

object aggregateBykey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("aggregateByKey").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    //需求:求出每个key对应的最大值与最小值
    val rdd1 = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 2), ("a", 7), ("a", 5), ("b", 2)), 2)
    rdd1.aggregateByKey(
      //初始值
      (Int.MinValue,Int.MaxValue))({
      //分区内函数
        case (((a,b),v))=>(v.max(a),v.min(b))
      },
      {
        //分区间函数
        case ((max1,min1),(max2,min2))=>(max1+max2,min1+min2)
      }
    ).collect().foreach(println)
  }
}

输出结果:

(b,(7,7))
(a,(9,6))

(5)foldByKey算子

分区与分区间相同逻辑

object foldByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("foldByKey").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val rdd1 = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 2), ("a", 7), ("a", 5), ("b", 2)), 2)
    rdd1.foldByKey(0)(_+_).collect().foreach(println)
    sc.stop()
  }
}

输出结果:

(b,7)
(a,15)

(6)combineByKey算子

转换结构后分区内和分区间操作
combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]

createCombiner:转换数据的结构
mergeValue:分区内逻辑函数
mergeCombiners:分区间逻辑函数

object combineByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("combineByKey").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    //需求:计算平均值  总和/个数
    val rdd1 = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 2), ("a", 7), ("a", 5), ("b", 2)), 2)
    val rdd2:RDD[(String,(Int,Int))] = rdd1.combineByKey(
      v => (v, 1),
      {
        case ((sum, count), v) => (sum + v, count + 1)
      },
      {
        case ((sum1, count1), (sum2, count2)) => (sum1 + sum2, count1 + count2)
      }
    )
    val rdd3 = rdd2.map {
      case (key, (sum, count)) => (key, sum.toDouble / count)
    }
      rdd3.collect().foreach(println)
      sc.stop()
  }
}

结果输出:

(b,3.5)
(a,3.75)

(7) combineByKey、aggregateByKey、foldByKey、reduceByKey算子之间的差异

combineByKey
    把第一个值变成特定结构
    分区内和分区间的聚合逻辑不一样   
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, defaultPartitioner(self))
aggregateByKey
    有零值
    分区内和分区间的聚合逻辑不一样
    combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),
      cleanedSeqOp, combOp, partitioner)
foldByKey
    有零值
    分区内和分区间的聚合逻辑一样
    combineByKeyWithClassTag[V]((v: V) => cleanedFunc(createZero(), v),
      cleanedFunc, cleanedFunc, partitioner)
reduceByKey
    没有零值
    分区内和分区间的聚合逻辑一样
    combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)

(8) sortByKey算子

sortByKey(ascending: Boolean = true, numPartitions: Int self.partitions.length) : RDD[(K, V)]
参数列表只有是否升序及分区数,因而它的功能还没有sortBy强大

object sortByKey {
  def main(args: Array[String]): Unit = {
      val conf: SparkConf = new SparkConf().setAppName("sortByKey").setMaster("local[*]")
      val sc: SparkContext = new SparkContext(conf)

      val rdd1 = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 2), ("a", 7), ("a", 5), ("b", 2)), 2)
    //按照key进行升序排序
    rdd1.sortByKey().collect().foreach(println)
    println("="*50)
    //按照key进行降序排序
    rdd1.sortByKey(false).collect().foreach(println)
    }
}

结果输出:

(a,1)
(a,2)
(a,7)
(a,5)
(b,5)
(b,2)
==================================================
(b,5)
(b,2)
(a,1)
(a,2)
(a,7)
(a,5)

(9) mapValues算子

针对(K,V)形式的类型只对V进行操作

object mapValue {
  def main(args: Array[String]): Unit = {
      val conf: SparkConf = new SparkConf().setAppName("mapValues").setMaster("local[*]")
      val sc: SparkContext = new SparkContext(conf)
      val rdd1 = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 2), ("a", 7), ("a", 5), ("b", 2)), 2)
      rdd1.mapValues(_*2).collect().foreach(println)
      sc.stop()
  }
}

结果输出:

(a,2)
(b,10)
(a,4)
(a,14)
(a,10)
(b,4)

(10) join算子

在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD

object join {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("JoinDemo").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 6, 10, 20, 30).map((_, "a"))
    val list2 = List(30, 70, 60, 1, 2, 30).map((_, "b"))
    val rdd1 = sc.parallelize(list1, 2)
    val rdd2 = sc.parallelize(list2, 2)
    //交集部分join
    val rdd3 = rdd1.join(rdd2).collect().foreach(println)
    println("="*50)
    //右外连接
    val rdd4 = rdd1.rightOuterJoin(rdd2).collect().foreach(println)
    println("="*50)
    //左外连接
    val rdd5 = rdd1.leftOuterJoin(rdd2).collect().foreach(println)
    println("="*50)
    //Option的两个子类Some,None
    rdd1.rightOuterJoin(rdd2).map{
      case (key,(Some(v),v2))=>(key,v,v2)
      case (key,(None,v))=>(key,10000,v)
    }.collect().foreach(println)
    sc.stop()
  }
}

结果输出:

(30,(a,b))
(30,(a,b))
(30,(a,b))
(30,(a,b))
(70,(a,b))
==================================================
(30,(Some(a),b))
(30,(Some(a),b))
(30,(Some(a),b))
(30,(Some(a),b))
(70,(Some(a),b))
(60,(None,b))
(2,(None,b))
(1,(None,b))
==================================================
(30,(a,Some(b)))
(30,(a,Some(b)))
(30,(a,Some(b)))
(30,(a,Some(b)))
(50,(a,None))
(6,(a,None))
(70,(a,Some(b)))
(20,(a,None))
(10,(a,None))
==================================================
(30,a,b)
(30,a,b)
(30,a,b)
(30,a,b)
(70,a,b)
(60,10000,b)
(2,10000,b)
(1,10000,b)

(11) cogroup算子

操作两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。

cogroup[W] (other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]

object cogroup {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("cogroup").setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    val list1 = List(30, 50, 70, 6, 10, 20, 30).map((_, "a"))
    val list2 = List(30, 70, 60, 1, 2, 30).map((_, "b"))
    val rdd1 = sc.parallelize(list1, 2)
    val rdd2 = sc.parallelize(list2, 2)
    rdd1.cogroup(rdd2).collect().foreach(println)
    sc.stop()
  }
}

结果输出:

(30,(CompactBuffer(a, a),CompactBuffer(b, b)))
(50,(CompactBuffer(a),CompactBuffer()))
(6,(CompactBuffer(a),CompactBuffer()))
(70,(CompactBuffer(a),CompactBuffer(b)))
(20,(CompactBuffer(a),CompactBuffer()))
(60,(CompactBuffer(),CompactBuffer(b)))
(10,(CompactBuffer(a),CompactBuffer()))
(2,(CompactBuffer(),CompactBuffer(b)))
(1,(CompactBuffer(),CompactBuffer(b)))

四、行动(Action)算子

4.1 reduce聚合

函数签名:reduce(f: (T, T) => T): T

聚集RDD中的所有元素,先聚合分区数据,再聚合分区间数据

4.2 collect

函数签名:collect(): Array[T]

在驱动程序中,以数组Array的形式返回数据集的所有元素
注意:所有的数据都会被拉到Driver端,慎用

4.3 count

函数签名:count(): Long
返回RDD中元素的个数

4.4 first

函数签名:first():T
返回RDD中的第一个元素

4.5 take

函数签名:take(num:Int):Array[T]
返回RDD中的前num个元素

4.6 takeOrdered

函数签名:takeOrdered(num:Int)(implit ord:Ordering[T]):Array[T]
返回该RDD排序后的前n个元素组成的数组

4.7 aggregate

换数签名:aggregate[U: ClassTag] (zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
aggregate函数将每个分区里面的元素通过分区内逻辑和初始值进行聚合,然后用分区间逻辑和初始值进行操作。

object aggregate {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("aggregate")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(1,10,2,4,5),2)
    val res1 = rdd.aggregate(0)(_+_,_+_)
    val res2 = rdd.aggregate(10)(_+_,_+_)
    println(res1)
    println(res2)
    sc.stop()
  }
}

结果输出:

22
52

4.8 fold

函数签名:fold(zeroValue: T)(op: (T, T) => T): T
aggregate的简化操作,即分区逻辑和分区间逻辑相同

object fold {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("fold")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(List(10,1,10,2,4,5),2)
    val res1 = rdd.fold(0)(_+_)
    val res2 = rdd.fold(100)(_+_)
    println(res1)
    println(res2)
    sc.stop()
  }
}

结果输出:

32
332

4.9 countByKey

函数签名:countByKey(): Map[K, Long]
统计每种key的个数

object countByKey {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("countByKey")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(10,20,23,20),2).map((_,"spark"))
    val res = rdd.countByKey()
    println(res)
    sc.stop()
  }
}

结果输出:

Map(20 -> 2, 10 -> 1, 23 -> 1)

4.10 save相关的算子

saveAsTextFile(path) 保存成Text文件
saveAsSequenceFile(path) 保存成Sequencefile文件,注意只有kv类型的RDD有该操作,单值的没有
saveAsObjectFile(path) 序列化成对象保存到文件

object save {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("save")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    //保存成Text文件
    //rdd.saveAsTextFile("output")

    //序列化成对象保存到文件
    //rdd.saveAsObjectFile("output1")

    //保存成SequenceFile文件
    rdd.map((_,1)).saveAsSequenceFile("output2")
    sc.stop()
  }
}

执行结果图:

在这里插入图片描述

4.11 foreach

函数签名:foreach[U](f: A => U): Unit
遍历RDD中的每一个元素,并依次应用f函数

//分布式打印
rdd.foreach(println)
//收集后打印
rdd.collect().foreach(println)

4.12 foreachPartitions

应用场景:减少了连接的次数,用foreach的话每条数据得建立一个连接。例如:一亿条数据建立一亿次连接,假设连接MySQL数据库,那数据库估计就崩了。

object ForeachDemo {
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setAppName("ForeachDemo").setMaster("local[2]")
        val sc: SparkContext = new SparkContext(conf)
        val list1 = List(30, 50, 70, 60, 10, 20)
        val rdd1: RDD[Int] = sc.parallelize(list1, 3)
        // 一亿条
    
        /*rdd1.foreach(x => {
            // 建立连接
            
            // 使用连接写数据
            // 关闭连接
        })*/
        rdd1.foreachPartition((it: Iterator[Int]) => {
            println("foreachPartition....")
            // 建立连接
            it.foreach(x => {
                // 写出去
            })
            // 关闭连接
        })
        sc.stop()
    }
}

五、RDD序列化

5.1 为什么要序列化

初始化工作是在Driver端进行的,而实际运行的程序是在Executor端进行的,这里面就涉及到跨进程通信,是需要序列化的。

***Driver:***算子以外的代码都是在Driver端执行
***Executor:***算子里面的代码都是在Executor端执行

函数序列化:

object serializable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("serializable")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark world","hadoop","hello world","spark world"))
    val search = new Search("world")
    search.getMatch(rdd).collect().foreach(println)
  }
}
//继承了Serializable,不继承就会报Task not serializable
class Search(query:String) extends Serializable {
  def isMatch(s:String):Boolean={
    s.contains(query)
  }
  def getMatch(rdd:RDD[String])={
    rdd.filter(isMatch)
  }
}

结果输出:

spark world
hello world
spark world

属性序列化处理方式一:

object serializable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("serializable")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark world","hadoop","hello world","spark world"))
    val search = new Search("world")
    search.getMatch(rdd).collect().foreach(println)
  }
}
class Search(query:String) extends Serializable{
  def isMatch(s:String):Boolean={
    s.contains(query)
  }
  def getMatch(rdd:RDD[String])={
  //直接传入匿名函数,但是由于用了Search类的属性query,所以也是需要序列化,否则会报Task not serializable
    rdd.filter(_.contains(query))
  }
}

结果输出:

spark world
hello world
spark world

属性序列化处理方式二:

object serializable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("serializable")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark world","hadoop","hello world","spark world"))
    val search = new Search("world")
    search.getMatch(rdd).collect().foreach(println)
  }
}
class Search(query:String){
  def isMatch(s:String):Boolean={
    s.contains(query)
  }
  def getMatch(rdd:RDD[String])={
    //将类变量赋值给局部变量
    val s = query
    rdd.filter(_.contains(s))
  }
}

结果输出:

spark world
hello world
spark world

将Search类变成样例类,样例类默认是序列化的

object serializable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("serializable")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark world","hadoop","hello world","spark world"))
    val search = new Search("world")
    search.getMatch(rdd).collect().foreach(println)
  }
}
case class Search(query:String){
  def isMatch(s:String):Boolean={
    s.contains(query)
  }
  def getMatch(rdd:RDD[String])={
    rdd.filter(isMatch)
  }
}

结果输出:

spark world
hello world
spark world

5.2 Kryo序列化框架

Java的序列化能够序列化任何类。但是比较重,这显然是不符合spark的性能要求。而kryo的速度是Java序列化的10倍,值得注意的是,即使使用kryo序列化,也要继承Serializable接口。

object kroy {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName("kroy")
      //替换默认的序列化机制
      .set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
      //注册需要使用的kroy序列化的自定义类
      .registerKryoClasses(Array(classOf[Search]))
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("spark world","hadoop","hello world","spark world"))
    val search = new Search("world")
    search.getMatch(rdd).collect().foreach(println)
    sc.stop()
  }
}
case class Search(q:String){
  def isMatch(s:String):Boolean={
    s.contains(q)
  }
  def getMatch(rdd:RDD[String])={
    rdd.filter(isMatch)
  }
}

输出结果:

spark world
hello world
spark world

六、RDD依赖关系

6.1 查看血缘关系

RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

查看方式:toDebugString方法查看RDD血缘关系

6.1 查看依赖关系

通常分两类;
宽依赖:宽依赖表示同一个父RDD的Partition被多个子RDD的Partition依赖,会引起Shuffle,总结:宽依赖我们形象的比喻为超生。例如:ShuffleDependency
窄依赖:窄依赖表示每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女。例如:OneToOneDependency等

具有宽依赖的transformations有哪些呢?

6.1 Stage任务的划分*

DAG有向无环图:有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。例如,DAG记录了RDD的转换过程和任务的阶段。

RDD任务切分中间分为:Application、Job、Stage、Task

  • Application:初始化一个SparkContext即生成一个Application
  • Job:一个Action算子就会生成一个Job
  • Stage:Stage等于宽依赖的个数加1
  • Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数

七、持久化

7.1 RDD Cache缓存

Cache缓存之前:

object cache {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("cache")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    val rdd1 = rdd.map(x => {
      println("map--")
      x + 100
    }).filter(x=>{
      println("filter>>")
      true}
    )
    println(rdd1.toDebugString)
    //数据缓存
    //rdd1.cache()

    //可以更改储存级别:内存和磁盘,持久序列化2份;通常使用默认的只保存在内存中
    //rdd1.persist(StorageLevel.MEMORY_AND_DISK_SER_2)

    //触发执行
    rdd1.collect()

    println("="*20)
    println(rdd1.toDebugString)
    //再次触发执行
    rdd1.collect()

    Thread.sleep(1000000000)
    sc.stop()
  }
}

结果输出:

(2) MapPartitionsRDD[2] at filter at cache.scala:14 []
 |  MapPartitionsRDD[1] at map at cache.scala:11 []
 |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 []
map--
map--
filter>>
map--
filter>>
filter>>
map--
filter>>
====================
(2) MapPartitionsRDD[2] at filter at cache.scala:14 []
 |  MapPartitionsRDD[1] at map at cache.scala:11 []
 |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 []
map--
filter>>
map--
filter>>
map--
filter>>
map--
filter>>

Cache缓存之后:

object cache {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("cache")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    val rdd1 = rdd.map(x => {
      println("map--")
      x + 100
    }).filter(x=>{
      println("filter>>")
      true}
    )
    println(rdd1.toDebugString)
    //数据缓存
    rdd1.cache()

    //可以更改储存级别:内存和磁盘,持久序列化2份;通常使用默认的只保存在内存中
    //rdd1.persist(StorageLevel.MEMORY_AND_DISK_SER_2)

    //触发执行
    rdd1.collect()

    println("="*20)
    println(rdd1.toDebugString)
    //再次触发执行
    rdd1.collect()

    Thread.sleep(1000000000)
    sc.stop()
  }
}

结果输出:

(2) MapPartitionsRDD[2] at filter at cache.scala:14 []
 |  MapPartitionsRDD[1] at map at cache.scala:11 []
 |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 []
map--
filter>>
map--
filter>>
map--
filter>>
map--
filter>>
====================
(2) MapPartitionsRDD[2] at filter at cache.scala:14 [Memory Deserialized 1x Replicated]
 |       CachedPartitions: 2; MemorySize: 48.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 |  MapPartitionsRDD[1] at map at cache.scala:11 [Memory Deserialized 1x Replicated]
 |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 [Memory Deserialized 1x Replicated]

spark自动会对一些shuffle操作的中间数据做持久化(如:groupBy)
这样做的目的是为了当一个节点Shuffle失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist或cache。

object cache {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("cache")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    val rdd1 = rdd.map(x => {
      println("map--")
      x + 100
    }).groupBy(x=>{
      println("groupBy")
      x})
    println(rdd1.toDebugString)
    //数据缓存
    //rdd1.cache()

    //可以更改储存级别:内存和磁盘,持久序列化2份;通常使用默认的只保存在内存中
    //rdd1.persist(StorageLevel.MEMORY_AND_DISK_SER_2)

    //触发执行
    rdd1.collect()

    println("="*20)
    println(rdd1.toDebugString)
    //再次触发执行
    rdd1.collect()

    Thread.sleep(1000000000)
    sc.stop()
  }
}

结果输出:

(2) ShuffledRDD[3] at groupBy at cache.scala:14 []
 +-(2) MapPartitionsRDD[2] at groupBy at cache.scala:14 []
    |  MapPartitionsRDD[1] at map at cache.scala:11 []
    |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 []
map--
map--
groupBy
groupBy
map--
map--
groupBy
groupBy
====================
(2) ShuffledRDD[3] at groupBy at cache.scala:14 []
 +-(2) MapPartitionsRDD[2] at groupBy at cache.scala:14 []
    |  MapPartitionsRDD[1] at map at cache.scala:11 []
    |  ParallelCollectionRDD[0] at makeRDD at cache.scala:10 []

7.2 RDD CheckPoint检查点

  • 检查点:是通过将RDD中间结果写入磁盘。
  • 为什么要做检查点?
    由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
  • 检查点存储路径:Checkpoint的数据通常是存储在HDFS等容错、高可用的文件系统
  • 检查点数据存储格式为:二进制的文件
  • 检查点切断血缘:在Checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除。
  • 检查点触发时间:对RDD进行Checkpoint操作并不会马上被执行,必须执行Action操作才能触发。但是检查点为了数据安全,会从血缘关系的最开始执行一遍。

设置检查点步骤:

  1. 设置检查点数据存储路径:sc.setCheckpointDir("./checkpoint1")
  2. 调用检查点方法:wordToOneRdd.checkpoint()

案例:

object CheckPoint {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("checkPoint")
    val sc = new SparkContext(conf)
    //设置检查点路径
    sc.setCheckpointDir("checkpoint1")
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    val rdd1 = rdd.map(x => {
      println("map-----")
      (x + 100, System.currentTimeMillis())
    })
    //针对rdd1做检查点计算
    rdd1.checkpoint()

    //触发执行逻辑
    rdd1.collect()
    println("="*20)
    //再次触发执行逻辑
    rdd1.collect()
    Thread.sleep(1000000000)
    sc.stop()
  }
}

结果输出:

map-----
map-----
map-----
map-----
map-----
map-----
map-----
map-----
====================

通过输出结果我们可以看出,checkpoint是单独一个job,因为检查点为了数据安全,会从血缘关系的最开始执行一遍。并且Checkpoint操作并不会马上被执行,必须执行Action操作才能触发。

Spark查看当前运行任务情况:

在这里插入图片描述

7.3 缓存与检查点的区别

1、Cache缓存只是将数据保存起来,不切断血缘依赖。Checkpoint检查点切断血缘依赖。
2、 Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。
3、建议对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,否则需要再从头计算一次RDD。
4、如果使用完了缓存,可以通过unpersist()方法释放缓存

7.4 检查点存储到HDFS集群

object HDFS {
  def main(args: Array[String]): Unit = {
    //设置访问HDFS集群的用户名
    System.setProperty("HADOOP_USER_NAME","yilei")
    val conf = new SparkConf().setMaster("local[*]").setAppName("checkPointToHDFS")
    val sc = new SparkContext(conf)
    //设置检查点路径
    sc.setCheckpointDir("hdfs://hadoop102:9820/checkpoint")
    val rdd = sc.makeRDD(List(10,11,20,10),2)
    val rdd1 = rdd.map(x => {
      (x + 100, System.currentTimeMillis())
    })
    //增加缓存,避免再跑一个job
    rdd1.cache()
    //针对rdd1做检查点计算
    rdd1.checkpoint()
    //触发执行逻辑
    rdd1.collect().foreach(println)
    //释放缓存
    rdd1.unpersist()
    //关闭连接
    sc.stop()
  }
}

执行结果:HDFS上生成相应文件

(110,1604071036130)
(111,1604071036131)
(120,1604071036130)
(110,1604071036130)

八、数据读取与保存

从文件格式分为:Text文件,json文件,CSV文件,Sequence文件以及Object文件
文件系统分为:本地文件系统,HDFS以及数据库

8.1 Text文件

数据读取:textFile(String)
数据保存:saveAsTextFile(String)

object TextFile {
  def main(args: Array[String]): Unit = {
    System.setProperty("HADOOP_USER_NAME","atguigu")
    val conf = new SparkConf().setMaster("local[*]").setAppName("TextFile")
    val sc = new SparkContext(conf)
    val inputRDD = sc.textFile("output")
    inputRDD.saveAsTextFile("hdfs://hadoop102:9820/txt")
    sc.stop()
  }
}

8.2 Sequence文件

SequenceFile文件是hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat File)

object Operate_Sequence {

    def main(args: Array[String]): Unit = {

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 创建rdd
        val dataRDD: RDD[(Int, Int)] = sc.makeRDD(Array((1,2),(3,4),(5,6)))

        //3.2 保存数据为SequenceFile
        dataRDD.saveAsSequenceFile("output")

        //3.3 读取SequenceFile文件
        sc.sequenceFile[Int,Int]("output").collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}

8.3 Object对象文件

对象文件是将对象序列化后保存的文件,采用java的序列化机制

object ObjectFile {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3.1 创建RDD
    val dataRDD: RDD[Int] = sc.makeRDD(Array(1,2,3,4))

    //3.2保存数据
    dataRDD.saveAsObjectFile("output11")

    //3.3读取数据
    sc.objectFile("output11").collect().foreach(println)

    //4.关闭连接
    sc.stop()
  }
}

结果输出:

1
2
3
4

九、累加器

累加器:分布式共享只写变量,Executor与Executor之间不能读取数据
累加器解决问题的方式:是将Executor端变量信息聚合到Driver端。我们都知道在Driver端定义的变量,在Executor端的每个task都会得到这个变量的一份新的副本,但是每个task更新这个副本之后,你怎样将每个新的副本的值传回给Driver端呢?这个时候我们往往会用到累加器,累加器就做到了将新副本的值传给Driver端,并在Driver端进行merge。
主要作用: 减少shuffle的数量,提高运行速度,优化性能


案例一:利用系统自带的累加器
object accumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("accumulator")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)),2)
    //通常情况通过reduceByKey进行聚合,那么就一定经过了shuffle过程
    val rdd1 = rdd.reduceByKey(_+_)
    rdd1.foreach(println)

    println("="*20)
    //不经过shuffle那么该怎么做呢?
    var sum = 0
    val rdd2 = rdd.foreach(x => {
      sum += x._2
      println(sum)
    })
    println("="*20)
    println(sum)
    sc.stop()
  }
}

运行结果:

(a,10)
====================
1
3
3
7
====================
0

通过结果我们会发现,sum输出为零。说明Executor运行的结果并没有返回给Driver端。

下面引入系统累加器:

object accumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("accumulator")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)),2)
    //通常情况通过reduceByKey进行聚合,那么就一定经过了shuffle过程
    val rdd1 = rdd.reduceByKey(_+_)
    rdd1.foreach(println)
    
    println("="*20)
    //不经过shuffle那么该怎么做呢?
    //声明累加器
    val sum = sc.longAccumulator("longAcc")
    val rdd2 = rdd.foreach(x => {
      //使用累加器
      sum.add(x._2)
      //不在Driver端使用累加器的值
      println(sum.value)
    })
    //Driver端使用累加器的值
    println("="*20)
    println(sum.value)
    sc.stop()
  }
}

结果输出:

(a,10)
====================
1
3
3
7
====================
10

自定义累加器:
步骤:

  1. 继承AccumulatorV2,设定输入即输出泛型
  2. 重写方法

自定义一个以H开头的wordCount累加器

class MapAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
  val map = mutable.Map[String,Long]()
  //判断集合是空的
  override def isZero: Boolean = map.isEmpty

  //复制累加器
  override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
    val accmap = new MapAccumulator
    accmap.map++=this.map
    accmap
  }

  //重置累加器
  override def reset(): Unit = map.clear()

  //往累加器中添加数据
  override def add(v: String): Unit = {
    if(v.startsWith("H")){
      map(v)=map.getOrElse("v",0L)+1
    }
  }

  //合并累加器
  override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
    other.value.foreach{
      //模式匹配,对other中的每个元素进行匹配
      case(word,count)=>{
        //将匹配上的元素添加到map集合中
        map(word)=map.getOrElse(word,0L)+count
      }
    }
  }

  //获取累加器中的值
  override def value: mutable.Map[String, Long] = map
}
object MyAccumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("MyAccumulator")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("Hadoop","spark kafka","Hadoop","Hello"))
    //创建累加器
    val mapAccumulator = new MapAccumulator
    //注册累加器
    sc.register(mapAccumulator,"WordCount")
    rdd.foreach(word=>{
      //使用累加器
      mapAccumulator.add(word)
    })
    //获取累加器的值
    println(mapAccumulator.value)
    sc.stop()
  }
}

结果输出

Map(Hadoop -> 2, Hello -> 1)

十、广播变量

在这里插入图片描述
广播变量主要解决的是大变量读的问题,减少内存的占用,起到优化的效果
广播变量广播到每个executor上, 而不用每个task一个

案例:

object broadcast {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("broadcast")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("WARN:Class Not Find", "INFO:Class Not Find", "DEBUG:Class Not Find"), 4)
    val list:String="WARN"
    //声明广播变量
    val bd = sc.broadcast(list)
    val res = rdd.filter(str => {
      //引用广播变量
      str.contains(bd.value)
    })
    res.foreach(println)
    sc.stop()
  }
}

结果输出:

WARN:Class Not Find

总结

累加器及广播变量是我们关注的重点,涉及到优化问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值