Spark Core

SparkCore

1 认识 RDD(弹性分布式数据集)

  • 实现了 Spark 的基本功能,包含任务调度、内存管理、错误恢复、与存储系统 交互等模块。 Spark Core 中还包含了对弹性分布式数据集(resilient distributed dataset,简称 RDD)的 API 定义
  • resilient distributed dataset
    弹性分布式数据集
  • RDD是SparkCore的核心

1.1 RDD 为什么会产生

  • RDD 是 Spark 的基石,是实现 Spark 数据处理的核心抽象。
  • Hadoop 的 MapReduce 是一种基于数据集的工作模式,面向数据,这种工作模式一般是从存储上加载数据集,然后操作数据集,最后写入物理存储设备。数据更多面临的是一次性处理。
  • MR 的这种方式对数据领域两种常见的操作不是很高效。第一种是迭代式的算法。比如机器学习中 ALS、凸优化梯度下降等。这些都需要基于数据集或者数据集的衍生数据反复查询反复操作。 MR 这种模式不太合适,即使多 MR 串行处理,性能和时间也是一个问题。数据的共享依赖于磁盘。另外一种是交互式数据挖掘, MR 显然不擅长。
  • MR 中的迭代:
    在这里插入图片描述
  • Spark中的迭代:
    一个执行流程称为一个作业(JOB)
    在这里插入图片描述
    JOB之间无缝对接,在JOB完成之后保存在内存里,JOB之间是血统支持,不会造成数据丢失,容错性高
  • Spark 效率非常快, 且能够支持迭代计算和有效数据共享的模型。 RDD 是基于工作集的工作模式, 更多的是面向工作流

1.2 RDD 概述

  • 分布式弹性数据集 -> Spark对数据以及数据处理进行抽象而形成的一种对象
  • 快速数据处理,对迭代式的数据处理更有优势,不是针对工作流,而是针对工作集进行操作

1.2.1 什么是 RDD

  • RDD:弹性分布式数据集
  • Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。
  • RDD的特性:不可变;可分区;弹性
  • 可分区:实现了分布式,存在于不同节点的内存中,并行度以每个分区的数据Task而言,每个分区都是每个任务。分区可以在不同的节点上,每个分区的数据处理任务由每个节点的Executor执行。每个分区的任务都处理完了,则当前RDD就处理完了。符合模块化思想,一个复杂的问题由若干个简单问题组成,把复杂的问题简单化。
  • RDD在源码中是一个抽象类(abstract),有各种各样的下属的子类别(工程中实现的、JDBC RDD;txt RDD…)
  • RDD的算子:转换算子(Map、ReudceByKey)和行动算子()
  • Spark对内存的管控:所有的转换算子都是懒执行;行动算子开始执行的时候转换算子都加载(立即执行)
  • RDD(Resilient Distributed Dataset)叫做分布式数据集,是 Spark 中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。 在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有 RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。 RDD 可以包含 Python、 Java、 Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。 RDD 具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。 RDD 允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
  • RDD 支持两种操作:转化操作和行动操作。 RDD 的转化操作是返回一个新的 RDD 的操作,比如 map()和 filter(),而行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如 count() 和 first()
  • Spark 采用惰性计算模式, RDD 只有第一次在一个行动操作中用到时,才会真正计算。Spark 可以优化整个计算过程。 默认情况下, Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个 RDD, 可以使用 RDD.persist() 让Spark 把这个 RDD 缓存下来

1.2.2 RDD 的属性

  1. 一组分片(Partition),即数据集的基本组成单位。对于 RDD 来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建 RDD 时指定 RDD 的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的 CPU Core 的数目。
  2. 一个计算每个分区的函数。 Spark 中 RDD 的计算是以分片为单位的,每个 RDD 都会实现 compute 函数以达到这个目的。 compute 函数会对迭代器进行复合,不需要保存每次计算的结果。
  3. RDD 之间的依赖关系。 RDD 的每次转换都会生成一个新的 RDD,所以 RDD 之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时, Spark 可以通过这个依赖关系重新计算丢失的分区数据,而不是对 RDD 的所有分区进行重新计算。
  1. 一个 Partitioner,即 RDD 的分片函数。当前 Spark 中实现了两种类型的分片函数,一个是基于哈希的 HashPartitioner,另外一个是基于范围的 RangePartitioner。只有对于于key-value 的 RDD,才会有 Partitioner,非 key-value 的 RDD 的 Parititioner 的值是 None。Partitioner 函数不但决定了 RDD 本身的分片数量,也决定了 parent RDD Shuffle 输出时的分片数量。
  1. 一个列表,存储存取每个 Partition 的优先位置(preferred location)。对于一个 HDFS 文件来说,这个列表保存的就是每个 Partition 所在的块的位置。按照“移动数据不如移动计算”的理念, Spark 在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
  • RDD 是一个应用层面的逻辑概念。一个 RDD 多个分片。 RDD 就是一个元数据记录集,记录了 RDD 内存所有的关系数据。

1.3 RDD 的弹性

  • RDD弹性表现:
    1. 存储的弹性:内存与磁盘的自动切换
      不需要人为干预
    2. 容错的弹性:数据丢失可以自动恢复
      不必担心容易出错,出错后可以根据血统把数据恢复。不必担心后续的工作执行。
    3. 计算的弹性:计算出错重试机制
      MR在出错的时候也有出错重试机制,默认重试4次;RDD默认也是4次,但是可以调整
    4. 分片的弹性:根据需要重新分片
      MR的分区分过之后无法重排,没有重排机制;Spark针对集合的分区可以打乱重排;假设原始数据100W个,占100个分区,计算后剩下10个,还占100个分区?不科学,分区重排,说不定10个不到。
  1. 自动进行内存和磁盘数据存储的切换
  • Spark 优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换
  1. 基于血统的高效容错机制
  • 在 RDD 进行转换和动作的时候,会形成 RDD 的 Lineage 依赖链,当某一个 RDD 失效的时候,可以通过重新计算上游的 RDD 来重新生成丢失的 RDD 数据。
  1. Task 如果失败会自动进行特定次数的重试
  • RDD 的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是 4 次。
  1. Stage 如果失败会自动进行特定次数的重试
  • 如果 Job 的某个 Stage 阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是 4 次
  1. Checkpoint 和 Persist 可主动或被动触发
  • DD 可以通过 Persist 持久化将 RDD 缓存到内存或者磁盘,当再次用到该 RDD 时直接读取就行。也可以将 RDD 进行检查点,检查点会将数据存储在 HDFS 中,该 RDD 的所有父 RDD 依赖都会被移除。
  1. 数据调度弹性
  • Spark 把这个 JOB 执行模型抽象为通用的有向无环图 DAG,可以将多 Stage 的任务串联或并行执行,调度引擎自动处理 Stage 的失败以及 Task 的失败。
  1. 数据分片的高度弹性
  • 可以根据业务的特征,动态调整数据分片的个数,提升整体的应用执行效率。
  • RDD 全称叫做弹性分布式数据集(Resilient Distributed Datasets),它是一种分布式的内存抽象,表示一个只读的记录分区的集合,它只能通过其他 RDD 转换而创建,为此, RDD 支持丰富的转换操作(如 map, join, filter, groupBy 等),通过这种转换操作,新的 RDD 则包含了如何从其他 RDDs 衍生所必需的信息,所以说 RDDs 之间是有依赖关系的。基于 RDDs 之间的依赖, RDDs 会形成一个有向无环图 DAG,该 DAG 描述了整个流式计算的流程,实际执行的时候, RDD 是通过血缘关系(Lineage)一气呵成的,即使出现数据分区丢失,也可以通过血缘关系重建分区,总结起来,基于 RDD 的流式计算任务可描述为:从稳定的物理存储(如分布式文件系统)中加载记录,记录被传入由一组确定性操作构成的 DAG,然后写回稳定存储。另外 RDD 还可以将数据集缓存到内存中,使得在多个操作之间可以重用数据集,基于这个特点可以很方便地构建迭代型应用(图计算、机器学习等)或者交互式数据分析应用。可以说 Spark 最初也就是实现 RDD 的一个分布式系统,后面通过不断发展壮大成为现在较为完善的大数据生态系统,简单来讲, Spark-RDD 的关系类似于 Hadoop-MapReduce 关系

1.4 RDD特点

  • RDD 表示只读的分区的数据集,对 RDD 进行改动,只能通过 RDD 的转换操作,由一个 RDD 得到一个新的 RDD,新的 RDD 包含了从其他 RDD 衍生所必需的信息。 RDDs 之间存在依赖, RDD 的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD 来切断血缘关系。

1.4.1 分区

  • RDD 逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个 compute函数得到每个分区的数据。如果 RDD 是通过已有的文件系统构建,则 compute 函数是读取指定文件系统中的数据,如果 RDD 是通过其他 RDD 转换而来,则 compute 函数是执行转换逻辑将其他 RDD 的数据进行转换。
    在这里插入图片描述

1.4.2 只读

  • RDD 是只读的,要想改变 RDD 中的数据,只能在现有的 RDD 基础上创建新的 RDD
    在这里插入图片描述
  • 由一个 RDD 转换到另一个 RDD,可以通过丰富的操作算子实现,不再像 MapReduce那样只能写 map 和 reduce 了
    在这里插入图片描述
  • RDD 的操作算子包括两类,一类叫做 transformations,它是用来将 RDD 进行转化,构建 RDD 的血缘关系;另一类叫做 actions,它是用来触发 RDD 的计算,得到 RDD 的相关计算结果或者将 RDD 保存的文件系统中。
    在这里插入图片描述

1.4.3 依赖

  • RDDs 通过操作算子进行转换,转换得到的新 RDD 包含了从其他 RDDs 衍生所必需的信息, RDDs 之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖, RDDs 之间分区是一一对应的,另一种是宽依赖,下游 RDD 的每个分区与上游RDD(也称之为父 RDD)的每个分区都有关,是多对多的关系
    在这里插入图片描述
  • 通过 RDDs 之间的这种依赖关系,一个任务流可以描述为 DAG(有向无环图),如下图所示,在实际执行过程中宽依赖对应于 Shuffle(图中的 reduceByKey 和 join),窄依赖中的所有转换操作可以通过类似于管道的方式一气呵成执行(图中 map 和 union 可以一起执行)。
    在这里插入图片描述

1.4.4 缓存

  • 如果在应用程序中多次使用同一个 RDD,可以将该 RDD 缓存起来,该 RDD 只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该 RDD 的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示, RDD-1经过一系列的转换后得到 RDD-n 并保存到 hdfs, RDD-1 在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的 RDD-1 转换到 RDD-m 这一过程中,就不会计算其之前的 RDD-0 了
    在这里插入图片描述

1.4.5 CheckPoint

  • 虽然 RDD 的血缘关系天然地可以实现容错,当 RDD 的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行, RDDs 之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此, RDD 支持 checkpoint 将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为 checkpoint 后的 RDD 不需要知道它的父 RDDs 了,它可以从 checkpoint处拿到数据
  • 给定一个 RDD 我们至少可以知道如下几点信息:
    1. 分区数以及分区方式;
    2. 由父 RDDs衍生而来的相关依赖信息
    3. 计算每个分区的数据,计算步骤为:
      1. 如果被缓存,则从缓存中取的分区的数据;
      2. 如果被 checkpoint,则从 checkpoint 处恢复数据;
      3. 根据血缘关系计算分区的数据。

2 RDD编程

2.1 编程模型

  • RDD本身不是为数据存储而成的
  • RDD的转换算子都是懒执行的
  • Spark Core的核心就是对RDD的各种运算
  • 原生数据空间 -> Spark RDD空间
    在这里插入图片描述
  • 在 Spark 中, RDD 被表示为对象,通过对象上的方法调用来对 RDD 进行转换。经过一系列的 transformations 定义 RDD 之后,就可以调用 actions 触发 RDD 的计算, action 可以是向应用程序返回结果(count, collect 等),或者是向存储系统保存数据(saveAsTextFile 等)。在Spark 中,只有遇到 action,才会执行 RDD 的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换
  • 要使用 Spark,开发者需要编写一个 Driver 程序,它被提交到集群以调度运行 Worker,如下图所示。 Driver 中定义了一个或多个 RDD,并调用 RDD 上的 action, Worker 则执行RDD 分区计算任务。
    在这里插入图片描述
    在这里插入图片描述

2.2 RDD 创建

  • 在 Spark 中创建 RDD 的创建方式大概可以分为三种:从集合中创建 RDD;从外部存储创建 RDD;从其他 RDD 创建。
    val sparkConf = new SparkConf()
                .setAppName("Create RDD")
                .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;

    val mkRDD = sparkContext.makeRDD(arr)
    val parRDD = sparkContext.parallelize(arr,2)  //调整分区两个

    val scalaArray1 = mkRDD.collect()
    val scalaArray2 = parRDD.collect()
    println("mkRDD:" + scalaArray1.toList)
    println("parRDD:" + scalaArray2.toList)

    println("mkRDD分区数:" + mkRDD.partitions.size)
    println("parRDD分区数:" + parRDD.partitions.size)
                                                                                mkRDD:List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
parRDD:List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
mkRDD分区数:4
parRDD分区数:2
  • 而从集合中创建 RDD, Spark 主要提供了两种函数: parallelize 和 makeRDD。我们可以先看看这两个函数的声明:
def parallelize[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T]
def makeRDD[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T]
def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T]
  • 我们可以从上面看出 makeRDD 有两种实现,而且第一个 makeRDD 函数接收的参数和 parallelize 完全一致。其实第一种 makeRDD 函数实现是依赖了 parallelize 函数的实现,来看看 Spark 中是怎么实现这个 makeRDD 函数的:
def makeRDD[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T] = withScope {
parallelize(seq, numSlices)
}
  • 我们可以看出,这个 makeRDD 函数完全和 parallelize 函数一致。但是我们得看看第二种 makeRDD 函数函数实现了,它接收的参数类型是 Seq[(T, Seq[String])], Spark 文档的说明是:
    1. Distribute a local Scala collection to form an RDD, with one or more location preferences(hostnames of Spark nodes) for each object. Create a new partition for each collection item.
  • 原来,这个函数还为数据提供了位置信息,来看看我们怎么使用:
scala> val result1= sc.parallelize(List(1,2,3))
result1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[10]
at parallelize at <console>:21
scala> val result2 = sc.makeRDD(List(1,2,3))
result2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[11]
at makeRDD at <console>:21
scala> val seq = List((1, List("node103")),| (2, List("node104")))
seq: List[(Int, List[String])] = List((1,List(node103)),
(2,List(node104)))
scala> val result3 = sc.makeRDD(seq)
result3: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[12]
at makeRDD at <console>:23
scala> result3.preferredLocations(result3.partitions(1))
res26: Seq[String] = List(node104)
scala> result3.preferredLocations(result3.partitions(0))
res27: Seq[String] = List(node103)
scala> result1.preferredLocations(result1.partitions(0))
res28: Seq[String] = List()
  • 我们可以看到, makeRDD 函数有两种实现,第一种实现其实完全和 parallelize 一致;而第二种实现可以为数据提供位置信息,而除此之外的实现和 parallelize 函数也是一致的,如下
def parallelize[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T] = withScope {
assertNotStopped()
new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int,
Seq[String]]())
}
def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T] =
withScope {
assertNotStopped()
val indexToPrefs = seq.zipWithIndex.map(t => (t._2, t._1._2)).toMap
new ParallelCollectionRDD[T](this, seq.map(_._1), seq.size,
indexToPrefs)
}
  • 都是返回 ParallelCollectionRDD,而且这个 makeRDD 的实现不可以自己指定分区的数量,而是固定为 seq 参数的 size 大小
  • 由外部存储系统的数据集创建,包括本地的文件系统,还有所有 Hadoop 支持的数据集,比如 HDFS、 Cassandra、 HBase 等
scala> val res = sc.textFile("hdfs://node102:8020/RELEASE")
res: org.apache.spark.rdd.RDD[String] = hdfs:// node102:8020/RELEASE
MapPartitionsRDD[4] at textFile at <console>:24

2.3 TransFormation

val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;

    val mkRDD = sparkContext.makeRDD(arr)

2.3.1 map(func)

  • 返回一个新的 RDD,该 RDD 由每一个输入元素经过 func 函数转换后组成


    //1.  Map算子的用法
    //Map的作用是把集合里面的每一个元素通过一些规则转换成另外一个元素

//    val mapRDD = mkRDD.map(_*2)

    //((v,2v),sum) => (v,(2v,sum))
    val mapRDD = mkRDD.map[((Int,Int),Int)](v=>((v,2*v),v+2*v)).map{
      item=>
        (item._1._1,(item._1._2,item._2))
      //      item=>
      //      case ((x:Int,y:Int),z:Int):((Int,Int),Int)=>(x,(y,z))
    }
    mapRDD.collect().foreach(println) //collect() foreach() 都是行动算子

                                                                                (1,(2,3))
(2,(4,6))
(3,(6,9))
(4,(8,12))
(5,(10,15))
(6,(12,18))
(7,(14,21))
(8,(16,24))
(9,(18,27))
(10,(20,30))

2.3.2 mapPartitions(func)

  • 类似于 map,但独立地在 RDD 的每一个分片上运行,因此在类型为 T 的 RDD 上运行时,func 的函数类型必须是 Iterator[T] => Iterator[U]。 假设有 N 个元素,有 M 个分区,那么 map的函数的将被调用 N 次,而 mapPartitions 被调用 M 次,一个函数一次处理所有分区
    //2.  mapPartitions
    //val mapRDD2 = mkRDD.mapPartitions(x => Iterator(x.next() + 1 ))
    val mapRDD2 = mkRDD.mapPartitions{
    items此时就是一个分片 ->也是一个Task
      items =>
        var arrBuf:ArrayBuffer[String] = new ArrayBuffer
        for (item <- items){
          arrBuf += "Hello=>" + (item + 1)
        }

        arrBuf.toIterator
    }
    mapRDD2.collect().foreach(println)
Hello=>2
Hello=>3
Hello=>4
Hello=>5
Hello=>6
Hello=>7
Hello=>8
Hello=>9
Hello=>10
Hello=>11

2.3.3 glom

  • 将每一个分区形成一个数组,形成新的 RDD 类型时 RDD[Array[T]]
    //3.  glom操作
    //每个分区转成一个数组
    val mapRDD3 = mkRDD.glom()
    //每个分区的数据都是一个数组,数组的元素用“ ”分割形成一个字符串
    mapRDD3.map(_.mkString(" ")).foreach(println)
1 2
6 7
8 9 10
3 4 5

2.3.4 flatMap(func)

  • 类似于 map,但是每一个输入元素可以被映射为 0 或多个输出元素(所以 func 应该返回一个序列,而不是单一元素)
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;
    val arrs = Array("a1a2a","b3b4b","c5c6c")
    val mkRDD = sparkContext.makeRDD(arrs)


    //4.  flatMap:压平
    val flatMapRDD = mkRDD.flatMap(_.split("\\d"))
    flatMapRDD.collect().foreach(println)
                                                                                a
a
a
b
b
b
c
c
c

2.3.5 filter(func)

  • 【单词filter:过滤】
  • 返回一个新的 RDD,该 RDD 由经过 func 函数计算后返回值为 true 的输入元素组成
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;
    val arrs = Array("a1a2a","b3b4b","5c6c")
    val mkRDD = sparkContext.makeRDD(arrs)

    val filterRDD = mkRDD.filter(_.length > 4)

    filterRDD.foreach(println)
  }
[Stage 0:>                                                          (0 + 0) / 4]a1a2a
b3b4b

2.3.6 mapPartitionsWithIndex(func)

  • 类似于 mapPartitions,但 func 带有一个整数参数表示分片的索引值,因此在类型为 T 的 RDD上运行时, func 的函数类型必须是(Int, Interator[T]) => Iterator[U]
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 4;

    val mkRDD = sparkContext.makeRDD(arr,5) //设置分区数

3>3
1>1
2>2
0>
4>4

2.3.7 sample(withReplacement, fraction, seed)

  • 【英文翻译:样本(带替换,分数,种子)】
  • 以指定的随机种子随机抽样出数量为 fraction 的数据, withReplacement 表示是抽出的数据是否放回, true 为有放回的抽样, false 为无放回的抽样, seed 用于指定随机数生成器种子。
    //  7.  sample 随机抽样
    val parRDD = sparkContext.makeRDD(1 to 50)
    val sampleRDD = parRDD.sample(false,0.3,4)
    sampleRDD.foreach(println)
6
44
13
18
22
23
25
26
30
31
32

2.3.8 distinct([numTasks]))

  • 【英语翻译:与众不同([numTasks]))】
  • 对源 RDD 进行去重后返回一个新的 RDD. 默认情况下,只有 8 个并行任务来操作,但是可以传入一个可选的 numTasks 参数改变它
//  8.  distinct([numTasks]))去除重数
    val parRDD2 = sparkContext.parallelize(List(1,5,5,6,2,5,4,4,4,4,1,5,6))
    //重新设定分区数 并 用mapPartitionsWithIndex展示分区号
    val disRDD = parRDD2.distinct(2)
                    .mapPartitionsWithIndex((index:Int,items) => Iterator(index + ">>>" + items.mkString("||")))
    disRDD.foreach(println)
1>>>1||5
0>>>4||6||2

2.3.9 partitionBy

  • 对 RDD 进行分区操作,如果原有的 partionRDD 和现有的 partionRDD 是一致的话就不进行分区, 否则会生成 ShuffleRDD。
val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val mkRDD = sparkContext.makeRDD(1 to 10)
    val mapRDD = mkRDD.map((_,""))
    var partRDD = mapRDD.partitionBy(new HashPartitioner(2));
    partRDD.mapPartitions(x => Iterator(x.mkString("----"))).foreach(println)
[Stage 0:==============>                                            (1 + 3) / 4](2,)----(4,)----(6,)----(8,)----(10,)
(1,)----(3,)----(5,)----(7,)----(9,)

2.3.10 coalesce(numPartitions)

  • 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;

    val mkRDD = sparkContext.makeRDD(arr)
    mkRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    val partRDD = mkRDD.coalesce(2,false)
    partRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)

[Stage 0:>                                                          (0 + 0) / 4]6||7
8||9||10
1||2
3||4||5
[Stage 1:>                                                          (0 + 4) / 4]2||4||7||9
1||3||5||6||8||10

2.3.11 repartition(numPartitions)

  • 根据分区数,从新通过网络随机洗牌所有数据。
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 10;

    val mkRDD = sparkContext.makeRDD(arr)
    mkRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    println("=========================")

    val partRDD = mkRDD.coalesce(2,false)
    partRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    println("=========================")

    val repartRDD = mkRDD.repartition(2)
    repartRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
[Stage 0:>                                                          (0 + 0) / 4]6||7
3||4||5
1||2
8||9||10
                                                                                =========================
6||7||8||9||10
1||2||3||4||5
=========================
[Stage 2:>                                                          (0 + 4) / 4]1||3||5||6||8||10
2||4||7||9

2.3.12 repartitionAndSortWithinPartitions(partitioner)

  • repartitionAndSortWithinPartitions 函数是 repartition 函数的变种,与 repartition 函数不同的是,repartitionAndSortWithinPartitions 在给定的 partitioner 内部进行排序,性能比 repartition 要高。
 val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)
    //10 - 12 缩减分区(重排分区)
    val arr = 1 to 10;

    val mkRDD = sparkContext.makeRDD(arr)
    mkRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    println("=========================")

    val partRDD = mkRDD.coalesce(2,false)
    partRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    println("=========================")

    val repartRDD = mkRDD.repartition(2)
    repartRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
    println("=========================")

    //水塘抽样法 肯定是shuffle重写的
    val repartSortRDD = mkRDD.map((_,0)).repartitionAndSortWithinPartitions(new HashPartitioner(2))
    repartSortRDD.mapPartitions(x => Iterator(x.mkString("||"))).foreach(println)
  
[Stage 0:>                                                          (0 + 0) / 4]1||2
6||7
8||9||10
3||4||5
                                                                                =========================
1||2||3||4||5
6||7||8||9||10
=========================
[Stage 2:==============>                                            (1 + 3) / 4]1||3||5||6||8||10
2||4||7||9
                                                                                =========================
(2,0)||(4,0)||(6,0)||(8,0)||(10,0)
(1,0)||(3,0)||(5,0)||(7,0)||(9,0)

2.3.13 sortBy(func,[ascending], [numTasks])

  • 用 func 先对数据进行处理,按照处理后的数据比较结果排序。
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(2,5,8,1,3,6,100)

    val mkRDD = sparkContext.makeRDD(arr)

    //sortBy()第一个参数是根据排序的key(不是改变数据)
    // 第二个参数boolean决定是否升序
    val sortRDD = mkRDD.sortBy(1*_,false);
    for(i <- sortRDD.collect){
      println(i)
    }

                                                                                100
8
6
5
3
2
1

2.3.14 union(otherDataset)

  • 对源 RDD 和参数 RDD 求并集后返回一个新的 RDD 不去重
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 5
    val mkRDD = sparkContext.makeRDD(arr)

    val arr2 = 3 to 6
    val mkRDD2 = sparkContext.makeRDD(arr2)

    val unionRDD = mkRDD.union(mkRDD2)

    val  arrResult = unionRDD.collect()
    arrResult.foreach(println)
                                                                                1
2
3
4
5
3
4
5
6

2.3.15 subtract (otherDataset)

  • 【英文翻译:减去】
  • 计算差的一种函数, 去除两个 RDD 中相同的元素,不同的 RDD 将保留下来
    //去除相同的保留不同的
    val subtractRDD = mkRDD.subtract(mkRDD2)
    subtractRDD.collect().foreach(println)
1
2

2.3.16 intersection(otherDataset)

  • 【英文翻译:相交】
  • 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
 //求交集
    val interRDD = mkRDD.intersection(mkRDD2)
    interRDD.foreach(println)
3
5
4

2.3.17 cartesian(otherDataset)

  • 【英文翻译:笛卡尔】
  • 笛卡尔积
    //求笛卡尔积
    val carRDD = mkRDD.cartesian(mkRDD2)
    carRDD.foreach(println)
(1,3)
(1,4)
(1,6)
(1,5)
(2,5)
(2,6)
(2,4)
(2,3)
(3,4)
(3,5)
(3,3)
(3,6)
(4,3)
(5,3)
(4,4)
(5,4)
(4,5)
(5,5)
(4,6)
(5,6)

2.3.18 pipe(command, [envVars])

  • 【翻译:管】
  • 管道,对于每个分区,都执行一个 perl 或者 shell 脚本,返回输出的 RDD
  • bin目录下创建一个脚本
[bduser@node102 bin]$ vim pipotest.sh
[bduser@node102 bin]$ chmod 777 pipotest.sh

#!/bin/sh
echo "hello spark!"
while read LINE;
do
        echo ">>>"${LINE}
done

  • 打开spark-shell
scala> val rdd = sc.makeRDD(1 to 5)
scala> val resultRDD = rdd.pipe("/home/bduser/bin/testpipe.sh")
res4: Array[String] = Array(hello spark!, >>>1, >>>2, hello spark!, >>>3, >>>4, >>>5)

2.3.19 join(otherDataset, [numTasks])

  • 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素对在一起的(K,(V,W))的 RDD
 val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = 1 to 5
    val mkRDD = sparkContext.makeRDD(arr)

    val arr2 = 3 to 6
    val mkRDD2 = sparkContext.makeRDD(arr2)

    val joinRDD = mkRDD.map((_,1)).join(mkRDD2.map((_,2)))
    val arrResult = joinRDD.collect()
    arrResult.foreach(println)
                                                                                (4,(1,2))
(5,(1,2))
(3,(1,2))

2.3.20 cogroup(otherDataset, [numTasks])

  • 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD
val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr)
	val arr2 = List(2,2,2,3)
    val mkRDD2 = sparkContext.makeRDD(arr2)
val cogroupRDD = mkRDD.map(x => (x,2*x)).cogroup(mkRDD2.map((_,1)))
    val arrResult2 = cogroupRDD.collect()
    arrResult2.foreach(println)
(1,(CompactBuffer(2, 2),CompactBuffer()))
(5,(CompactBuffer(10, 10),CompactBuffer()))
(2,(CompactBuffer(4),CompactBuffer(1, 1, 1)))
(3,(CompactBuffer(),CompactBuffer(1)))

2.3.21 reduceByKey(func, [numTasks])

  • 在一个(K,V)的 RDD 上调用,返回一个(K,V)的 RDD,使用指定的 reduce 函数,将相同 key的值聚合到一起, reduce 任务的个数可以通过第二个可选的参数来设置
val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr)

    val mapRDD = mkRDD.map((_,1))//一对一 flatMap -> 一对多
    val reduceRDD = mapRDD.reduceByKey(_+_)
    reduceRDD.foreach(println)
[Stage 0:>                                                          (0 + 4) / 4](1,2)
(5,2)
(2,1)

2.3.22 sortByKey([ascending], [numTasks])

  • 在一个(K,V)的 RDD 上调用, K 必须实现 Ordered 接口,返回一个按照 key 进行排序的(K,V)的 RDD
    val sortRDD = mapRDD.sortByKey(false,1)
    sortRDD.foreach(println)
    println("---------------------")
(5,1)
(5,1)
(2,1)
(1,1)
(1,1)

2.3.23 mapValues

  • 针对于(K,V)形式的类型只对 V 进行操作
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr)

    val mapRDD = mkRDD.map(x => (x,2*x))//一对一 flatMap -> 一对多
   
(1,12)
(2,14)
(1,12)
(5,20)
(5,20)

2.3.24 combineByKey[C]

  • createCombiner:第一次遇到某Key时执行的过程
    a:(v,1)
  • mergeValue:非第一次遇到某个KEY时执行的过程
    a:(c._1+v,c._2+1)
  • mergeCombiners:把所有分支的合并结果统一合并起来
    a:(c1._1+c2._2,c1._2+c2._2)
( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)

对相同 K,把 V 合并成一个集合。
1. createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就 和之前的某个元素的键相同。如果这是一个新的元素,combineByKey() 会使用一个叫作 createCombiner() 的函数来创建那个键对应的累加器的初始值
2. mergeValue: 如果这是一个在处理当前分区之前已经遇到的键, 它会使用 mergeValue() 方法将该键的累加器对应的当前值与这个新的值进行合并
3. mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的mergeCombiners() 方法将各个分区的结果进行合并。

val combineRDD = mapRDD.combineByKey((_,1)
      ,(c:(Int,Int),v:Int) => (c._1+v,c._2 +1)
      ,(c1:(Int,Int),c2:(Int,Int)) => (c1._1 + c2._2,c1._2 + c2._2))
    combineRDD.foreach(println)
    println("---------------------")
(1,(3,2))
(5,(20,2))
(2,(4,1))

在这里插入图片描述

2.3.25 aggregateByKey

  • 【英文翻译:合计】
  • (zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
  • 在 kv 对的 RDD 中,,按 key 将 value 进行分组合并,合并时,将每个 value 和初始值作为 seq函数的参数,进行计算,返回的结果作为一个新的 kv 对,然后再将结果按照 key 进行合并,最后将每个分组的 value 传递给 combine 函数进行计算(先将前两个 value 进行计算,将返回结果和下一个 value 传给 combine 函数,以此类推),将 key 与计算结果作为一个新的 kv对输出
  • seqOp 函数用于在每一个分区中用初始值逐步迭代 value, combOp 函数用于合并每个分区中的结果
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr,4)

    val mapRDD = mkRDD.map(x => (x,2*x))

    val aggRDD = mapRDD.aggregateByKey((0,0))(
          (items:(Int,Int),value:Int) => (items._1 + value,items._2+1),
          (c1:(Int,Int),c2:(Int,Int)) => (c1._1 + c2._1 ,c1._2+c2._2)
        )

    aggRDD.foreach(println)
[Stage 0:>                                                          (0 + 0) / 4](2,(4,1))
(1,(4,2))
(5,(20,2))

2.3.26 foldByKey

  • (zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
  • aggregateByKey 的简化操作, seqop 和 combop 相同
    val foldRDD = mapRDD.foldByKey(0)((sum,v) => sum+v)
    foldRDD.foreach(println)
(1,4)
(5,20)
(2,4)

2.4 Action

2.4.1 reduce(func)

  • 通过 func 函数聚集 RDD 中的所有元素,这个功能必须是可交换且可并联的
scala> val rdd1 = sc.makeRDD(1 to 10,2)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[85] atmakeRDD at <console>:24
scala> rdd1.reduce(_+_)
res50: Int = 55
scala> val rdd2 = sc.makeRDD(Array(("a",1),("a",3),("c",3),("d",5)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] =ParallelCollectionRDD[86] at makeRDD at <console>:24
scala> rdd2.reduce((x,y)=>(x._1 + y._1,x._2 + y._2))
res51: (String, Int) = (adca,12)

2.4.2 collect()

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

2.4.3 count()

  • 返回 RDD 的元素个数

2.4.4 first()

  • 返回 RDD 的第一个元素(类似于 take(1))

2.4.5 take(n)

  • 返回一个由数据集的前 n 个元素组成的数组

2.4.6 takeSample(withReplacement,num, [seed])

  • 返回一个数组,该数组由从数据集中随机采样的 num 个元素组成,可以选择是否用随机数替换不足的部分, seed 用于指定随机数生成器种子

2.4.7 takeOrdered(n)

  • 返回前几个的排序

2.4.8 saveAsTextFile(path)

  • 将数据集的元素以 textfile 的形式保存到 HDFS 文件系统或者其他支持的文件系统,对于每个元素, Spark 将会调用 toString 方法,将它装换为文件中的文本

2.4.9 saveAsSequenceFile(path)

  • 将数据集中的元素以 Hadoop sequencefile 的格式保存到指定的目录下,可以使 HDFS 或者其他 Hadoop 支持的文件系统。

2.4.10 saveAsObjectFile(path)

  • 用于将 RDD 中的元素序列化成对象,存储到文件中。

2.4.11 countByKey()

  • 针对(K,V)类型的 RDD,返回一个(K,Int)的 map,表示每一个 key 对应的元素个数。
scala> val rdd =
sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
rdd: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[95]at parallelize at <console>:24
scala> rdd.countByKey()
res63: scala.collection.Map[Int,Long] = Map(3 -> 2, 1 -> 3, 2 -> 1)

2.4.12 foreach(func)

  • 在数据集的每一个元素上,运行函数 func 进行更新。
scala> var rdd = sc.makeRDD(1 to 10,2)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[107] at
makeRDD at <console>:24
scala> var sum = sc.accumulator(0)
warning: there were two deprecation warnings; re-run with -deprecation
for details
sum: org.apache.spark.Accumulator[Int] = 0
scala> rdd.foreach(sum+=_)
scala> sum.value
res68: Int = 55
scala> rdd.collect().foreach(println)
1 2 3 4 5 6 7 8 9
10

2.4.13 aggregate

  • (zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
  • aggregate 函数将每个分区里面的元素通过 seqOp 和初始值进行聚合,然后用 combine 函数将每个分区的结果和初始值(zeroValue)进行 combine 操作。这个函数最终返回的类型不需要和 RDD 中元素类型一致。
val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr,4)

    val mapRDD = mkRDD.map(x => (x,2*x))
    val aggAtcRDD = mapRDD.aggregate((0,0))(
      (tempTup:(Int,Int),items) => (tempTup._1 + items._2,tempTup._2+1),
      (c1:(Int,Int),c2:(Int,Int)) => (c1._1 + c2._1 ,c1._2+c2._2)
    )
    println(aggAtcRDD)
(28,5)

2.4.14 fold(num)(func)

  • 折叠操作, aggregate 的简化操作, seqop 和 combop 一样。
scala> var rdd1 = sc.makeRDD(1 to 4,2)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[90] at
makeRDD at <console>:24
scala> rdd1.aggregate(1)(
| {(x : Int,y : Int) => x + y},
| {(a : Int,b : Int) => a + b}
| )
res59: Int = 13
scala> rdd1.fold(1)(_+_)
res60: Int = 13

2.5 MapWork Practise

  • 一个字符串每个字符出现多少次
  1. Java
    Map版本
     String s = "udfgubcbbufcbrhu8paucbyf7yr";

        //分拣思想
        //种类、个数
        Map<String,Integer> map = new HashMap<>();

        for(int idx = 0; idx < s.length() ; idx++){
            String a = s.charAt(idx) + "";
            if(map.get(a) == null){
                map.put(a,1);
            }else{
                map.put(a,map.get(a) + 1);
            }
        }

        System.out.println(map);
{a=1, b=5, c=3, d=1, f=3, g=1, h=1, p=1, r=2, u=5, 7=1, 8=1, y=2}

字符串版本

    String word = null;
        String temp = null;
        while (s.length() != 0) {
            word = s.substring(0,1);
            temp = s.replace(word,"");
            int num = s.length() - temp.length();
            System.out.println(word+"出现了"+num+"次");

            s = temp;
        }
u出现了5次
d出现了1次
f出现了3次
g出现了1次
b出现了5次
c出现了3次
r出现了2次
h出现了18出现了1次
p出现了1次
a出现了1次
y出现了27出现了1
  1. Scala
    val s = "udfgubcbbufcbrhu8paucbyf7yr"

    //分拣思想 Scala比Java更加简化 (函数式编程)
    var map = new mutable.HashMap[Char,Int]()

//    s.foldLeft(map){
//      (map:mutable.HashMap[Char,Int], value:Char) =>
//        if(map.get(value) == None) map.put(value,1)
//        if(map.get(value) != None) map.put(value,map(value) + 1)
//        map
//    }

    //scala字符串本来就是一个集合 里面的每个字符都是一个元素
    val result = s.foldLeft(map) {
      (map: mutable.HashMap[Char, Int], value: Char) =>
        map += ((value, map.getOrElse(value, 0) + 1))
    }

    println(result)
Map(h -> 1, b -> 5, 8 -> 1, d -> 1, 7 -> 1, g -> 1, p -> 1, y -> 2, a -> 1, r -> 2, c -> 3, u -> 5, f -> 3)

2.6 RDD 依赖关系

  • RDD 和它依赖的父 RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)。
    1. 窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用(没有打乱重排)
    2. 宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle(打乱重排)
    3. spark讲究MEMORY_ONLY讲究快,不在乎内存的易失
      在这里插入图片描述

2.6.1 窄依赖

  • 窄依赖指的是每一个父 RDD 的 Partition 最多被子 RDD 的一个 Partition 使用,窄依赖我们形象的比喻为独生子女

2.6.2 宽依赖

  • 宽依赖指的是多个子 RDD的 Partition会依赖同一个父 RDD的 Partition,会引起 shuffle,总结: 宽依赖我们形象的比喻为超生

2.6.3 Lineage

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

2.7 RDD 的运行机制

  • 输入可能以多个文件的形式存储在 HDFS 上,每个 File 都包含了很多块,称为 Block。当 Spark 读取这些文件作为输入时,会根据具体数据格式对应的 InputFormat 进行解析,一般是将若干个 Block 合并成一个输入分片,称为 InputSplit,注意 InputSplit 不能跨越文件。随后将为这些输入分片生成具体的 Task。 InputSplit 与 Task 是一一对应的关系。随后这些具体的 Task 每个都会被分配到集群上的某个节点的某个 Executor 去执行
  • 每个节点可以起一个或多个 Executor。
  • 每个 Executor 由若干 core 组成,每个 Executor 的每个 core 一次只能执行一个 Task。
  • 每个 Task 执行的结果就是生成了目标 RDD 的一个 partiton。
  • 这里的 core 是虚拟的 core 而不是机器的物理 CPU 核,可以理解为就是 Executor的一个工作线程。而 Task 被执行的并发度 = Executor 数目 * 每个 Executor 核数。
  • partition 的数目:
    1. 对于数据读入阶段,例如 sc.textFile,输入文件被划分为多少 InputSplit 就会需要多少初始 Task。
    2. 在 Map 阶段 partition 数目保持不变。
    3. 在 Reduce 阶段, RDD 的聚合会触发 shuffle 操作,聚合后的 RDD 的 partition 数目跟具体操作有关,例如 repartition 操作会聚合成指定分区数,还有一些算子是可配置的
  • RDD 在计算的时候,每个分区都会起一个 task,所以 rdd 的分区数目决定了总的的 task数目。申请的计算节点(Executor)数目和每个计算节点核数,决定了你同一时刻可以并行执行的 task
  • 在这里插入图片描述
  • 每一个jar就是一个application,一个行动算子是一个job,每一个宽依赖就是一个Stage,每一个Stage都是由若干个Task在每个分区的执行,每个分区都有一个Task。每个 节点可以有多个分区。
  • 根据宽依赖来切分成为stage,是从血统中从后向前推演(判断是否有行动算子,再向前推演),判断会不会出现打乱重排[shuffle]的过程:
    1. 从后向前推演进行压栈操作,判断切分stage。
    2. 推研后出栈根据血统关系进行执行。
  • 比如有的 RDD 有 100 个分区,那么计算的时候就会生成 100 个 task,你的资源配置为10 个计算节点,每个两 2 个核,同一时刻可以并行的 task 数目为 20,计算这个 RDD 就需要 5 个轮次。如果计算资源不变,你有 101 个 task 的话,就需要 6 个轮次,在最后一轮中,只有一个 task 在执行,其余核都在空转。如果资源不变,你的 RDD 只有 2 个分区,那么同一时刻只有 2 个 task 运行,其余 18 个核空转,造成资源浪费。这就是在 spark 调优中,增大 RDD 分区数目,增大任务并行度的做法
  • 在这里插入图片描述

2.8 DAG 的生成

  • DAG(Directed Acyclic Graph)叫做有向无环图。
    在这里插入图片描述
  • 原始的 RDD 通过一系列的转换就就形成了 DAG,根据 RDD 之间的依赖关系的不同将 DAG 划分成不同的 Stage,对于窄依赖, partition 的转换处理在 Stage 中完成计算。对于宽依赖,由于有 Shuffle 的存在,只能在 parent RDD 处理完成后,才能开始接下来的计算,因此宽依赖是划分 Stage 的依据。
    在这里插入图片描述

2.9 RDD 持久化

2.9.1 RDD 的缓存

  • Spark 速度非常快的原因之一,就是在不同操作中可以在内存中持久化或缓存个数据集。当持久化某个 RDD 后,每一个节点都将把计算的分片结果保存在内存中,并在对此 RDD或衍生出的 RDD 进行的其他动作中重用。这使得后续的动作变得更加迅速。 RDD 相关的持久化和缓存,是 Spark 最重要的特征之一。可以说,缓存是 Spark 构建迭代式算法和快速交互式查询的关键。 如果一个有持久化数据的节点发生故障, Spark 会在需要用到缓存的数据时重算丢失的数据分区。如果 希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。

2.9.2 RDD 缓存方式

  • RDD通过 persist方法或 cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空 间中。
  • 但是并不是这两个方法被调用时立即缓存,而是触发后面的 action 时,该 RDD 将会被缓存在计算节点的内存中,并供后面重用
  • 在这里插入图片描述
  • 通过查看源码发现 cache 最终也是调用了 persist 方法,默认的存储级别都是仅在内存存储一份, Spark 的存储级别还有好多种,存储级别在 object StorageLevel 中定义的。
  • 在这里插入图片描述
  • 在存储级别的末尾加上“_2”来把持久化数据存为两份
  • 在这里插入图片描述
  • 缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除, RDD 的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。
  • 注意: 使用 Tachyon 可以实现堆外缓存
scala> val rdd = sc.makeRDD(1 to 10)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24

scala> val nocacheRDD = rdd.map(_.toString+"["+System.currentTimeMillis+"]")
nocacheRDD: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[1] at map at <console>:26

scala> val cacheRDD = rdd.map(_.toString+"["+System.currentTimeMillis+"]")
cacheRDD: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at map at <console>:26

scala> cacheRDD.cache
res0: cacheRDD.type = MapPartitionsRDD[2] at map at <console>:26

在这里插入图片描述

2.10 RDD 检查点机制

  • Spark 中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制, 检查点(本质是通过将 RDD 写入 Disk 做检查点)是为了通过 lineage 做容错的辅助, lineage 过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的 RDD 开始重做 Lineage,就会减少开销。 检查点通过将数据写入到 HDFS文件系统实现了 RDD 的检查点功能
  • cache 和 checkpoint 是有显著区别的, 缓存把RDD 计算出来然后放在内存中,但是
    RDD 的依赖链(相当于数据库中的redo 日志), 也不能丢掉, 当某个点某个executor 宕了,上面cache 的RDD就会丢掉, 需要通过 依赖链重放计算出来, 不同的是, checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链, 是通过复制实现的高容错。
  • 如果存在以下场景, 则比较适合使用检查点机制:
    1. DAG 中的 Lineage 过长,如果重算,则开销太大(如在 PageRank 中)。
    2. 在宽依赖上做 Checkpoint 获得的收益更大。
  • 为当前 RDD 设置检查点。该函数将会创建一个二进制的文件,并存储到 checkpoint 目录中,该目录是用 SparkContext.setCheckpointDir()设置的。在 checkpoint 的过程中,该 RDD的所有依赖于父 RDD 中的信息将全部被移出。对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发
  • 在HDFS上创建一个检查点目录
[bduser@node102 ~]$ hdfs dfs -mkdir /user/spark/chkpoint

scala> sc.setCheckpointDir("hdfs://node102:8020/user/spark/checkpoint")

scala> val ch1 = sc.parallelize(1 to 2)
ch1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[3] at parallelize at <console>:24

scala>  val ch2 =
     | ch1.map(_.toString+"["+System.currentTimeMillis+"]")
ch2: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[4] at map at <console>:27

scala>  val ch3 =
     | ch1.map(_.toString+"["+System.currentTimeMillis+"]")
ch3: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[5] at map at <console>:27

scala> ch3.checkpoint

在这里插入图片描述

  • 检查点机制保存只有数据本身,没有血统,保存在hdfs上

3 键值对 RDD

  • 键值对 RDD 是 Spark 中许多操作所需要的常见数据类型。 本章做特别讲解。除了在基础 RDD 类中定义的操作之外, Spark 为包含键值对类型的 RDD 提供了一些专有的操作 在 PairRDDFunctions 专门进行了定义。这些 RDD 被称为 pair RDD。
  • 有很多种方式创建 pair RDD,在输入输出章节会讲解。一般如果从一个普通的 RDD转 为 pair RDD 时,可以调用 map()函数来实现,传递的函数需要返回键值对。
val pairs = lines.map(x => (x.split(" ")(0), x))

3.1 键值对 RDD 的转化操作

3.1.1 转化操作列表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1.2 聚合操作

  • 当数据集以键值对形式组织的时候,聚合具有相同键的元素进行一些统计是很常见的操 作。之前讲解过基础 RDD 上的 fold()、 combine()、 reduce()等行动操作, pair RDD 上则 有相应的针对键的转化操作。 Spark 有一组类似的操作,可以组合具有相同键的值。这些 操作返回 RDD,因此它们是转化操作而不是行动操作
  • reduceByKey() 与 reduce() 相当类似;它们都接收一个函数,并使用该函数对值进行合并。 reduceByKey() 会为数据集中的每个键进行并行的归约操作,每个归约操作会将键相同的值合 并起来。因为数据集中可能有大量的键,所以 reduceByKey() 没有被实现为向用户程序返回一 个值的行动操作。实际上,它会返回一个由各键和对应键归约出来的结果值组成的新的 RDD。
  • foldByKey() 则与 fold() 相当类似;它们都使用一个与 RDD 和合并函数中的数据类型相 同的零值作为初始值。与 fold() 一样, foldByKey() 操作所使用的合并函数对零值与另一 个元素进行合并,结果仍为该元素
  • 在这里插入图片描述
  • combineByKey() 是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它 实现的。和 aggregate() 一样, combineByKey() 可以让用户返回与输入数据的类型不同的 返回值。
    1. 要理解 combineByKey(),要先理解它在处理数据时是如何处理每个元素的。由于combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。
    2. 如果这是一个新的元素, combineByKey() 会使用一个叫作 createCombiner() 的函数来创建 那个键对应的累加器的初始值。需要注意的是,这一过程会在每个分区中第一次出现各个 键时发生,而不是在整个 RDD 中第一次出现一个键时发生。
    3. 如果这是一个在处理当前分区之前已经遇到的键,它会使用 mergeValue() 方法将该键的累 加器对应的当前值与这个新的值进行合并。
    4. 由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更 多的分区都有对应同一个键的累加器,就需要使用用户提供的 mergeCombiners() 方法将各 个分区的结果进行合并。

3.1.3 数据分组

  • 如果数据已经以预期的方式提取了键, groupByKey() 就会使用 RDD 中的键来对数据进行 分组。对于一个由类型 K 的键和类型 V 的值组成的 RDD,所得到的结果 RDD类型会是 [K, Iterable[V]]。
  • groupBy() 可以用于未成对的数据上,也可以根据除键相同以外的条件进行分组。它可以 接收一个函数,对源 RDD 中的每个元素使用该函数,将返回结果作为键再进行分组。
  • 多个 RDD 分组,可以使用 cogroup 函数, cogroup() 的函数对多个共享同 一个键的RDD 进行分组。对两个键的类型均为 K 而值的类型分别为 V 和 W 的 RDD 进行cogroup() 时,得到的结果 RDD 类型为 [(K, (Iterable[V], Iterable[W]))]。如果其中的 一个RDD 对于另一个 RDD 中存在的某个键没有对应的记录,那么对应的迭代器则为空。cogroup() 提供了为多个 RDD 进行数据分组的方法。

3.1.4 连接

  • 连接主要用于多个 Pair RDD 的操作, 连接方式多种多样:右外连接、左外连接、交 叉连接以及内连接。
  • 普通的 join 操作符表示内连接 2。只有在两个 pair RDD 中都存在的键才叫输出。当一个输 入对应的某个键有多个值时,生成的 pair RDD 会包括来自两个输入 RDD 的每一组相对应 的记录。
  • leftOuterJoin()产生的 pair RDD 中,源 RDD 的每一个键都有对应的记录。每个 键相应的值是由一个源 RDD 中的值与一个包含第二个 RDD 的值的 Option(在 Java 中为Optional)对象组成的二元组。
  • rightOuterJoin() 几乎与 leftOuterJoin() 完全一样,只不过预期结果中的键必须出现在第二个 RDD 中,而二元组中的可缺失的部分则来自于源 RDD 而非第二个 RDD。

3.1.5 数据排序

  • sortByKey() 函数接收一个叫作 ascending 的参数,表示我们是否想要让结果按升序排序(默认值为 true)。

3.2 键值对 RDD 的行动操作

在这里插入图片描述

3.3 键值对 RDD 的数据分区

  • Spark目前支持Hash分区和Range分区,用户也可以自定义分区, Hash分区为当前的默认分区, Spark中分区器直接决定了RDD中分区的个数、 RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数

3.3.1 获取 RDD 分区的方式

  • 可以通过使用 RDD 的 partitioner 属性来获取 RDD 的分区方式。它会返回一个scala.Option 对象, 通过 get 方法获取其中的值。
  • 在这里插入图片描述

3.3.2 Hash 分区方式

  • HashPartitioner 分区的原理:对于给定的 key,计算其 hashCode,并除于分区的个数取余,如果余数小于 0,则用余数+分区的个数,最后返回的值就是这个 key 所属的分区 ID。

3.3.3 Ranger 分区方式

  • HashPartitioner 分区弊端:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有 RDD 的全部数据。有可能产生数据倾斜问题
  • RangePartitioner 分区优势:尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大;
  • 但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内
  • RangePartitioner 作用:将一定范围内的数映射到某一个分区内,在实现中,分界的算法尤为重要。用到了水塘抽样算法。
  • Spark会自动切换Hash和Range分区,实现性能的优化。

3.3.4 自定义分区方式

  • 要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法
    1. numPartitions: Int:返回创建出来的分区数。
    2. numPartitions: Int:返回创建出来的分区数。
    3. equals():Java 判断相等性的标准方法。这个方法的实现非常重要, Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD的分区方式是否相同
class MyPartitioner(num:Int) extends Partitioner {
  override def numPartitions: Int = num

  override def getPartition(key: Any): Int = {
    key.toString.hashCode & Integer.MAX_VALUE%num
  }

}




val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)
    val rdd = sparkContext.makeRDD(1 to 10)
val partRDD = rdd.map((_,1))
    println(partRDD.partitionBy(new MyPartitioner(1)).mapPartitions(x => Iterator(x.mkString("|")))collect())
  • 使用自定义的 Partitioner 是很容易的:只要把它传给 partitionBy() 方法即可。 Spark 中有许多依赖于数据混洗的方法,比如 join() 和groupByKey(),它们也可以接收一个可选的
    Partitioner 对象来控制输出数据的分区方式。

3.3.5 分区 shuffle 优化

  • 在分布式程序中, 通信的代价是很大的,因此控制数据分布以获得最少的网络传输可以极大地提升整体性能
  • Spark 中所有的键值对 RDD 都可以进行分区。系统会根据一个针对键的函数对元素进行分 组。 主要有哈希分区和范围分区,当然用户也可以自定义分区函数。
  • 通过分区可以有效提升程序性能。如下例子:
  • 分析这样一个应用,它在内存中保存着一张很大的用户信息表—— 也就是一个由(UserID, UserInfo) 对组成的 RDD,其中 UserInfo 包含一个该用户所订阅 的主题的列表。该应用会周期性地将这张表与一个小文件进行组合,这个小文件中存着过 去五分钟内发生的事件——其实就是一个由 (UserID, LinkInfo) 对组成的表,存放着过去 五分钟内某网站各用户的访问情况。例如,我们可能需要对用户访问其未订阅主题的页面 的情况进行统计。
  • 解决方案一:
  • 在这里插入图片描述
  • 在这里插入图片描述
  • 这段代码可以正确运行,但是不够高效。这是因为在每次调用 processNewLogs() 时都会用 到 join() 操作,而我们对数据集是如何分区的却一无所知。默认情况下,连接操作会将两 个数据集中的所有键的哈希值都求出来,将该哈希值相同的记录通过网络传到同一台机器 上,然后在那台机器上对所有键相同的记录进行连接操作。因为 userData 表比 每五分钟出现的访问日志表 events 要大得多,所以要浪费时间做很多额外工作:在每次调 用时都对 userData 表进行哈希值计算和跨节点数据混洗, 降低了程序的执行效率。
  • 在这里插入图片描述
  • 我们在构 建 userData 时调用了 partitionBy(), Spark 就知道了该 RDD 是根据键的哈希值来分 区的,这样在调用 join() 时, Spark 就会利用到这一点。具体来说,当调用userData. join(events) 时, Spark 只会对 events 进行数据混洗操作,将 events 中特定UserID 的记 录发送到 userData 的对应分区所在的那台机器上。这样,需要通过网络传输的 数据就大大减少了,程序运行速度也可以显著提升了。
  • 在这里插入图片描述

3.3.6 基于分区进行操作

  • 基于分区对数据进行操作可以让我们避免为每个数据元素进行重复的配置工作。诸如打开 数据库连接或创建随机数生成器等操作,都是我们应当尽量避免为每个元素都配置一次的 工作。 Spark 提供基于分区的 mapPartition 和 foreachPartition,让你的部分代码只对RDD 的每个分区运行 一次,这样可以帮助降低这些操作的代价。

3.3.7 从分区中获益的操作

  • 能够从数据分区中获得性能提升的操作有 cogroup()、 groupWith()、 join()、leftOuterJoin()、 rightOuterJoin()、 groupByKey()、 reduceByKey()、 combineByKey() 以及lookup()等

4 数据读取与保存

4.1 文本文件

  • 当我们将一个文本文件读取为 RDD 时,输入的每一行 都会成为 RDD 的一个元素。也可以将多个完整的文本文件一次性读取为一个 pair RDD, 其中键是文件名,值是文件内容
val input = sc.textFile("./README.md")
  • 如果传递目录,则将目录下的所有文件读取作为 RDD。
  • 文件路径支持通配符。
  • 通过 wholeTextFiles()对于大量的小文件读取效率比较高,大文件效果没有那么高。
  • Spark 通过 saveAsTextFile() 进行文本文件的输出,该方法接收一个路径,并将 RDD中的内容都输入到路径对应的文件中。 Spark 将传入的路径作为目录对待,会在那个 目录下输出多个文件。这样, Spark 就可以从多个节点上并行输出了。
result.saveAsTextFile(outputFile)

4.2 JSON 文件

  • 如果 JSON 文件中每一行就是一个 JSON 记录,那么可以通过将 JSON 文件当做文本文件来读取,然后利用相关的 JSON 库对每一条数据进行 JSON 解析。

4.3 CSV 文件

  • 读取 CSV/TSV 数据和读取 JSON 数据相似,都需要先把文件当作普通文本文件来读取数据, 然后通过将每一行进行解析实现对 CSV 的读取
  • CSV/TSV 数据的输出也是需要将结构化 RDD 通过相关的库转换成字符串 RDD,然后使用 Spark 的文本文件 API 写出去。

4.4 Sequence 文件

  • SequenceFile 文件是 Hadoop 用来存储二进制形式的 key-value 对而设计的一种平面文件(Flat File)
  • Spark 有专门用来读取 SequenceFile 的接口。在 SparkContext 中,可以调用sequenceFile keyClass, valueClass
  • 在这里插入图片描述
  • 在这里插入图片描述

4.5 对象文件

  • 对象文件是将对象序列化后保存的文件,采用 Java 的序列化机制。 可以通过objectFilek,v 函数接收一个路径, 读取对象文件, 返回对应的 RDD, 也可以通过调用 saveAsObjectFile() 实现对对象文件的输出。因为是序列化所以要指定类型。
scala> val
data=sc.parallelize(List((2,"aa"),(3,"bb"),(4,"cc"),(5,"dd"),(6,"
ee")))
data: org.apache.spark.rdd.RDD[(Int, String)] =
ParallelCollectionRDD[20] at parallelize at <console>:24
scala> data.saveAsObjectFile("hdfs://node102:8020/objfile")
scala> import org.apache.spark.rdd.RDD
import org.apache.spark.rdd.RDD
scala> val objrdd:RDD[(Int,String)] =
sc.objectFile[(Int,String)]("hdfs://node102:8020/objfile/p*")
objrdd: org.apache.spark.rdd.RDD[(Int, String)] =
MapPartitionsRDD[28] at objectFile at <console>:25
scala> objrdd.collect()
res20: Array[(Int, String)] = Array((2,aa), (3,bb), (4,cc), (5,dd),
(6,ee))

4.6 HDFS

  • Spark 的整个生态系统与 Hadoop 是完全兼容的,所以对于 Hadoop 所支持的文件类型或者数据库类型,Spark 也同样支持.另外,由于 Hadoop 的 API 有新旧两个版本,所以 Spark 为了能够兼容 Hadoop 所有的版本,也提供了两套创建操作接口.对于外部存储创建操作而言,hadoopRDD 和 newHadoopRDD 是最为抽象的两个函数接口,主要包含以下四个参数.
    1. 输入格式(InputFormat): 制定数据输入的类型,如 TextInputFormat 等,新旧两个版本所引用的版本分别是org.apache.hadoop.mapred.InputFormat
      org.apache.hadoop.mapreduce.InputFormat(NewInputFormat)
    2. 键类型: 指定[K,V]键值对中 K 的类型
    3. 值类型: 指定[K,V]键值对中 V 的类型
    4. 分区值: 指定由外部存储生成的 RDD 的 partition 数量的最小值,如果没有指定,系统会使用默认值 defaultMinSplits
  • 其他创建操作的 API接口都是为了方便最终的 Spark程序开发者而设置的,是这两个接口的高效实现版本.例如,对于 textFile 而言,只有 path 这个指定文件路径的参数,其他参数在系统内部指定了默认值

4.7 文件系统

  • Spark 支持读写很多种文件系统, 像本地文件系统、 Amazon S3、 HDFS 等。

4.8 数据库

4.8.1关系型数据库连接

  • 支持通过 Java JDBC 访问关系型数据库。需要通过 JdbcRDD 进行,
  • Mysql 读取:
def main (args: Array[String] ) {
val sparkConf = new SparkConf ().setMaster ("local[2]").setAppName
("JdbcApp")
val sc = new SparkContext (sparkConf)
val rdd = new org.apache.spark.rdd.JdbcRDD (
sc,
() => {
Class.forName ("com.mysql.jdbc.Driver").newInstance()
java.sql.DriverManager.getConnection
("jdbc:mysql://node102:3306/rdd", "root", "hive")
},
"select * from rddtable where id >= ? and id <= ?;",
1,
10,
1,
r => (r.getInt(1), r.getString(2)))
println (rdd.count () )
rdd.foreach (println (_) )
sc.stop ()
}
  • Mysql 写入:
def main(args: Array[String]) {
val sparkConf = new
SparkConf().setMaster("local[2]").setAppName("HBaseApp")
val sc = new SparkContext(sparkConf)
val data = sc.parallelize(List("Female", "Male","Female"))
data.foreachPartition(insertData)
}
def insertData(iterator: Iterator[String]): Unit = {
Class.forName ("com.mysql.jdbc.Driver").newInstance()
val conn =
java.sql.DriverManager.getConnection("jdbc:mysql://node102:3306/r
dd", "root", "hive")
iterator.foreach(data => {
val ps = conn.prepareStatement("insert into rddtable(name) values
(?)")
ps.setString(1, data)
ps.executeUpdate()
})
}
  • JdbcRDD 接收这样几个参数。
    1. 首先,要提供一个用于对数据库创建连接的函数。这个函数让每个节点在连接必要的配 置后创建自己读取数据的连接。
    2. 接下来,要提供一个可以读取一定范围内数据的查询,以及查询参数中 lowerBound和 upperBound 的值。这些参数可以让 Spark 在不同机器上查询不同范围的数据,这样就不 会因尝试在一个节点上读取所有数据而遭遇性能瓶颈。
    3. 这个函数的最后一个参数是一个可以将输出结果从转为对操作数据有用的格式的函数。如果这个参数空缺, Spark 会自动将每行结果转为一个对象数组。
  • Cassandra 数据库和 ElasticSearch 集成:
  • 在这里插入图片描述

4.8.2 HBase 数据库

  • 由于 org.apache.hadoop.hbase.mapreduce.TableInputFormat 类的实现, Spark 可以通过Hadoop 输入格式访问 HBase。这个输入格式会返回键值对数据,其中键的类型为 org.apache.hadoop.hbase.io.ImmutableBytesWritable, 值的类型为 org.apache.hadoop.hbase.client.Result
  • HBase读取
def main(args: Array[String]) {
val sparkConf = new
SparkConf().setMaster("local[2]").setAppName("HBaseApp")
val sc = new SparkContext(sparkConf)
val conf = HBaseConfiguration.create()
//HBase 中的表名
conf.set(TableInputFormat.INPUT_TABLE, "fruit")
val hBaseRDD = sc.newAPIHadoopRDD(conf, classOf[TableInputFormat],
classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable],
classOf[org.apache.hadoop.hbase.client.Result])
val count = hBaseRDD.count()
println("hBaseRDD RDD Count:"+ count)
hBaseRDD.cache()
hBaseRDD.foreach {
case (_, result) =>
val key = Bytes.toString(result.getRow)
val name = Bytes.toString(result.getValue("info".getBytes,
"name".getBytes))
val color = Bytes.toString(result.getValue("info".getBytes,
"color".getBytes))
println("Row key:" + key + " Name:" + name + " Color:" + color)
}
sc.stop()
}
  • HBase 写入:
def main(args: Array[String]) {
val sparkConf = new
SparkConf().setMaster("local[2]").setAppName("HBaseApp")
val sc = new SparkContext(sparkConf)
val conf = HBaseConfiguration.create()
val jobConf = new JobConf(conf)
jobConf.setOutputFormat(classOf[TableOutputFormat])
jobConf.set(TableOutputFormat.OUTPUT_TABLE, "fruit_spark")
val fruitTable = TableName.valueOf("fruit_spark")
val tableDescr = new HTableDescriptor(fruitTable)
tableDescr.addFamily(new HColumnDescriptor("info".getBytes))
val admin = new HBaseAdmin(conf)
if (admin.tableExists(fruitTable)) {
admin.disableTable(fruitTable)
admin.deleteTable(fruitTable)
}
admin.createTable(tableDescr)
def convert(triple: (Int, String, Int)) = {
val put = new Put(Bytes.toBytes(triple._1))
put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("name"),
Bytes.toBytes(triple._2))
put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("price"),
Bytes.toBytes(triple._3))
(new ImmutableBytesWritable, put)
}
val initialRDD = sc.parallelize(List((1,"apple",11),
(2,"banana",12), (3,"pear",13)))
val localData = initialRDD.map(convert)
localData.saveAsHadoopDataset(jobConf)
}

5 RDD 编程进阶

5.1 累加器

  • 累加器用来对信息进行聚合,通常在向 Spark 传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱 动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本, 更新这些副本的值也不会影响驱动器中的对应变量。 如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。

5.1.1 系统累加器

  • 累加器的用法如下所示。
  • 通过在驱动器中调用 SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为 org.apache.spark.Accumulator[T] 对象,其中 T 是初始值 initialValue 的类型。 Spark 闭包里的执行器代码可以使用累加器的 += 方法(在 Java 中是 add)增加累加器的值。 驱动器程序可以调用累加器的 value 属性(在 Java 中使用 value()或 setValue())来访问累加器的值
  • 注意: 工作节点上的任务不能访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。
  • 对于要在行动操作中使用的累加器,Spark 只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计 算时都绝对可靠的累加器,我们必须把它放在foreach() 这样的行动操作中。转化操作中累加器可 能会发生不止一次更新
    val sparkConf = new SparkConf()
      .setAppName("Create RDD")
      .setMaster("local[*]")

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array(1,1,2,5,5)
    val mkRDD = sparkContext.makeRDD(arr,4)

    //  系统自带的累加器
    var count = sparkContext.accumulator(0);

    val resultArray = mkRDD.map{
      x =>
        count.add(1);
        (x,1)
    }.collect()

    for(v <- resultArray) println("v :" + v)
    println("count:" + count)
                                                                                v :(1,1)
v :(1,1)
v :(2,1)
v :(5,1)
v :(5,1)
count:5

5.1.2 自定义累加器

  • 自定义累加器类型的功能在 1.X 版本中就已经提供了,但是使用起来比较麻烦,在 2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2 来提供更加友好的自定义类型累加器的实现方式。实现自定义类型累加器需要继承 AccumulatorV2 并至少覆写下例中出现的方法,下面这个累加器可以用于在程序运行过程中收集一些文本类信息,最终以 Set[String]的形式返回。
class MyAccumulator extends AccumulatorV2 [Any,util.HashSet[String]]{

  var strSet = new util.HashSet[String]()
  var count = 0

  override def isZero: Boolean = !(strSet.size>0)

  override def copy(): AccumulatorV2[Any, util.HashSet[String]] = {
    val tempAccu = new MyAccumulator
    tempAccu.strSet.addAll(this.strSet)
    tempAccu
  }

  override def reset(): Unit = {
    strSet.clear()
  }


  override def add(v: Any): Unit = {
    if(v.isInstanceOf[String]){
      strSet.add(v.asInstanceOf[String])
    }
  }

  override def merge(other: AccumulatorV2[Any, util.HashSet[String]]): Unit = {
    if(other.isInstanceOf[MyAccumulator]){
      val otherAccu = other.asInstanceOf[MyAccumulator]
      this.strSet.addAll(otherAccu.strSet)
    }
  }

  override def value: util.HashSet[String] = {
    this.strSet
  }

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

    val sparkContext = new SparkContext(sparkConf)

    val arr = Array("aaa",5,"asd",2,"spark","data","aaa",2,"spark")
    val mkRDD = sparkContext.makeRDD(arr,4)

    //注册累加器
    val myaccu = new MyAccumulator
    sparkContext.register(myaccu,"strCollect")

    val resultArray = mkRDD.map{
      x =>
        myaccu.add(x)
        (x,1)
    }.collect()


    resultArray.foreach(println)

    println(myaccu.value)
  }
}

                                                                                (aaa,1)
(5,1)
(asd,1)
(2,1)
(spark,1)
(data,1)
(aaa,1)
(2,1)
(spark,1)
[aaa, asd, data, spark]

5.2 广播变量

  • 广播变量用来高效分发较大的对象。向所有工作节点发送一个 较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发 送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起 来都很顺手。
  • 传统方式下, Spark 会自动把闭包中所有引用到的变量发送到工作节点上。虽然这很方便,但也很低效。原因有二:首先,默认的任务发射机制是专门为小任务进行优化的; 其次,事实上你可能会在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。
  • 使用广播变量的过程如下:
    1. 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T]对象。 任何可序列化的类型都可以这么实现。
    2. 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
    3. 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。
      在这里插入图片描述
    //广播变量:每个Excutor只发送一份的数据,避免每个分区发送一份
    val broadcastVar = sparkContext.broadcast(List(1,2,4))
        //注册累加器
    val myaccu = new MyAccumulator
    sparkContext.register(myaccu,"strCollect")



    val resultArray = mkRDD.map{
      x =>
        myaccu.add(x)
        println("" + broadcastVar.value)
        (x,1)
    }.collect()
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
List(1, 2, 4)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值