Spark学习-架构和RDD

spark的架构角色

Master角色, 管理整个集群的资源

Worker角色, 管理单个服务器的资源

Driver角色, 单个Spark任务在运行的时候的工作

Executor角色,单个任务运行的时候的工作者

请添加图片描述

spark的StandAlone模式

原理

Master和Worker角色以独立进程的形式存在,并组成Spark运行时环境(集群)

Spark角色分布

Master:

Master进程

Worker:

worker进程

Driver:

以线程运行在Master中

Executor:

以线程运行在Worker中

如何提交Spark应用

bin/spark-submit --master spark://hadoop102:7077

4040端口

是单个程序运行的时候绑定的端口可供查看本任务运行情况

8080端口

Master端口

18080端口

历史服务器端口

job、Stage、Task的关系

一个Spark程序会被分成多个子任务job运行,每个job会分成多个stage来运行,每一个Stage内会分出多个线程来执行具体的任务

StandAlone HA原理

​ 基于zookeeper做状态的维护,开启多个Master进程,一个作为活跃,其他的作为备份,当活跃宕机,备份Master进行接管

Spark on Yarn

本质

Master角色由YARN的ResourceManager担任

Worker角色由YARN的NodeManager担任

Driver角色运行在YARN容器内 或 提交任务的客户端进程中

Executor运行在YARN提供的容器内

请添加图片描述

部署模式

Cluster模式

Driver运行在容器内部

性能高

请添加图片描述

Client模式

Driver运行在客户端程序进程中

可以在客户端直接看输出

请添加图片描述
请添加图片描述

Why on YARN

​ 提高资源利用率,在已有YARN的场景下让Spark收到YARN的调度可以更好的管控资源提高利用率

​ ResourceManager(资源)和Driver(计算)之间的解耦合靠的就是ApplicationMaster。

Spark-RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。

请添加图片描述

  • RDD有装饰者设计模式
  • RDD的数据处理方式类似于IO流,而且是懒加载,调用collect才会执行业务逻辑操作
  1. 弹性

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

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

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

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

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

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

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

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

  6. 可分区、并行计算

核心属性

分区列表

RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性

分区计算函数

Spark在计算时,是使用分区函数对每一个分区进行计算

RDD 之间的依赖关系

RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系

分区器

当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区

首选位置

计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

RDD创建

从内存中创建

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

makeRDD方法在底层实现时其实就是调用了rdd对象的parallelize方法

package com.topview.bigdata.spark.core.rdd.builder

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

object rdd_01_Memory {

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

    // TODO 准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    // TODO 创建环境
    //从内存中创建RDD, 将内存中集合的数据作为处理的数据源
    val seq = Seq[Int](1,2,3,4)

    //parallelize 并行
//    sc.parallelize(seq).collect().foreach(println)
    //makeRDD方法在底层实现时其实就是调用了rdd对象的parallelize方法
    sc.makeRDD(seq).collect().foreach(println)
    // TODO 关闭环境
    sc.stop()
  }
}

从文件中创建

sc.textfile( path )

可以写绝对路径和相对路径

也可以写目录,读取目录下所有文件,也可以用通配符 *

package com.topview.bigdata.spark.core.rdd.builder

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

object rdd_02_file {

  def main(args: Array[String]): Unit = {
    val sparConf = new SparkConf().setMaster("local").setAppName("wordCount")
    val sc = new SparkContext(sparConf)

    //可以写绝对路径和相对路径
    val rdd1 = sc.textFile("E:\\JavaProject\\topview-classes\\input\\word.txt")
    val rdd2 = sc.textFile("input/word.txt")

//    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
      
   // 通配符路径
    //  input/w*.txt

    sc.stop()
  }
}

从文件读取数据并且识别数据从哪来

sc.wholeTextFiles

sc.wholeTextFiles("input/word.txt").
collect().
foreach(println)

结果(是一个元组,第一个元素是路径)

(file:/E:/JavaProject/topview-classes/input/word.txt,hello hello word
word
spark 黄国杰
黄国杰 黄国杰)

分区

分区的设定

从内存中读取

​ makeRDD方法可以传递第二个参数,这个参数表示分区的数量
​ 第二个参数不传则用默认值 :
​ 从配置对象中获取配置参数:spark.default.parallelism,可以在sparkconf设置
如果获取不到就是用当前运行环境的最大可用核数

package com.topview.bigdata.spark.core.rdd.builder

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

object rdd_03_Memory_Par {

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

    // TODO 准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")

//    sparkConf.set("spark.default.parallelism","5")

    val sc = new SparkContext(sparkConf)

    // TODO 创建环境
    //RDD的并行度和分区
    //makeRDD方法可以传递第二个参数,这个参数表示分区的数量
    //第二个参数不传则用默认值 :
    //从配置对象中获取配置参数:spark.default.parallelism
    //如果获取不到就是用当前运行环境的最大可用核数

    val rdd = sc.makeRDD(List(1, 2, 3, 4))

    //将处理的数据保存为分区文件
    rdd.saveAsTextFile("output")
    // TODO 关闭环境
    sc.stop()
  }
}
从文件中读取(重点)

​ 读取文件数据时,数据是按照Hadoop 文件读取的规则进行切片分区,而切片规则和数据读取的规则有些差异,具体看代码

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

object rdd_02_file_par {

  def main(args: Array[String]): Unit = {
    val sparConf = new SparkConf().setMaster("local").setAppName("wordCount")
    val sc = new SparkContext(sparConf)

    //textFile默认也可以设定分区
    //  minPartition : 最小分区数量
    // 源码中 math.min(defaultParallelism, 2) defaultParallelism是16个
    // 默认两个
    //也可以通过第二个参数指定分区数
    //spark读取文件,底层使用的是Hadoop的读取方式,注意不是分区方式
    //分区数量的计算方式
    //假设文件总大小 totalSize = 7
    //底层计算 goalSize = 7 / 2(设定的分区数) = 3(byte) 即是每个分区放3byte
    
    //根据Hadoop的1.1倍
    //7 / 3 = 2 .... 1(1.1) + 1 = 3分区
    //所以最后是三分区,而不是设定的二分区
    sc.textFile("input/word.txt",2).collect().foreach(println)

    sc.stop()
  }
}

分区数据的分配

1. 数据以行为单位进行读取
spark读取文件,底层使用的是Hadoop的读取方式,所以是一行一行读取,和字节数没有关系
2. 数据读取时以偏移量为单位,偏移量不会被重新读取
3. 数据分区的偏移量范围
4. 如果数据源为多个文件,那么计算分区时以文件为单位进行分区

RDD方法

转换算子: 功能的补充和封装,将旧的RDD包装成新的RDD

行动算子: 触发任务的调度和作业的执行

RDD方法 =>算子

转换算子

​ RDD 根据数据处理方式的不同将算子整体上分为Value 类型、双 Value 类型和Key-Value类型

Value类型
map

​ 将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。

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

object rdd_operator_Transform {

  def main(args: Array[String]): Unit = {
    // TODO 准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd = sc.makeRDD(List(1, 2, 3, 4))

    //转换函数
//    def mapFunction(num:Int) : Int = {
//      num*2
//    }
//
//    rdd.map(mapFunction).collect().foreach(println)

    //简化
    rdd.map((num:Int)=>{num*2}).collect().foreach(println)
    //一行代码,花括号可以省略
    rdd.map((num:Int)=>num*2).collect().foreach(println)
    //类型推断,类型省略
    rdd.map((num)=>num*2).collect().foreach(println)
    //参数只有一个,括号省略
    rdd.map(num=>num*2).collect().foreach(println)
    //参数只出现一次,参数省略
    rdd.map(_*2).collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}

map计算并行计算演示

1.rdd的计算一个分区内的数据是一个一个执行逻辑

​	一个分区只有前面的数据执行完才到下一个,这点和flink不一样

​	分区内的数据执行是有序的


2. 不同分区数据计算是无序的
mapPartitions

​ 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据

  • ​ 可以以分区为单位进行数据转换操作
  • ​ 但是会将整个分区的数据加载到内存进行引用
  • ​ 如果处理完的数据是不会被释放掉,存在对象的引用
  • ​ 内存较小,数据量较大的场合下,容易出现内存溢出
import org.apache.spark.{SparkConf, SparkContext}

object rdd_operator_Transform02 {

  def main(args: Array[String]): Unit = {
    // TODO 准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd = sc.makeRDD(List(1, 2, 3, 4),2)

    // TODO 算子 - mapPartitions
    //一次性把分区的数据拿过来
    //mapPartitions :可以以分区为单位进行数据转换操作
    //                但是会将整个分区的数据加载到内存进行引用
    //                如果处理完的数据是不会被释放掉,存在对象的引用
    //                内存较小,数据量较大的场合下,容易出现内存溢出
    rdd.mapPartitions(
      iter => {
        println(">>>>>>>>>>>>>")
        iter.map(_*2)
      }
    ).collect().foreach(println)
    // TODO 关闭环境
    sc.stop()
  }
}

​ mapPartitions可以获取每个数据分区的最大值

rdd.mapPartitions(
      iter => {
          //要求返回迭代器,所以包成迭代器
        List(iter.max).iterator
      }
    )
map和mapPartitions的区别
数据处理角度

Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。

功能的角度

Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据

性能的角度

Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处
理,所以性能较高。但是mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
mapPartitionsWithIndex

​ 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引

val RDD1 = RDD.mapPartitionsWithIndex( (index, datas) => {
	if( index == 1){
        //保存第二个分区
        iter
    } else {
        Nil.iterator
    }
  }
)

mapPartitionsWithIndex可以获取第二个数据分区的数据

flatmap

将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射

package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_operator_Transform03 {

  def main(args: Array[String]): Unit = {
    // TODO 准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd:RDD[List[Int]] = sc.makeRDD(List(
      List(1, 2),List(3,4)
    ))

    // TODO 算子 - flatMap

    val value = rdd.flatMap(list => {
      list
    })

    value.collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}

flatmap可以 将 List(List(1,2),3,List(4,5))进行扁平化操作,只需要用模式匹配,将不是集合的值变成集合

glom

​ 将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

val dataRDD = sparkContext.makeRDD(List( 1,2,3,4),1)

val dataRDD1:RDD[Array[Int]] = dataRDD.glom()

小功能:计算所有分区最大值求和(分区内取最大值,分区间最大值求和)

groupBy

​ 将数据根据指定的规则进行分组, 分区默认不变

​ 极限情况下,数据可能被分在同一个分区中,一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

​ 注意:分组和分区没有必然的关系

​ groupBy会将数据打乱,重新组合,这个操作我们称之为shuffle

​ 有可能会导致数据倾斜

package com.topview.bigdata.spark.core.operator.transform

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

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

    val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    // TODO 算子 - glom

    //  groupBy会将数据源中的每一个数据进行分组判断
    //  相同的key值的数据会放置在一个组

    rdd.groupBy(num => num%2).collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}
filter
  • ​ 将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
  • ​ 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
package com.topview.bigdata.spark.core.operator.transform

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

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

    val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    // TODO 算子 - glom
    rdd.filter(_%2!=0).collect().foreach(println)
    // TODO 关闭环境
    sc.stop()
  }
}
sample

​ 根据指定的规则从数据集中抽取数据,需要传三个参数,具体看代码

​ 可以解决数据倾斜,抽取分区的值,看看数据里的哪个值多,然后进行优化

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

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

    val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6,7,8,9,0),2)

    // TODO 算子 - sample

    //sample算子需要传递三个参数
    //1. 第一个参数: 抽取数据后是否将数据返回 true返回 flase丢弃
    //2. 第二个参数: 数据源中每条数据被抽取的概率
            //基准值的概念
    //3. 第三个参数: 抽取数据时随机算法的种子
            //不传第三个参数,使用系统时间
    println(rdd.sample(
      true,
      0.4,
//      1
    ).collect().mkString(","))
    // TODO 关闭环境
    sc.stop()
  }
}
distinct

将数据集中重复的数据去重

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

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

      val rdd: RDD[Int] = sc.makeRDD(List(1, 3, 3, 4, 5, 5, 7, 8, 9, 0), 2)

      // TODO 算子 - distinct

      println(rdd.distinct().collect().mkString(","))

      // TODO 关闭环境
      sc.stop()
    }
  }

//结果 
4,0,8,1,3,7,9,5
coalesce

根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率

当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本

第一个参数设置分区数,第二个参数是否shuffle

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

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

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 3)

    // TODO 算子 - coalesce

    //coalesce方法默认情况不会将分区的数据打乱重新组合
    //这种情况下的缩减分区可能会导致数据不均衡, 出现数据倾斜
    //如果想让数据集合均衡,可以设置第二个参数进行shuffle
    rdd.coalesce(2, true).saveAsTextFile("output")
    // TODO 关闭环境
    sc.stop()
  }
}
repartition

​ 该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true

​ 无论是将分区数多的RDD 转换为分区数少的RDD,还是将分区数少的 RDD 转换为分区数多的RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。

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

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

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 3)

    // TODO 算子 - reparation
    //扩大分区一定要shuffle
    rdd.repartition(5).saveAsTextFile("output1")
    // TODO 关闭环境
    sc.stop()
  }
}
sortBy

​ 该操作用于排序数据。 在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原RDD 的分区数一致。中间存在 shuffle 的过程

​ 第二个参数可以改变排序的方式

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

object rdd_operator_Transform10 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)
//1, 2, 3, 4, 5, 6, 7, 8, 9, 0
    val rdd: RDD[Int] = sc.makeRDD(List(5,3,6,7,8,2,34), 3)

    // TODO 算子 - sortBy
    println(rdd.sortBy(
        num => num, false).collect().mkString(","))

    // TODO 关闭环境
    sc.stop()
  }
}

结果:
2,3,5,6,7,8,34
双Value类型
  • zip不要求两个数据源类型一致,其他需要一样
  • 两个数据源要求分区数量要保持一致
  • 分区中数据数量保持一致
intersection

​ 对源RDD 和参数RDD 求交集后返回一个新的RDD

union

​ 对源RDD 和参数RDD 求并集后返回一个新的RDD

subtract

​ 以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来

zip

​ 将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。

​ 不要求两个数据源类型一致,其他需要一样

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

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

    // TODO 算子 - 双Value
    val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))

    //  交集
    println(rdd1.intersection(rdd2).collect().mkString(", "))
    //  并集
    println(rdd1.union(rdd2).collect().mkString(", "))
    //  差集
    println(rdd1.subtract(rdd2).collect().mkString(", "))
    //  拉链
    println(rdd1.zip(rdd2).collect().mkString(", "))
    // TODO 关闭环境
    sc.stop()
  }
}
key-value类型
partitionBy

​ 将数据按照指定Partitioner 重新进行分区。Spark 默认的分区器是HashPartitioner

​ 如果重分区的分区器和当前RDD 的分区器一样,则返回自己

​ spark可以用两个分区器 : HashPartitioner和RangePartitioner

RangePartitioner一般用在排序,sortBy底层运用到了

//HashPartitioner底层原理
//x % mod 
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

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

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4), 2)

    // TODO 算子 - key-value
    //HashPartitioner底层原理
    //x % mod
    rdd.map((_,1)).partitionBy(new HashPartitioner(2)).saveAsTextFile("output")

    // TODO 关闭环境
    sc.stop()
  }
}
reduceByKey

可以将数据按照相同的Key 对Value 进行聚合

reduceByKey中key的数据只有一个,是不会参与运算的

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

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

    val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("b", 4)),2)

    // TODO 算子 - key-value
    //reduceByKey中key的数据只有一个,是不会参与运算的
    println(rdd.reduceByKey(_ + _).collect().mkString(","))

    // TODO 关闭环境
    sc.stop()
  }
}
groupByKey

​ 将数据源中的数据,相同key的数据分在一个组中, 形成一个对偶元组

​ 元组第一个元素就是key

​ 元组第二个元素就是相同key的value集合

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey() val dataRDD3 = dataRDD1.groupByKey(2)
val dataRDD4 = dataRDD1.groupByKey(new HashPartitioner(2))
reduceByKey和groupByKey的区别

​ 首先,spark中,shuffle操作必须落盘处理,不能再内存中数据等待,会导致内存溢出.shuffle性能很低.

shuffle角度

reduceByKey 和 groupByKey 都存在 shuffle 的操作

  • reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,可以减少落盘的数据量(reduceByKey分区内和分区间计算规则是相同的)
  • groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高
功能角度

reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合.

  • ​ 分组聚合的场合下,推荐使用 reduceByKey
  • ​ 分组而不需要聚合。那么还是只能使用groupByKey
aggregateByKey

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

 aggregateByKey存在函数柯里化, 有两个参数列表
第一个参数列表需要传递一个参数, 表示初始值
    主要用于碰见第一个key的时候,和value进行分区计算
    要是没有值大过初始值,那么会用到初始值
第二个参数列表需要传递两个参数
    第一个参数表示分区内计算规则
    第二个参数表示分区间计算规则
package com.topview.bigdata.spark.core.operator.transform

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

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

    val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)

    
    rdd.aggregateByKey(0)(
        //表示分区内计算规则
      (a, b) => math.max(a , b),
        //表示分区间计算规则
      (a, b) => a + b
    ).collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}

foldByKey

​ 当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey

​ 和reduceByKey比多了初始值

package com.topview.bigdata.spark.core.operator.transform

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

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

    val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)

    // TODO 算子 - key-value
    //当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey
    //和reduceByKey多了初始值
    rdd.foldByKey(0)(_+_).collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}
combineByKey

​ 对key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。

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

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

    val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)

    // TODO 算子 - key-value
    //第一个参数: 将相同key的第一个数据进行结构的转换, 实现操作
    //第二个参数: 分区内的计算规则
    //第三个参数: 分区内的计算规则


    rdd.combineByKey(
      v => (v, 1),
      ( t:(Int,Int), v) => {
        (t._1 + v, t._2 + 1)
      },
      ( t1:(Int,Int), t2:(Int,Int) )=>{
        (t1._1 + t2._1, t1._2 + t2._2)
      }
    ).collect().foreach(println)

    // TODO 关闭环境
    sc.stop()
  }
}
sortByKey

​ 在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序的

join

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

  • ​ 两个不同的数据源的数据,相同的key的value会连接在一起,形成元组
  • ​ 如果两个数据源中key没有匹配上,那么数据不会出现在结果中
  • ​ 如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔乘积,数据量几何倍增长,性能降低
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6))) rdd.join(rdd1).collect().foreach(println)
leftOuterJoin和rightOuterJoin

左连接和右连接,sql一样,不多解释

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

val rdd: RDD[(String, (Int, Option[Int]))] = dataRDD1.leftOuterJoin(dataRDD2)
cogroup

connection+group: 分组连接

在类型为(K,V)和(K,W)的RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("a",2),("c",3)))
val dataRDD2 = sparkContext.makeRDD(List(("a",1),("c",2),("c",3))) val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = dataRDD1.cogroup(dataRDD2)

行动算子

行动算子就是触发作业 (Job) 执行的方法

底层代码调用的是环境对象的runJob方法

底层代码中会创建ActiveJob, 并提交执行

reduce

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

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 聚合数据
val reduceResult: Int = rdd.reduce(_+_)
collect

​ 方法会将不同分区的数据按照分区顺序采集到Driver端内存中

在驱动程序中,以数组Array 的形式返回数据集的所有元素

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 收 集 数 据 到 Driver 
rdd.collect().foreach(println)
count

返回RDD 中元素的个数

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 返回 RDD 中元素的个数
val countResult: Long = rdd.count()
first

返回RDD 中的第一个元素

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 返回 RDD 中元素的个数
val firstResult: Int = rdd.first() println(firstResult)
take

返回一个由RDD 的前 n 个元素组成的数组

vval rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 返回 RDD 中元素的个数
val takeResult: Array[Int] = rdd.take(2) println(takeResult.mkString(","))
takeOrdered

返回该 RDD 排序后的前 n 个元素组成的数组

val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))

// 返回 RDD 中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
aggregate

​ 分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

​ aggregateByKey只会参与分区内计算

​ aggregate会参与分区内计算,并且参与分区间计算

val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)

// 将该 RDD 所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _) 
 val result: Int = rdd.aggregate(10)(_ + _, _ + _)
fold

折叠操作,aggregate 的简化版操作

val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))

val foldResult: Int = rdd.fold(0)(_+_)
countByKey和countByValue

统计每种 key 或 value的个数

val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))

// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
save相关算子

将数据保存到不同格式的文件中

  • saveAsTextFile
  • saveAsObjectFile
  • saveAsSequenceFile
foreach

分布式遍历RDD 中的每一个元素,调用指定函数

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 收集后打印
rdd.map(num=>num).collect().foreach(println) println("****************")
// 分布式打印
rdd.foreach(println)

t.mkString(“,”))




#### takeOrdered

返回该 RDD **排序后**的前 n 个元素组成的数组

```scala
val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))

// 返回 RDD 中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
aggregate

​ 分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

​ aggregateByKey只会参与分区内计算

​ aggregate会参与分区内计算,并且参与分区间计算

val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)

// 将该 RDD 所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _) 
 val result: Int = rdd.aggregate(10)(_ + _, _ + _)
fold

折叠操作,aggregate 的简化版操作

val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))

val foldResult: Int = rdd.fold(0)(_+_)
countByKey和countByValue

统计每种 key 或 value的个数

val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))

// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
save相关算子

将数据保存到不同格式的文件中

  • saveAsTextFile
  • saveAsObjectFile
  • saveAsSequenceFile
foreach

分布式遍历RDD 中的每一个元素,调用指定函数

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

// 收集后打印
rdd.map(num=>num).collect().foreach(println) println("****************")
// 分布式打印
rdd.foreach(println)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值