Spark中RDD转换算子以及案例

1 Spark核心编程

Spark计算框架为了能对数据进行高并发和搞吞吐的处理,封装了三大数据结构,分别是:

  • RDD:弹性分布式数据集
  • 累加器:分布式共享只写变量
  • 广播变量:分布式共享只读变量

1.1 RDD

代表是一个弹性的,不可变,可分区,里面的元素可并行计算的集合

弹性:
存储的弹性:内存与磁盘的自动切换
容错的弹性:数据丢失可以自动修复
计算的弹性:计算出错重试机制
分片的弹性:可根据需要重新分片
分布式: 数据存储在大数据集群不同节点上
数据集: RDD封装了计算逻辑,并不保存数据
数据抽象: RDD是一个抽象类,需要子类实现
不可变: RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
可分区,并行计算

1.2 核心属性

源码

1.2.1 分区列表

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

1.2.2 分区计算函数

计算时,使用分区计算函数对每个分区进行计算

1.2.3 RDD之间的依赖关系

RDD是计算模型的封装,计算模型进行组合时,需要建立依赖关系

1.2.4 分区器(可选)

只有数据为KV类型数据时,可以通过设定分区器自定义数据的分区,简单说就是给任务分配不同的分区

1.2.5 首选位置(可选)

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

1.3 实现原理

RDD体现了装饰者设计模式,一层一层进行装饰,与IO基本类似
装饰者模式
RDD执行是延迟加载的
只有当collect进行采集的时候,才会执行。

1.4 执行原理

执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个计算任务,将任务分配给资源不同节点,然后按照计算模型进行数据计算,最后得到计算结果
1)启动Yarn集群环境
2)Spark通过申请资源创建调度节点和计算节点
调度和计算节点

3)Spark框架根据需求将计算逻辑按分区划分成不同的任务
分任务
4)调度节点将任务根据计算节点状态发送到对应的计算节点进行计算
分给节点

1.5 基础编程

1.5.1 RDD创建

1)从集合(内存)中创建RDD
Spark提供了两种方法:parallelize和makeRDD

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

    val rdd1: RDD[Int] = sparkContext.parallelize(
      List(1, 2, 4, 3)
    )

    val rdd2: RDD[Int] = sparkContext.makeRDD(
      List(1, 2, 3, 4)
    )
    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
    sparkContext.stop()
  }

}

从底层代码来看,makeRDD其实就是parallelize方法

2)从外部存储(文件)创建RDD
外部数据集创建RDD:本地的文件系统,所有Hadoop支持的数据集,比如HDFS,HBase等

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

    val fileRDD: RDD[String] = sparkContext.textFile("D:\\1input\\word")
    fileRDD.collect().foreach(println)
    sparkContext.stop()
  }
}

3)从其他RDD创建
通过一个RDD运算完后,再产生新的RDD。
4)直接创建RDD(new)

1.5.2 RDD并行度与分区

读取内存数据时
对于:val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4,5,6), 3)
底层进行数据分区的时候关键源码:

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)
      }
    }
case _ =>
        val array = seq.toArray // To prevent O(n^2) operations for List etc
        positions(array.length, numSlices).map { case (start, end) =>
            array.slice(start, end).toSeq
        }.toSeq

通过元组(start,end)进行操作,左闭右开截取数组数据,放入分区文件

读取文件数据的分区和读取内存中集合数据的分区是不一样的
读取文件时:
bytesRemaining:剩余的字节
splitSize:它总共的字节
goalSize:每个分区的数据的大小
按照hadoop的行读取规则,依照偏移量进行读取,不足一行读取一行:
详细的过程:根据自定义的分区数,或者默认的2个分区,然后(long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);)计算得出目标分区的每个分区的数据,bytesRemaining)/splitSize > SPLIT_SLOP 这个条件成立,则进去一个分区,与hadoop的行读取类似

* RDD转换算子

1)map

函数说明:将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
注意:分区不变,
mapValues操作的是只有value,不改变key


    val dataRDD: RDD[Int] = rdd.map(
      num => {
        num * 2
      }
    )
    

2)mapPartitions

函数说明:以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据
返回的值需要可以迭代,可变可不变
注意:分区数据没有处理完就不能释放,容易造成内存溢出

3)mapPartitionsWithIndex

函数说明:处理的同时可以获取当前分区索引

val dataRDD1 = dataRDD.mapPartitionsWithIndex(
    (index, datas) => {
         datas.map(index, _)
    }
)

4)flatMap

函数说明:将数据先进行扁平化,然后再进行映射处理,所以称作扁平映射

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Any] = sparkContext.makeRDD(
      List(List(1, 2), 3, List(4, 5)), 1
    )

    val value: RDD[Any] = rdd.flatMap(
      data => {
        data match {
          case list: List[_] => list
          case d => List(d)
        }
      }
    )
    value.collect().foreach(println)

    sparkContext.stop()

  }

}

5)glom

函数说明:将同一个分区的数据转换为相同类型的数据处理

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[Array[Int]] = rdd.glom()
    value.collect().foreach(
      data =>{
        println("--------------------")
        for(i <- data){
          println(i)
        }
      }
    )

    sparkContext.stop()

  }

}

6)groupBy

函数说明:数据根据指定规则进行分组,分区默认不变,数据会被打乱,此过程为shuffle

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      data => {
        data % 10
      }
    )

    value.collect().foreach(
      data => {
        println("\nkey is :" + data._1)
        for(word <- data._2){
          print(word + " ")
        }
      }
    )

    sparkContext.stop()

  }

}

7)filter

函数说明:按照指定的规则将数据进行过滤,有可能出现数据倾斜

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[Int] = rdd.filter(_ % 2 == 0)
    value.collect().foreach(println)
    sparkContext.stop()

  }

}

8)sample

函数说明:根据规则从数据集中抽取数据,第一个参数为是否放回,第二个参数是概率(放回为大于1,不放回取0~1之间),第三个参数是随机种子

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 3, 44, 55), 2
    )

    val value: RDD[Int] = rdd.sample(true, 2,1)
    value.collect().foreach(
      data =>{
        print( data +" ")
      }
    )

    println()
    val value1: RDD[Int] = rdd.sample(true, 2,1)
    value1.collect().foreach(
      data =>{
        print(data + " ")
      }
    )
  }

}

9)distinct

函数说明:将数据集中的重复数据去重

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

10)coalesce

函数说明:缩减分区,提高小数据集的执行效率

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 3, 44, 55), 3
    )
    val value1: RDD[Int] = rdd.coalesce(2)

    val value: RDD[Int] = value1.mapPartitionsWithIndex(
      (par, data) => {
        println("分区号:" + par)
        for (num <- data) {
          print(num + " ")
        }
        println()
        data
      }
    )
    value.collect().foreach(println)


  }

}

11)repartition

函数说明:内部执行的就是coalesce,只是将coalesce默认的参数shuffle为false改成了true,一个缩减分区用coalesce,扩大分区采用repartition

12)sortBy

函数说明:按照指定的规则,第一个参数指定规则,第二个参数为是否正序排列,第三个参数是分成多少个分区

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 9, 44, 55), 3
    )
    val value: RDD[Int] = rdd.sortBy(num => num%10, true, 6)
    println(value.getNumPartitions)
    value.collect().foreach(println)


  }

}

13)pipe

函数说明:管道,针对每个分区,都调用一次shell脚本,返回输出的RDD

14)intersection

函数说明:对源RDD和参数RDD求交集后返回一个新的RDD

val rdd: RDD[Int] = sparkContext.makeRDD(
  List(4, 2, 1, 9, 44, 55), 3
)
val rdd2: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val rdd3: RDD[Int] = rdd.intersection(rdd2)
rdd3.collect().foreach(println)

15)union

函数说明:求并集

16)substract

函数说明:求差集,rdd1-rdd2
注意:数据类型不一致会出错

17)zip

函数说明:rdd1和rdd2两两打包形成元组
注意:类型可以不一致

18)partitionBy

函数说明:将数据按照指定的partitioner进行分区,Spark默认的分区器是HashPartitioner
默认分区器:

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
      List((1,"aaa"),(2,"bbb"),(3,"ccc")), 3
    )
    val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))

    val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
      (num, data) => {
        data.map(
          d => {
            (num, d)
          }
        )
      }
    )
    rdd3.collect().foreach(println)


    sparkContext.stop()
  }

}

自定义分区器:

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
      List((1,"aaa"),(20,"bbb"),(30,"ccc")), 3
    )
    val rdd2: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(3))

    val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
      (num, data) => {
        data.map(
          d => {
            (num, d)
          }
        )
      }
    )
    rdd3.saveAsTextFile("output")
    rdd3.collect().foreach(println)


    sparkContext.stop()
  }

  class MyPartitioner(num:Int) extends Partitioner{
    override def numPartitions: Int = num

    override def getPartition(key: Any): Int = {

      val zzuli: Int = key.toString.toInt
      if(zzuli < 10)
        0
      else if(zzuli == 10)
        1
      else
        2
    }
  }

}

19)reduceByKey

函数说明:将数据按照相同的key对value进行聚合操作

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 1),("a",22), ("b", 2), ("c", 3)))
    val value: RDD[(String, Int)] = rdd.reduceByKey((a:Int,b:Int)=>{
      a + b
    })
    value.collect().foreach(println)
    sparkContext.stop()

  }

}

20)groupByKey

函数说明:按照key来进行分组

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

21)aggregateByKey

函数说明:将数据根据不同的规则进行分区计算和分区间计算

案例:取每个分区内相同key的最大值然后分区间相加
此算子是函数柯里化,存在两个参数列表
第一个参数列表中的参数表示初始值
第二个参数列表含有两个参数,一个为分区内的计算规则,一个分区间的计算规则

package com.baidu.exer

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

object Aggregate {

  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("zzuli").setMaster("local[*]")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(
      ("a", 1), ("a", 2), ("c", 3),
      ("b", 4), ("c", 5), ("c", 6))
      , 2
    )
    val result: RDD[(String, Int)] = rdd.aggregateByKey(0)(
      (x, y) => math.max(x, y), (x, y) => x + y
    )

   result.collect().foreach(println)
  }

}

22)foldByKey

当分区内计算规则和分区间计算规则相同时,直接一个参数就能达到aggregate算子的效果

23)combineByKey

函数说明:对k-v型rdd进行聚集操作的聚集函数,类似于aggregate,combineByKey允许用户返回值的类型与输入不一致
练习:求每个key的平均值
第一个参数:改变数据格式
第二个参数:分区内计算
第三个参数:分区间计算

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val list = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
    val sparkConf = new SparkConf().setAppName("zzuli").setMaster("local")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(list, 2)
    val combineRdd: RDD[(String, (Int, Int))] = rdd.combineByKey(
      (a: Int) => ((a, 1)),
      (acc: (Int, Int), v: Int) => (acc._1 + v, acc._2 + 1),
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
    )
    val value: RDD[(String, Int)] = combineRdd.mapValues(
      data => {
        (data._1 / data._2)
      }
    )
    value.collect().foreach(println)

    sparkContext.stop()

  }

}

24)sortByKey

函数说明:按照(k,v)中的k进行排序,如果是自定义的k值,需要实现Ordered接口

val value: RDD[(String, Int)] = rdd1.sortByKey(true)

25)join

函数说明:相当于sql中的内连接

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("a", 9), ("a", 8)))

    val value: RDD[(String, (Int, Int))] = rdd1.join(rdd2)

    value.collect().foreach(println)

  }

}

26)leftOuterJoin

函数说明:类似于sql中的左外连接,遍历每个(k,v),返回存在的值

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("s", 8)))

    val value: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)

    value.collect().foreach(println)

  }

}

27)cogroup

函数说明:在(k,v)的RDD上调用,返回(K,(iterable,Iterable))的RDD

package com.baidu.exer

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

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("c", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("c", 8)))

    val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)

    value.collect().foreach(println)

  }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章