(二)RDD算子和共享变量

rdd算子介绍

1:rdd介绍

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

1:rdd创建

可以从现有数据集(list,map等),外部加载(hdfs,hbase,kafka,hive)等,对于重复使用的RDD可以进行持久化操作

1:现有数据集

parallelize可以指定分区数,决定后续执行的并行度

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
val rdd = sc.makeRDD(Array(1,2,3,4,5,6,7,8))
2:加载

支持在目录,压缩文件和通配符上运行,如"/my/directory/*.txt"

外部存储系统:val rdd = sc.textFile("hdfs://myha01/spark/wc/input/words.txt")

scala的spark操作有更多的扩展,详情看RDD编程指南

2:rdd操作

RDD支持两种类型的操作Transformations和action
Transformations:是懒加载的,只有当发生一个要求返回结果给 Driver 的动作时,这些转换才会真正运行。

1:Transformations转换算子

filter:过滤符合条件的记录数,true的保留、false的过滤
map:将RDD中通过map中的每条数据应用到该函数(1进1出)
mapPartition:执行结果与map相同,但是可以一次遍历整个patition
mapPartitionWithIndex:类似于mapPartitions,除此之外还会携带分区的索引值
mapToPair:返回(k,v)格式的RDD
flatMap:对RDD中的数据项,先map再flat(1进多出)
flatMapToPair:对RDD中的数据项,先map再flat,在返回(k,v)格式的RDD
sample:抽样,传进一个比例值,可以选择传入参数决定是否有放回的抽样
sortBy/sortByKey:作用在K,V格式的RDD上,对Key进行升序或降序排序
join:作用在K,V格式的RDD上。根据K进行连接,对(K,V)join(K,W)返回(K,(V,W))
leftOuterJoin:作用在K,V格式的RDD上。根据K进行连接,对(K,V)join(K,W)返回(K,(V,W)),左边中的key为主,只显示左边中存在的key值
rightOuterJoin:作用在K,V格式的RDD上。根据K进行连接,对(K,V)join(K,W)返回(K,(V,W)),右边中的key为主,只显示右边中存在的key值
fullOuterJoin:作用在K,V格式的RDD上。根据K进行连接,对(K,V)join(K,W)返回(K,(V,W)),两边的key值都显示
union:合并两个数据集。两个数据集的类型要一致。返回新的RDD的分区数是合并RDD分区数的总和
intersection:交集
subtract:差集
distinct:去重
cogroup:当调用类型(K,V)和(K,W)的数据上时,返回一个数据集(K,(Iterable,Iterable))
repartition:增加或减少分区。会产生shuffle
coalesce:常用来减少分区,第二个参数是减少分区的过程中是否产生shuffle。如果用来增加分区,必须设置为true
groupByKey:作用在K,V格式的RDD上。根据Key进行分组。作用在(K,V),返回(K,Iterable )
zip:将两个RDD中的元素(KV格式/非KV格式)变成一个KV格式的RDD,两个RDD的个数必须相同
zipWithIndex:该函数将RDD中的元素和这个元素在RDD中的索引号(从0开始)组合成(K,V)对。

2:Action触发算子(行动算子)

reduceByKey:将相同的Key根据逻辑进行处理
count:返回数据集中的元素数,会在结果计算完成后返回到Driver端
countByKey: 作用到K,V格式的RDD上,根据Key计数相同Key的数据集元素
countByValue:根据数据集每个元素相同的内容来计数。返回相同内容的元素对应的条数
Collect:将计算结果回收到Driver端
foreach:循环遍历数据集中的元素,运行相应的逻辑
take:返回一个包含数据集前n个元素的集合take(n)
first:返回数据集中的第一个元素first=take(1)
foreachPartition:遍历的是一个patition上的数据
print()	 输出算子
saveAsTextFiles(prefix, [_suffix_])		将此 DStream 的内容另存为文本文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsObjectFiles(prefix, [_suffix_])	将此 DStream 的内容另存为序列化 Java 对象的 SequenceFiles。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsHadoopFiles(prefix, [_suffix_])	将此 DStream 的内容另存为 Hadoop 文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
foreachRDD(func)						对从流中生成的每个 RDD 应用函数 func 的最通用的输出运算符。此功能应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或将其通过网络写入数据库。请注意,函数 func 在运行流应用程序的 driver 进程中执行,通常会在其中具有 RDD 动作,这将强制流式传输 RDD 的计算和调试很有用。
saveAsTextFiles(prefix, [_suffix_])		将此 DStream 的内容另存为文本文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsObjectFiles(prefix, [_suffix_])	将此 DStream 的内容另存为序列化 Java 对象的 SequenceFiles。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsHadoopFiles(prefix, [_suffix_])	将此 DStream 的内容另存为 Hadoop 文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
foreachRDD(func)						对从流中生成的每个 RDD 应用函数 func 的最通用的输出运算符。此功能应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或将其通过网络写入数据库。请注意,函数 func 在运行流应用程序的 driver 进程中执行,通常会在其中具有 RDD 动作,这将强制流式传输 RDD 的计算

3:一些输出算子

输出操作允许将 DStream 的数据推送到外部系统,如数据库或文件系统

print()									在运行流应用程序的 driver 节点上的DStream中打印每批数据的前十个元素。这对于开发和调试很有用。
saveAsTextFiles(prefix, [_suffix_])		将此 DStream 的内容另存为文本文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsObjectFiles(prefix, [_suffix_])	将此 DStream 的内容另存为序列化 Java 对象的 SequenceFiles。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
saveAsHadoopFiles(prefix, [_suffix_])	将此 DStream 的内容另存为 Hadoop 文件。每个批处理间隔的文件名是根据 前缀 和 后缀_:"prefix-TIME_IN_MS[.suffix]"_ 生成的。
foreachRDD(func)						对从流中生成的每个 RDD 应用函数 func 的最通用的输出运算符。此功能应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或将其通过网络写入数据库。请注意,函数 func 在运行流应用程序的 driver 进程中执行,通常会在其中具有 RDD 动作,这将强制流式传输 RDD 的计算

4:常见算子使用

1:foreachRDD

此功能应将每个 RDD 中的数据推送到外部系统,例如将 RDD 保存到文件,或将其通过网络写入数据库。请注意,函数 func 在运行流应用程序的 driver 进程中执行。也就是spark的闭包机制导致的(闭包是执行者在RDD上执行其计算时必须可见的那些变量和方法。此闭包被序列化并发送给每个执行器。
1:错误的代码示例
这是不正确的,因为这需要将连接对象序列化并从 driver 发送到 worker。这种连接对象很少能跨机器转移。此错误可能会显示为序列化错误(连接对象不可序列化),初始化错误(连接对象需要在 worker 初始化)等。

dstream.foreachRDD(rdd -> {
  Connection connection = createNewConnection(); // executed at the driver,运行在drive端
  rdd.foreach(record -> {
    connection.send(record); // executed at the worker,运行在worker端
  });
});

2:正确的示例
正确的解决方案是在 worker 创建连接对象。但是创建和销毁每个记录的连接对象可能会引起不必要的高开销,并可显着降低系统的总体吞吐量,因此可用 rdd.foreachPartition代替 rdd.foreach

dstream.foreachRDD(rdd -> {
  rdd.foreachPartition(partitionOfRecords -> {
    Connection connection = createNewConnection();
    while (partitionOfRecords.hasNext()) {
      connection.send(partitionOfRecords.next());
    }
    connection.close();
  });
});

3:优化
通过连接池进一步优化

dstream.foreachRDD(rdd -> {
  rdd.foreachPartition(partitionOfRecords -> {
    // ConnectionPool is a static, lazily initialized pool of connections
    Connection connection = ConnectionPool.getConnection();
    while (partitionOfRecords.hasNext()) {
      connection.send(partitionOfRecords.next());
    }
    ConnectionPool.returnConnection(connection); // return to the pool for future reuse
  });
});
3:reduceByKey 和 groupByKey

reduceByKey 和 groupByKey都存在shuffle操作,但是reduceByKey可以在shuffle之前对分区内相同key的数据集进行预聚合(combine),这样会减少落盘的数据量,而groupByKey只是进行分组,不存在数据量减少的问题,reduceByKey性能比较高。
reduceByKey其实包含分组和聚合的功能;groupByKey只能分组,不能聚合,所以在分组聚合的场合下,推荐使用reduceByKey,如果仅仅是分组而不需要聚合,那么还是只能使用groupByKey。

5:rdd持久化

你能通过 persist() 或者 cache() 方法持久化一个rdd,持久化级别是内存和磁盘的利用。默认MEMORY_ONLY

3:rdd的宽窄依赖

RDD 和它依赖的父 RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)

  • 窄依赖指的是每一个父 RDD 的 Partition 最多被子 RDD 的一个 Partition 使用
    函数有:map, filter, union, join(父 RDD是 hash-partitioned ), mapPartitions, mapValues
  • 宽依赖指的是多个子 RDD 的 Partition 会依赖同一个父 RDD 的 Partition
    宽依赖的函数有:groupByKey、partitionBy、reduceByKey、sortByKey、join(父 RDD 不是 hash-partitioned )

3.1:DAG有向无环图

DAG(Directed Acyclic Graph)叫做有向无环图,根据 RDD 之间的依赖关系的不同将 DAG 划分成不同的 Stage,

1:stage划分
窄依赖,partition 的转换处理在 Stage 中完成计算。
宽依赖,由于有 Shuffle 的存在,只能在 parent RDD 处理完成后,才能开始接下来的计算,因此宽依赖是划分 Stage 的依据。
spark 划分 stage 的整体思路是:从后往前推,遇到宽依赖就断开,划分为一个 stage;遇到窄依赖就将这个 RDD 加入该 stage 中。

3.2:shuffle混洗

它必须从所有分区读取以找到所有键的所有值,然后将各个分区的值汇总在一起以计算每个键的最终结果-这称为shuffle。
导致操作:重新分区操作 repartition和coalesce;ByKey”操作,比如(除计数)groupByKey和reduceByKey; 加入操作,如cogroup和join。

1:shuffle影响

混洗操作会消耗大量的堆内存,因为它们在转移它们之前或之后采用内存中的数据结构来组织记录

4:共享变量

共享变量是在driver端定义的变量会分享到每个task任务。为了减小存储空间,可以使用广播变量到每个执行器executor.

4.1:广播变量

注意:
1:变量一旦被定义为一个广播变量,那么这个变量只能读,不能修改
2:广播变量只能在 Driver 端定义,不能在 Executor 端定义

1:定义

定义:
val a = 3
val broadcast = sc.broadcast(a)
还原:
val c = broadcast.value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值