RDD

RDD

  • <1> 概述
    • 一. 什么是RDD
    • 二. spark 编程模型
      • 1. DataSource
      • 2. SparkContext
      • 3. Diver
        • (1)SparkConf
        • (2)SparkEnv
        • (3)DAGScheduler
        • (4)TaskScheduler
        • (5)ScheduleBackend
    • 二. RDD属性
      • RDD的五个特征包含四个函数和一个属性:
      • 1. 一组分片(Partition)分片
      • 2. 一个计算每个分区的函数
      • 3. 依赖关系
      • 4. RDD的分片函数
      • 5. 一个列表
      • 6. 只读的
    • 三. RDD特点
      • 1、分区
          • 1.1 原因:
          • 1.2 目的:
          • 1.3.原则:
          • 1.4.如何分区?
            • 1.4.1 HashPartitioner
            • 1.4.2 RangePartitioner
          • 1.5 .分区器
            • 1.5.1 作用
            • 1.5.2 HashPartitioner (默认)
            • 1.5.3 RangePartitioner (sort之类)
            • 1.5.4 什么操作会导致子RDD失去父RDD的分区方式?
            • 1.5.5 多元RDD的分区操作后,子RDD如何继承分区信息?
          • 1.6 函数操作
            • 1.6.1. **查看分区方式**
            • 1.6.2. **查看分区个数**
            • 1.6.3. **查看分区存储规律**
            • 1.6.4. **获得默认分区数**
            • 1.6.5. **重新定义partitioner主动使用分区**
            • 1.6.6. **重新设置分区**coalesce() 和 repartition()
      • 2、只读
      • 3、依赖
          • 3.1.窄依赖:
          • 3.2.宽依赖:
          • 3.3.作用:
          • 3.4.stage的划分
            • (1)划分:
            • (2)用宽依赖划分stage原因:
          • 3.5. 查看依赖
            • 3.5.1. 查看依赖长度
            • 3.5.2. 查看所有父RDD
            • 3.5.3. 查看第一个父RDD数据并转换格式
      • 4、持久化(缓存)
          • 4.1. cache持久化
          • 4.2. persist:
          • 4.3. cache和persist
          • 4.4. checkpoint
    • 四. 持久化
      • 1.定义:
      • 2.容错机制
          • 2.1.原因
          • 2.2.checkpoint使用场景
          • 2.3.checkpoint和cache,persist的区别
          • 2.4.checkpoint操作
      • 3.RDD控制算子
          • (1) cache :
          • (2) persist
          • (3) checkpoint
      • 4.控制算子的使用
      • 5.pesist
      • 6.如何使用缓存
    • 五. 创建RDD
      • 1. 合并行化创建 (通过scala集合创建)
      • 2. 文件系统 , 比如 HDFS
      • 3. 从父RDD转换成新的子RDD
    • RDD操作(惰性求值)
  • <2> RDD算子
    • 1. 常见算子
    • 2. 打印元素
    • 3. Pair RDD
      • 3.1.什么是Pair RDD
      • 3.2.创建Pair RDD
      • 3.3.常见操作
        • 1)reduceByKey和 groupByKey 区别:
        • 2)常见算子
          • 常用的Transformation:
          • 常用的Action:
    • 4. 共享变量
      • 4.1. 定义
      • 4.2. 分类
      • 4.3. 广播变量(broadcast variables)
          • 4.3.1 设置
          • 4.3.2 获取
      • 4.4. 累加器 ( accumulators )
    • 4.案例
      • 1.集合的交并差
      • 2.corgroup
      • 3.最值
      • 4. 排序
          • 4.1.单一数组的排序
          • 4.2.分组元组排序
          • 4.3.求平均值
          • 4.4.求多列数据的平均值
          • 4.5.二次排序
      • Java CountWord

<1> 概述

一. 什么是RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,

  1. RDD是Spark中的抽象数据结构类型,Spark中最基本的数据抽象,实现了以操作本地集合的方式来操作分布式数据集的抽象实现
  2. 它代表一个不可变、可分区、里面的元素可并行计算的集合
  3. RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性
  4. RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
  5. RDD是Spark最核心的东西,RDD必须是可序列化的。RDD可以cache到内存中,省去了MapReduce大量的磁盘IO操作
  6. 任何数据在Spark中都被表示为RDD。从编程的角度来看,RDD可以简单看成是一个数组。和普通数组的区别是,RDD中的数据是分区存储的可以分布在不同的机器上,同时可以被并行处理
  7. 作用:Spark应用程序所做的无非是把需要处理的数据转换为RDD,然后对RDD进行一系列的变换和操作从而得到结果。

二. spark 编程模型

RDD_第1张图片

  1. RDD被表示为对象;
  2. 通过对象上的方法调用来对RDD进行转换;
  3. 最后输出结果 或是 向存储系统保存数据;
  4. RDD转换算子被称为Transformation;
  5. 只有遇到Action算子,才会执行RDD的计算(懒执行)

1. DataSource

(1)定义:spark的数据来源
(2)分类:

  1. DB(数据库)
  2. File System (文件系统)
  3. Socket (传输)
  4. Hdfs , HBase… …

2. SparkContext

(1)RDD是一个对象
(2)是Spark的第一个类的入口,负责集群的交互
(3)用于连接Spark集群,创建RDD,累加器,广播变量… …
(4)Method : Transformation (转换)
Action (动作)
(5)Spark 的物理模型
Driver 主要是对SparkContext进行配置、初始化以及关闭。初始化SparkContext是为了构建Spark应用程序的运行环境,在初始化SparkContext,要先导入一些Spark的类和隐式转换;在Executor部分运行完毕后,需要将SparkContext关闭。
RDD_第2张图片
在Executor中完成数据的处理,数据有以下几种:
1、Scala集合数据(测试)
2、文件系统、DB(SQL、NOSQL)的数据
3、RDD
4、网络
Driver : 主节点 ,主要是对SparkContext进行配置、初始化以及关闭。初始化就是构建运行环境(导入类和隐式转换)
RAM: 随机存取存储器(内存)
如果Spark集群是服务器则Driver是客户端:driver发送tasks给工作节点,worker返回结果给driver

3. Diver

RDD_第3张图片
RDD_第4张图片

(1)SparkConf

是Spark的配置类,配置项包括:master、appName、Jars、ExecutorEnv等等 (键值对形式存储)

(2)SparkEnv

a.利用Rpc协议 ---->心跳机制 传输数据
b.维护Spark的执行环境,有:serializer、RpcEnv、Block Manager、内存管理等

(3)DAGScheduler

a.高层调度器
b.将Job按照RDD的依赖关系划分成若干个TaskSet(任务集),也称为Stage(阶段,时期);再结合当前缓存情况及数据就近的原则,将Stage提交给TaskScheduler
RDD_第5张图片

(4)TaskScheduler

负责任务调度资源的分配.

(5)ScheduleBackend

负责集群资源的获取和调度。

二. RDD属性

RDD的五个特征包含四个函数和一个属性:

RDD_第6张图片

  1. def computer(split:Partition, context:TaskContext):Interator[T]
    // 对一个分片进行计算,得出一个可遍历的结果
  2. protected def getPartitions:Array[Partition]
    // 只计算一次
  3. protected def getDependencies:Seq[Dependency[_]] = deps
    //只计算一次,计算RDD对父RDD的依赖
  4. protected def getPreferredLocations(split:Partition):Seq[String] = Nil
    // 可选,制定优先位置,输入的参数是split分片,输出的结果是一组优点的节点位置
  5. @transient
    Val partitioner:Option[Partiotion] = None
    // 可选,分区的方法,针对分区的方法(哈希、RangePartitioner)类似MR中的接口控制key的去向(默认hashPartitioner)
    Spark 以一个弹性分布式数据集(RDD)的概念为中心,它是一个容错且可以执行并行操作的元素的集合。

1. 一组分片(Partition)分片

一组分片(Partition),即数据集的基本组成单位。每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目(设置的最大core数)。

2. 一个计算每个分区的函数

(2)一个计算每个分区的函数。RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据.Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。

3. 依赖关系

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

4. RDD的分片函数

(4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。

5. 一个列表

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

6. 只读的

(6)只读:RDD是只读的,要想改变RDD中的数据,只能创建一个新的RDD由一个RDD转换到另一个RDD,通过操作算子(map、filter、union、join、reduceByKey… …)实现,不再像MR那样只能写map和reduce了。

三. RDD特点

1、分区

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

1.1 原因:

要处理的原始数据很大,会被分成很多个分区,分别保存在不同的节点上。

1.2 目的:

设置合理的并行度,提高数据处理的性能。

1.3.原则:

RDD分区的一个分区原则是:
尽可能使得分区的个数,等于集群核心数目;
尽可能使同一 RDD 不同分区内的记录的数量一致;

1.4.如何分区?

Spark包含两种数据分区方式:HashPartitioner(哈希分区)RangePartitioner(范围分区),数据分区方式只作用于形式的数据

1.4.1 HashPartitioner

HashPartitioner采用哈希的方式对键值对数据进行分区。其数据分区规则为 partitionId = Key.hashCode % numPartitions,其中partitionId代表该Key对应的键值对数据应当分配到的Partition标识,Key.hashCode表示该Key的哈希值,numPartitions表示包含的Partition个数。

1.4.2 RangePartitioner

Spark引入RangePartitioner的目的是为了解决HashPartitioner所带来的分区倾斜问题,也即分区中包含的数据量不均衡问题。HashPartitioner采用哈希的方式将同一类型的Key分配到同一个Partition中,因此当某一或某几种类型数据量较多时,就会造成若干Partition中包含的数据过大问题,而在Job执行过程中,一个Partition对应一个Task,此时就会使得某几个Task运行过慢。RangePartitioner基于抽样的思想来对数据进行分区。
先Hash数据倾斜时在sample重新采样

1.5 .分区器

HashPartitioner & RangePartitioner

1.5.1 作用

决定了RDD中分区的个数;
RDD中每条数据经过Shuffle过程属于哪个分区;
reduce的个数;
注意:

  1. 只有Key-Value类型的RDD才有分区器,非Key-Value类型的RDD分区器的值是None。.
  2. 如果是对键操作,则子RDD不再继承父RDD的分区器,但是分区数会继承.
1.5.2 HashPartitioner (默认)

hashCode%分区数=余数—>决定在那个区
该分区方法保证key相同的数据出现在同一个分区中。
易发生数据倾斜.

1.5.3 RangePartitioner (sort之类)

定义:
简单的说就是将一定范围内的数映射到某一个分区内
sample采样抽样确定边界

1.5.4 什么操作会导致子RDD失去父RDD的分区方式?

如果是对键操作,则子RDD不再继承父RDD的分区器,但是分区数会继承.
使用map()算子生成的RDD,由于该转换操作理论上可能会改变元素的键(Spark并不会去判断是否真的改变了键),所以不再继承父RDD的分区器

1.5.5 多元RDD的分区操作后,子RDD如何继承分区信息?

对于两个或多个RDD的操作,生成的新的RDD,其分区方式,取决于父RDD的分区方式。如果两个父RDD都设置过分区方式,则会选择第一个父RDD的分区方式。

1.6 函数操作
1.6.1. 查看分区方式

rdd.partitioner

1.6.2. 查看分区个数

rdd.getNumPartitions == rdd.partitions.size

1.6.3. 查看分区存储规律

getElement(rdd)

1.6.4. 获得默认分区数

rdd.defsultParallelism

1.6.5. 重新定义partitioner主动使用分区

用户可通过partitionBy主动使用分区器,通过partitions参数指定想要分区的数量。
可重新定义partitioner主动使用分区
val rdd1= rdd.partitionBy(new arg.apache.spark.HashPartitioner(2))

1.6.6. 重新设置分区coalesce() 和 repartition()
coalesce() 和 repartition()
val rdd=sc.makeRDD(arr,9)	
rdd.coalesce(num,false)=rdd.coalesce(num)
注意:num的数值必须小于原分区的数量(因为是false)
rdd.repartition(num)==rdd.coalesce(num,true)
注意: num可大可小于原分区的数值

2、只读

RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD;由一个RDD转换到另一个RDD,可以通过丰富的操作算子(map、filter、union、join、reduceByKey… …)实现,不再像MR那样只能写map和reduce了。
RDD_第8张图片

3、依赖

RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系(lineage),也称之为依赖。依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的;另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。
RDD_第9张图片

3.1.窄依赖:

每个父RDD的一个Partition最多被子RDD的一个Partition所使用,(1:1 n:1)

3.2.宽依赖:

一个父RDD的Partition会被多个或所有子RDD的Partition所使用,(1:n n:n)

3.3.作用:

其一用来解决数据容错;
其二用来划分stage。

3.4.stage的划分
(1)划分:

action触发job,依照RDD依赖关系切分若干TaskSet即(stage) ; task任务处理的最小单元
划分Stage:
1.从后向前遍历,遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到Stage中;
2.每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition数量决定的
3.最后一个Stage里面的任务的类型是ResultTask,前面所有其他Stage里面的任务类型都是ShuffleMapTask
4.代表当前Stage的算子一定是该Stage的最后一个计算步骤

(2)用宽依赖划分stage原因:

相比于宽依赖,窄依赖对优化很有利,

  1. 宽依赖对应着shuffle操作,数据传输时宽依赖对应多个节点的传输
  2. 当RDD分区丢失时(某个节点故障),spark会对数据进行重算时,宽依赖重算的效用不仅在于算的多,还在于有多少是冗余的计算
    RDD_第10张图片
    b1分区丢失,则需要重新计算a1,a2和a3,这就产生了冗余计算
    (a1,a2,a3中对应b2的数据)
  3. 窄依赖能够更有效地进行失效节点的恢复,即只需重新计算丢失RDD分区的父分区,而且不同节点之间可以并行计算;
  4. 窄依赖允许在一个集群节点上以流水线的方式(pipeline)计算所有父分区
3.5. 查看依赖
3.5.1. 查看依赖长度

rdd1.dependencies.size

3.5.2. 查看所有父RDD

rdd1.dependencies.collect

3.5.3. 查看第一个父RDD数据并转换格式

rdd1.dependencies(0).rdd.collect.asInstanceOf[Array[Int]]

4、持久化(缓存)

可以控制存储级别(内存、磁盘等)来进行持久化。如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。

4.1. cache持久化

cache实际上是persist的一种简化方式,是一种懒执行的,执行action类算子才会触发,cahce后返回值要赋值给一个变量,下一个job直接基于变量进行操作。

4.2. persist:

可以指定持久化的级别。最常用的是MEMORY_ONLY,MEMORY_AND_DISK,MEMORY_ONLY_SER,MEMORY_AND_DISK_SER。”_2”表示有副本数。

4.3. cache和persist

cache和persist算子后不能立即紧跟action算子。因为rdd.cache().count() 返回的不是持久化的RDD,而是一个数值了。

4.4. checkpoint

checkpoint将RDD持久化到磁盘,还可以切断RDD之间的依赖关系。
checkpoint 的执行原理:
(1) 当RDD的job执行完毕后,会从finalRDD从后往前回溯。
(2) 当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
(3) Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。
优化:
对RDD执行checkpoint之前,最好对这个RDD先执行cache,这样新启动的job只需要将内存中的数据拷贝到HDFS上就可以,省去了重新计算这一步。

四. 持久化

1.定义:

spark中控制算子也是懒执行的,需要Action算子触发才能执行,主要是为了对数据进行缓存。可以构建迭代式算法和快速交互式查询.

2.容错机制

当存储于内存的数据由于内存不足而被删除时,RDD的缓存的容错机制执行,丢失的数据会被重算。RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition.

2.1.原因

为了防止计算失败后从头开始计算造成的大量开销,RDD会checkpoint计算过程的信息,这样作业失败后从checkpoint点重新计算即可,提高效率过对RDD启动Checkpoint机制来实现容错和高可用;

2.2.checkpoint使用场景
  1. 会被重复使用的(但是)不能太大的RDD需要persist或者cache 。
  2. 运算时间很长或运算量太大才能得到的 RDD,computing chain 过长或依赖其他 RDD 很多的 RDD。
  3. 针对整个RDD计算链条中特别需要数据持久化的环节(后面会反复使用当前环节的RDD)使用checkpoint
2.3.checkpoint和cache,persist的区别
  1. 缓存后checkpoint会斩断RDD的依赖,cache,persist依然有依赖
  2. 磁盘或内存可能被清理,但是checkpoint的数据通常保存到hdfs上,放在了高容错文件系统。
  3. rdd.persist() 将 RDD 的 partition 持久化到磁盘,但该 partition 由 blockManager 管理。一旦 driver program 执行结束,也就是 executor 所在进程stop,blockManager 也会 stop,被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除)。而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉,是一直存在的。
2.4.checkpoint操作
  1. 创建RDD
    val cktest = sc.parallelize(1 to 100000)
  2. 设置存储路径(HDFS或者本地路径)
    sc.setCheckpointDir("/tmp/checkpoint")–>hdfs的
  3. 执行checkpoint
    cktest2.checkpoint
    // checkpoint是lazy操作 检查是否缓存
    cktest2.isCheckpointed
    // 再次查看RDD的依赖关系
    cktest2.dependencies.collect
    //查看RDD所依赖的checkpoint文件
    cktest2.getCheckpointFile

3.RDD控制算子

(1) cache :

每一个节点都将把计算的分片结果缓存到内存rdd.cache()==rdd.persist(MERMORY_ONLY)

(2) persist

①指定参数缓存在缓存或磁盘
②rdd.unpersist() 把持久化的RDD从缓存中移除;

(3) checkpoint

缓存在hdfs和磁盘,并切断依赖
开头要写sc.setCheckPointDir()
在执行checkpoint()

4.控制算子的使用

  1. 如果多个动作需要用到某个 RDD,而它的计算代价又很高,使用缓存
  2. 注意:当进行了RDD0→RDD1→RDD2的计算作业,计算结束时,RDD1就已经缓存在系统中了,如果在进行RDD0 →RDD1→RDD3的计算作业时,只须进行RDD1→RDD3的计算,RDD1已经在缓存中0->1的转换不会重复进行
  3. 使用persist()方法对一个RDD标记为持久化,并不会马上计算生成RDD并把它持久化,而是要等到遇到第一个行动操作触发真正计算以后,才会把计算结果进行持久化

5.pesist

MEMORY_ONLY 将RDD 作为反序列化的的对象存储JVM中。如果RDD不能被内存装下,一些分区将不会被缓存,并且在需要的时候被重新计算,按照LRU(Least recently used,最近最少使用)原则替换缓存中的内容。默认的缓存级别 等于cache()
MEMORY_AND_DISK 将RDD作为反序列化的的对象存储在JVM中。如果RDD不能被与内存装下,超出的分区将被保存在硬盘上,并且在需要时被读取(优先存取内存)
MEMORY_ONLY_SER 将RDD作为序列化的的对象进行存储(每一分区占用一个字节数组)。通常来说,这比将对象反序列化的空间利用率更高,尤其当使用fast serializer,但在读取时会比较占用CPU
MEMORY_AND_DISK_SER 与MEMORY_ONLY_SER 相似,但是把超出内存的分区将存储在硬盘上而不是在每次需要的时候重新计算
DISK_ONLY 只将RDD分区存储在硬盘上
DISK_ONLY_2 、等带2的 与上述的存储级别一样,但是将每一个分区都复制到集群的两个结点上

6.如何使用缓存

  1. 如果RDD的数据可以很好的兼容默认存储级别(MEMORY_ONLY),那么优先使用它,这是CPU工作最为高效的一种方式,可以很好地提高运行速度;
  2. 如果(1)不能满足,则尝试使用MEMORY_ONLY_SER,且选择一种快速的序列化工具,也可以达到一种不错的效果;
  3. 由于数据量大,一般不会持久化到磁盘,除非计算是非常“昂贵”的或者计算过程会过滤掉大量数据,因为重新计算一个分区数据的速度可能要高于从磁盘读取一个分区数据的速度;
  4. 备份:如果需要快速的失败恢复机制,则使用备份的存储级别,如MEMORY_ONLY_2、MEMORY_AND_DISK_2;虽然所有的存储级别都可以通过重新计算丢失的数据实现容错,但是缓存机制使得大部分情况下应用无需中断,即数据丢失情况下,直接使用缓存数据,而不需要重新计算数据的过程;
  5. 如果处于大内存或多应用的场景下,OFF_HEAP可以带来以下的好处:它允许Spark Executors可以共享Tachyon的内存数据;它很大程序上减少JVM垃圾回收带来的性能开销;Spark Executors故障不会导致数据丢失。
    OFF_HEAP与MEMORY_ONLY_SER类似,但将数据存储在 堆外内存中。这需要启用堆外内存。

五. 创建RDD

1. 合并行化创建 (通过scala集合创建)

通过集合并行化方式创建RDD,适用于本地测试,做实验
scala中的本地集合 -->Spark RDD
spark-shell --master spark://master:7077

  1. parallelize(数据集)方法
    scala> val arr = Array(1,2,3,4,5)
    scala> val rdd = sc.parallelize(arr)
  2. makeRDD(数据集)方法
    scala> val arr = Array(1,2,3,4,5)
    scala> val rdd = sc.makeRDD(arr)
    scala> rdd.collect
    res0: Array[Int] = Array(1, 2, 3, 4, 5)

2. 文件系统 , 比如 HDFS

  1. 读取HDFS文件系统(默认)
    val rdd2 = sc.textFile(“hdfs://master:9000/words.txt”)
    val line=sc.textFile(“words.txt”)
    这两个是相等的
  2. 读取本地文件
    val rdd2 = sc.textFile(“file:///root/words.txt”)
    scala> val rdd2 = sc.textFile(“file:root/word.txt”)
    scala> rdd2.collect
    res2: Array[String] = Array(hadoop hbase java, hbase java spark, java, hadoop hive hive, hive hbase)

3. 从父RDD转换成新的子RDD

调用 Transformation 类的方法,生成新的 RDD只要调用transformation类的算子,都会生成一个新的RDD。RDD中的数据类型,由传入给算子的函数的返回值类型决定.
注意:action类的算子,不会生成新的 RDD
scala> rdd.collect
res3: Array[Int] = Array(1, 2, 3, 4, 5)
scala> val rdd = sc.parallelize(arr)
scala> val rdd2 = rdd.map(_*100)
scala> rdd2.collect
res4: Array[Int] = Array(100, 200, 300, 400, 500)
使用 rdd.partitions.size 查看分区数量
scala> rdd.partitions.size
res7: Int = 4
scala> rdd2.partitions.size
res8: Int = 4

RDD操作(惰性求值)

RDD_第11张图片
RDD是惰性求值的,整个转换过程只是记录了转换的轨迹,没有发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作.

<2> RDD算子

1. 常见算子

这是一个全的RDD算子案例
http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html

动作 含义
reduce(func) 通过func函数对RDD中的所有元素处理,传入函数必须满足交换律和结合律(传入两个参数输入返回一个值)
collect() 在驱动程序中,以数组的形式返回数据集的所有元素
count() 返回RDD的元素个数
first() 返回RDD的第一个元素(类似于take(1))
take(n) 返回一个由数据集的前n个元素组成的数组
takeSample(withReplacement,num, [seed]) 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子初始值
saveAsTextFile(path) 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它封装为文件中的文本
saveAsSequenceFile(path) 将数据集中的元素以二进制的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
countByKey() 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func) 在数据集的每一个元素上,运行函数func进行更新。

2. 打印元素

一般会采用语句rdd.foreach(println)

  1. 本地模式(local)
    会打印出一个RDD中的所有元素
  2. 集群模式
    1)Driver Program中的stdout是不会显示打印语句的这些输出内容的;在worker节点上执行打印语句是输出到worker节点的stdout中,而不是输出到任务控制节点Driver Program中.需要到worker节点日志查看
    2)rdd.take(num).foreach(println);
    3)rdd.collect().foreach(println)

3. Pair RDD

3.1.什么是Pair RDD

键值对(K,V),RDD的数据集,RDD的键值对操作

3.2.创建Pair RDD

(1)
val list = List(“Hadoop”,“Spark”,“Hive”,“Spark”)
val rdd = sc.parallelize(list)
val pairRDD = rdd.map(word => (word,1))
val pairRDD = rdd.map((,1))
(2)
val lines = sc.textFile(“file://+Path”)
val pairRDD = lines.flatMap(
.split(" ")).map((_,1))

3.3.常见操作

1)reduceByKey和 groupByKey 区别:

reduceByKey:
RDD_第12张图片

用于对每个key对应的多个value进行merge操作,它能够在本地先进行merge操作
每个分区先进行merge操作然后再所有分区进行汇总merge操作,reduceByKey的效率高

groupByKey:
RDD_第13张图片
对每个key进行操作,但只生成一个sequence。
所有分区进行汇总再merge操作,集群节点之间的开销很大

2)常见算子

常用的Transformation:
转换 含义
map(func) 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
filter(func) 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
flatMap(func) 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
mapPartitions(func) 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func) 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U]sample(withReplacement, fraction, seed)根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子
union(otherDataset) 对源RDD和参数RDD求并集后返回一个新的RDD
intersection(otherDataset) 对源RDD和参数RDD求交集后返回一个新的RDD
distinct([numTasks])) 对源RDD进行去重后返回一个新的RDD
groupByKey([numTasks]) 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD
reduceByKey(func, [numTasks]) 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) 先按分区聚合 再总的聚合 每次要跟初始值交流 例如:aggregateByKey(0)(+,+) 对k/y的RDD进行操作
sortByKey([ascending], [numTasks]) 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks]) 与sortByKey类似,但是更灵活 第一个参数是根据什么排序 第二个是怎么排序 false倒序 第三个排序后分区数 默认与原RDD一样
join(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD 相当于内连接(求交集)
cogroup(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
cartesian(otherDataset) 两个RDD的笛卡尔积 的成很多个K/V
pipe(command, [envVars]) 调用外部程序
coalesce(numPartitions) 重新分区 第一个参数是要分多少区,第二个参数是否shuffle 默认false 少分区变多分区 true 多分区变少分区 false
repartition(numPartitions) 重新分区 必须shuffle 参数是要分多少区 少变多
repartitionAndSortWithinPartitions(partitioner) 重新分区+排序 比先分区再排序效率高 对K/V的RDD进行操作
foldByKey(zeroValue)(seqOp) 该函数用于K/V做折叠,合并处理 ,与aggregate类似 第一个括号的参数应用于每个V值 第二括号函数是聚合例如:+
combineByKey 合并相同的key的值 rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
partitionBy(partitioner) 对RDD进行分区 partitioner是分区器 例如new HashPartition(2)
cache、persist RDD缓存,可以避免重复计算从而减少时间,区别:cache内部调用了persist算子,cache默认就一个缓存级别MEMORY-ONLY ,而persist则可以选择缓存级别
Subtract(rdd) 返回前rdd元素不在后rdd的rdd
leftOuterJoin leftOuterJoin类似于SQL中的左外关联left outer join,返回结果以前面的RDD为主,关联不上的记录为空。只能用于两个RDD之间的关联,如果要多个RDD关联,多关联几次即可。
rightOuterJoin rightOuterJoin类似于SQL中的有外关联right outer join,返回结果以参数中的RDD为主,关联不上的记录为空。只能用于两个RDD之间的关联,如果要多个RDD关联,多关联几次即可
subtractByKey substractByKey和基本转换操作中的subtract类似只不过这里是针对K的,返回在主RDD中出现,并且不在otherRDD中出现的元素
常用的Action:

触发代码的运行,我们一段spark代码里面至少需要有一个action操作。

动作 含义
reduce(func) 通过func函数聚集RDD中的所有元素,这个功能必须是课交换且可并联的
collect() 在驱动程序中,以数组的形式返回数据集的所有元素
count() 返回RDD的元素个数
first() 返回RDD的第一个元素(类似于take(1))
take(n) 返回一个由数据集的前n个元素组成的数组
takeSample(withReplacement,num, [seed]) 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子
takeOrdered(n, [ordering])
saveAsTextFile(path) 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
saveAsSequenceFile(path) 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
saveAsObjectFile(path)
countByKey() 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func) 在数据集的每一个元素上,运行函数func进行更新。
aggregate 先对分区进行操作,在总体操作

4. 共享变量

4.1. 定义

Spark在集群的多个不同节点的多个任务上并行运行一个函数时,把一个变量设为共享变量,就可以在不同节点使用其变量的值. 注意: 共享变量只读

4.2. 分类

广播变量(broadcast variables)
累加器(accumulators)

4.3. 广播变量(broadcast variables)

Spark的Action操作会跨越多个阶段(stage),对于每个阶段内的所有任务所需要的公共数据,Spark都会自动进行广播

4.3.1 设置

val broadcastVar = sc.broadcast(Array(1, 2, 3))

4.3.2 获取

broadcastVar.value

4.4. 累加器 ( accumulators )

sc.longAccumulator()
sc.doubleAccumulator()

4.案例

1.集合的交并差

交集: 数据集1.intersection(数据集2)
并集: 数据集1.union(数据集2)
差集: 数据集1.subtract(数据集2)

2.corgroup

cogroup(otherDataset, [numTasks])
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD, 也就是相同K的数据集不同为空(k,(,v2))或者(k,(v1,)),相同的如统join类似(k,(v1,v2)) ,numTasks为并发的任务数
RDD_第14张图片

3.最值

// 同时找最大、最小值。缺点:不适合在大数据量情况下运行
val rdd1 = sc.makeRDD((1 to 20).toArray)
rdd1.map((1,_)).groupByKey()
.map(x=>(x._2.min, x._2.max)).collect
// 对于仅有一个元素(基本数据类型)的RDD有更简便的方法
rdd1.stats / rdd1.max / rdd1.min / rdd1.mean

4. 排序

4.1.单一数组的排序
val out=sc.textFile().filter(_.trim.size!=0)		//空行排除
		.map(_.split(“ ”).trim.toInt)			//拆分并去除首尾空格
		.sortBy(_,false)						//排序方式
		.take(num)							//取前几
		.foreach(println)
4.2.分组元组排序
val out=sc.textFile()
		.filter(_.trim.size!=0)
		.map(_.split(“ “).trim)
		.map(x=>(x(0),x(1)))	//变为元组
		.groupByKey()				//生成(k,(v1,v2..))
		//排序
		.map(x=>(x._1,x._2.toList.sorted.reverse.take(3)))
		.collect	
		
val rdd1 = sc.textFile("").map(line => {
		  val value = line.split(" ")
		  (value(0), value(1).toDouble)
		}).groupByKey()	
		.map(x=>(x._1,x._2.toList.sorted.reverse.take(3)))
		.collect	

这两个相同只不过map拆开而已

4.3.求平均值

val ar=Array((“spark”,2),(“hadoop”,6),(“hadoop”,4),(“spark”,6))
val rdd=sc.makeRDD(ar)

  1. 使用(groupByKey)
    rdd.groupBykey()
    .map(x=>(x._1,x._2.sum.toDouble/x._2.size))
    .collect
  2. 使用(reduceByKey)
    mapValues增加标志位并求和来查看相同的key的个数
    rdd.mapValues(x=>(x,1))
    .reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))
    .mapValues(x=>(x._1.toDouble/x._2))
  3. 使用(foldByKey)
    和reduceByKey类似
    rdd.mapValues((_,1))
    .foldByKey((0,0))((x,y)=>(x._1+y._1,x._2+y._2))
    .mapValues(x=>(x._1.toDouble/x._2))
4.4.求多列数据的平均值
  1. 生成一个RDD,每行包含2个数字,要计算这两列数据的平均值

     import scala.util.Random
     val random = new Random(100)
     val arr =(1 to 100).map(x=>(random.nextInt(1000), random.nextInt(1000))).toArray
     val rdd = sc.makeRDD(arr)
    
  2. 使用stat分别计算,算法简单,效率低)

     val (firstAvg,secondAvg)=(rdd.map(_._1).stats.mean,
     rdd.map(_._2).stats.mean)
     如果有一列就得计算一次效率低
    
  3. (利用了Vector,高效。需要借助外部的数据结构):
    Vector数组相加里面相同下标的元素相加(仅有它是可以的),但也要加上标志位好知道个数

     import breeze.linalg.Vector
     val (sums, count)=
     rdd.map(x=>((Vector(x._1.toDouble, x._2.toDouble), 1.0)))
     .reduce((x,y) => (x._1+y._1,x._2 + y._2))
     val means = sums/count
    
  4. (自定义方法实现:数组累加):

     不使用Vector数组的相加,使用常用数组拉链的方法,
     再map映射成一个内部元素相加和的数组
     但也要加标志位1看个数
     def arrAdd(x: Array[Int], y:Array[Int]): Array[Int] = x.zip(y).map(x=>x._1+x._2)
     val (sums, count) =
      rdd.map(x=>(Array(x._1, x._2), 1)) //加标志位
     .reduce((x,y) => (arrAdd(x._1, y._1), x._2+y._2)) 
     //数组执行方法,数量相加
     val means = sums.map(_.toDouble/count)
    
4.5.二次排序
  1. 数据来源

     import scala.util.Random
     val random = new Random(100)
     val arr = (1 to 10000).map(x=>(random.nextInt(100), random.nextInt(100)))
    
  2. 自定义函数继承Ordered接口或实现Orderring方法

     case class MyObject(x:Int, y:Int) extends Ordered[MyObject]{
       def compare(other:MyObject):Int = {
         if (x - other.x !=0) x - other.x   else y - other.y  }}
    
  3. 调用方法排序

     val rdd = sc.makeRDD(arr)
     val rdd1 = rdd.map(pair=>(new MyObject(pair._1, pair._2),pair))
     val sorted = rdd1.sortByKey(false)
     注意:二次排序后的结果写成文件,文件内及文件间的数据是有顺序的。但是文件间的顺序是不保证的!!!
     val lines = sc.textFile(" ") 
     val rdd1  = lines.map(line=>(new MyObject(line.split(" ")(0).toInt, line.split(" ")(1).toInt),line) )  //元组((k0,k1),k)
     val sorted = rdd1.sortByKey(false)       
     val result = sorted.map(sortedLine =>sortedLine._2)  //要k1     
     result.collect().foreach (println)
    

Java CountWord

  1. 创建SparkConf
    SparkConf conf =new SparkConf().setAppName().setMaster();
  2. 创建JavaSparkContext(这是Java)
    JavaSparkContext sc=new JavaSparkContext(conf);
  3. 创建RDD
  4. JavaRDD<类型> 变量=sc.textFile()
    JavaRDD lines=sc.textFile();
    注意:拆分行时读取的行成为字符串数组(使用Arrays.asList()转成列表操作切分)数组无法拆分
    JavaRDD words=lines.
    flatMap(line->Arrays.asList(line.split(“ ”)).iterator());
  5. 变成JavaPairRDD 键值对使用
    mapToPair()方法和new Tuple2(s,1)
    JavaPairRDD pairs=
    words.mapToPair(s->new Tuple2(s,1))
  6. 计算 注意:在Java中没有占位符 (_) 这个说法
    JavaPairRDD count=
    pairs.reduceByKey((a,b)->a+b)
  7. 结果存储到List中foreach遍历
    List(Tuple2) list=count.collect();
    for(Tuple2 tuple : list){
    System.out.println( tuple._1+””+tuple._2);
    }

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

你可能感兴趣的:(BigData,#,spark,RDD,RDD,详细,RDD,算子)