spark算子 一、RDD概述 二、RDD算子

1.1 RDD叫做弹性分布式数据集,是spark中最基本的抽象数据。它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。

1)弹性:

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

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

  计算的弹性:计算出容错机制

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

2)分布式

  数据存储在大数据集群不同节点上

3)数据集

  RDD封装了计算逻辑,并不保存数据

4)数据抽象

  RDD是一个抽象类,需要子类具体实现

5)不可变

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

   spark算子
一、RDD概述
二、RDD算子

6)可分区、并行计算

spark算子
一、RDD概述
二、RDD算子

1.2RDD特性

五大特性:

-A list of partitions

多个分区,分区可以看成是数据集的基本组成单位

对于 RDD 来说, 每个分区都会被一个计算任务处理, 并决定了并行计算的粒度。

用户可以在创建 RDD 时指定 RDD 的分区数, 如果没有指定, 那么就会采用默认值。 默认值就是程序所分配到的 CPU Core 的数目.

每个分配的存储是由BlockManager 实现的, 每个分区都会被逻辑映射成 BlockManager 的一个 Block,,而这个 Block 会被一个 Task 负责计算。

 -A function for computing each split

 计算每个切片(分区)的函数.

 Spark 中 RDD 的计算是以分片为单位的,每个 RDD 都会实现compute函数以达到这个目的

-A list of dependencies on other RDDs

 与其他 RDD 之间的依赖关系

RDD 的每次转换都会生成一个新的 RDD, 所以 RDD 之间会形成类似于流水线一样的前后依赖关系。 在部分分区数据丢失时,Spark 可以通过这个依赖关系重新计算丢失的分区数据, 而不是对 RDD 的所有分区进行重新计算

-Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)

 对存储键值对的 RDD,还有一个可选的分区器

只有对于 key-value的 RDD,才会有 Partitioner, 非key-value的 RDD 的 Partitioner 的值是 None;Partitiner 不但决定了 RDD 的本区数量, 也决定了 parent RDD Shuffle 输出时的分区数量

-Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

 存储每个切片优先(preferred location)位置的列表

 比如对于一个 HDFS 文件来说, 这个列表保存的就是每个 Partition 所在文件块的位置. 按照“移动数据不如移动计算”的理念, Spark 在进行任务调度的时候, 会尽可能地将计算任务分配到其所要处理数据块的存储位置.

二、RDD算子

2.1value型算子

parallelize、makeRdd
含义:将外部资源转换为rdd
hello spark
world naxions
spark naxions
naxions
1.txt
//方式一:通过本地集合创建
val list1: List[Int] = List(1, 2, 3, 4, 5)
val rddTest: RDD[Int] = sc.parallelize(list1)

// 方式二:通过本地文件创建
val rddTest: RDD[String] = sc.textFile("D:\IdeaProjects\mytest\WordCount\input\3.txt", 5).flatMap(_.split(" "))

//方式三:通过外部文件创建
val rddTest: RDD[String] = sc.textFile("hdfs://ipaddress:8020/user/username/input")
groupBy
含义:将每个分区的数据按照分组条件放到一个数组
    val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)

    rdd.groupBy(_ % 2).collect().foreach(println)

    println("********************")

    val rdd1: RDD[String] = sc.makeRDD(List("hello","hive","hadoop","spark","scala"))

    rdd1.groupBy(str=>str.substring(0,1)).collect().foreach(println)

运行结果:

(0,CompactBuffer(2, 4))
(1,CompactBuffer(1, 3))
********************
(h,CompactBuffer(hello, hive, hadoop))
(s,CompactBuffer(spark, scala))
coalesce
含义:减少分区数量
    val rdd: RDD[Int] = sc.makeRDD(1 to 6, 3)
    val newRdd: RDD[Int] = rdd.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "-------->" + datas.mkString(","))
        datas
      }
    }
    newRdd.collect().foreach(println)

    println("*********************************")

    // coalesce会改变分区数量,但不会更改分区内的值,同时coalesce扩大分区也不会起作用,因为底层没有shuffle
    // 一般用于缩减分区
    // repartition一般用于扩大分区,底层为coalesce(n,true)
    val rdd2: RDD[Int] = rdd.coalesce(2)
    val newRdd2: RDD[Int] = rdd2.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "-------->" + datas.mkString(","))
        datas
      }
    }
    newRdd2.collect().foreach(println)
运行结果:
2-------->5,6
0-------->1,2
1-------->3,4
*********************************
0-------->1,2
1-------->3,4,5,6
distinct
含义:对RDD内的数据进行去重
val numRdd: RDD[Int] = sc.makeRDD(List(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6), 6)

// map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)
numRdd.distinct(3).collect().foreach(println)
运行结果:
6
3
4
1
5
2
filter
含义:对RDD数据进行过滤
    val rdd: RDD[String] = sc.makeRDD(List("mayun", "mahuateng", "liyanhong"))
    val newRdd: RDD[String] = rdd.filter(_.contains("ma"))
    newRdd.collect().foreach(println)

    val rdd2: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8))
    val newRdd2: RDD[Int] = rdd2.filter(_ % 2 != 0)
    newRdd2.collect().foreach(println)
运行结果:
mahuateng
1
3
5
7

glom

含义:将分区内的数据变成数组

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3)
    println("调用glom之前-----------------------")
    val newRdd: RDD[Int] = rdd.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "------------->" + datas.mkString(","))
        datas
      }
    }
    newRdd.collect()

    println("调用glom之后-----------------------")
    val rdd2: RDD[Array[Int]] = rdd.glom()
    rdd2.mapPartitionsWithIndex {
      (index, datas) => {
        // 每个分区的数据变成了数组
        println(index + "------------->" + datas.next().mkString(","))
        datas
      }
    }.collect()
调用glom之前-----------------------
]2------------->5,6
0------------->1,2
1------------->3,4
调用glom之后-----------------------
1------------->3,4
2------------->5,6
0------------->1,2
mapPartitionsWithIndex
含义:已知分区号,并对RDD内分区内的数据做单独运算
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3)

    val newRdd: RDD[Int] = rdd.mapPartitionsWithIndex {
      (index, datas) => {
        index match {
          case 1 => datas.map(_ * 2)
          case _ => datas
        }
      }
    }

    newRdd.collect().foreach(println)
运算结果:
1
2 6 8 5 6
sortBy
含义:排序
    val rdd: RDD[String] = sc.makeRDD(List("1", "22", "3", "4"))
    val newRdd: RDD[String] = rdd.sortBy(_.toInt)
    //    val newRdd: RDD[String] = rdd.sortBy(t => t,false)
    newRdd.collect().foreach(println)
运算结果:
1
3
4
22
sample
含义:抽样检测
    /*
    * withReplacement: Boolean,
    *   true  抽样放回
    *   false 抽样不放回
      fraction: Double,
        withReplacement=true  表示期望每一个元素出现的次数
        withReplacement=false 表示RDD每一个元素出现的概率
      seed: Long = Utils.random.nextLong  一般不会自定义
    * */
    val rdd: RDD[Int] = sc.makeRDD(1 to 10)
    val newRdd: RDD[Int] = rdd.sample(true, 1)
    newRdd.collect().foreach(println)

    println("----------------------")

    val rdd2: RDD[Int] = sc.makeRDD(1 to 10)
    val newRdd2: RDD[Int] = rdd2.sample(false, 0.5)
    newRdd2.collect().foreach(println)

    // 也可以使用takeSample()算子
运算结果:
4
9
----------------------
1
2
3
6
pipe
含义:执行服务器脚本
    val rdd: RDD[Int] = sc.makeRDD(1 to 6)
    // 可在spark-shell中执行
    rdd.pipe("shell脚本路径").collect()

2.2双Value型

    //并集
    rdd1.union(rdd2).collect().foreach(println)
    println("********************************************")
    //交集
    rdd1.intersection(rdd2).collect().foreach(println)
    println("********************************************")

    //差集
    rdd1.subtract(rdd2).collect().foreach(println)
    println("********************************************")

    //拉链
    //rdd内数据数量必须一致:Can only zip RDDs with same number of elements in each partition
    //rdd分区数量必须一致:Can't zip RDDs with unequal numbers of partitions
    rdd1.zip(rdd2).collect().foreach(println)
运行结果:
1
2
3
4
4
5
6
7
********************************************
4
********************************************
2
1
3
********************************************
(1,4)
(2,5)
(3,6)
(4,7)

2.3 key-value型

partitionBy
含义:对先前分区号做hashcode,hash后的值除以现有分区数量,余数为几就放在哪个分区号上
    //注意:RDD本事是没有partitionBy这个算子的,通过隐式转换动态给kv类型的RDD拓展功能
    val rdd: RDD[(Int, Char)] = sc.makeRDD(List((1, 'a'), (2, 'b'), (3, 'c')), 3)
    rdd.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "->" + datas.mkString(","))
        datas
      }
    }.collect()

    println("**********************************")

    //打乱了分区内的数据,产生了shuffle
    val newRdd: RDD[(Int, Char)] = rdd.partitionBy(new HashPartitioner(2))
    newRdd.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "->" + datas.mkString(","))
        datas
      }
    }.collect()

运算结果:

1->(2,b)
0->(1,a)
2->(3,c)
**********************************
0->(2,b)
1->(1,a),(3,c)