spark的架构角色
Master角色, 管理整个集群的资源
Worker角色, 管理单个服务器的资源
Driver角色, 单个Spark任务在运行的时候的工作
Executor角色,单个任务运行的时候的工作者
spark的StandAlone模式
原理
Master和Worker角色以独立进程的形式存在,并组成Spark运行时环境(集群)
Spark角色分布
Master:
Master进程
Worker:
worker进程
Driver:
以线程运行在Master中
Executor:
以线程运行在Worker中
如何提交Spark应用
bin/spark-submit --master spark://hadoop102:7077
4040端口
是单个程序运行的时候绑定的端口可供查看本任务运行情况
8080端口
Master端口
18080端口
历史服务器端口
job、Stage、Task的关系
一个Spark程序会被分成多个子任务job运行,每个job会分成多个stage来运行,每一个Stage内会分出多个线程来执行具体的任务
StandAlone HA原理
基于zookeeper做状态的维护,开启多个Master进程,一个作为活跃,其他的作为备份,当活跃宕机,备份Master进行接管
Spark on Yarn
本质
Master角色由YARN的ResourceManager担任
Worker角色由YARN的NodeManager担任
Driver角色运行在YARN容器内 或 提交任务的客户端进程中
Executor运行在YARN提供的容器内
部署模式
Cluster模式
Driver运行在容器内部
性能高
Client模式
Driver运行在客户端程序进程中
可以在客户端直接看输出
Why on YARN
提高资源利用率,在已有YARN的场景下让Spark收到YARN的调度可以更好的管控资源提高利用率
ResourceManager(资源)和Driver(计算)之间的解耦合靠的就是ApplicationMaster。
Spark-RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。
- RDD有装饰者设计模式
- RDD的数据处理方式类似于IO流,而且是懒加载,调用collect才会执行业务逻辑操作
-
弹性
存储的弹性:内存与磁盘的自动切换;
容错的弹性:数据丢失可以自动恢复;
计算的弹性:计算出错重试机制;
分片的弹性:可根据需要重新分片。
-
分布式:数据存储在大数据集群不同节点上
-
数据集:RDD 封装了计算逻辑,并不保存数据
-
数据抽象:RDD 是一个抽象类,需要子类具体实现
-
不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD 里面封装计算逻辑
-
可分区、并行计算
核心属性
分区列表
RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性
分区计算函数
Spark在计算时,是使用分区函数对每一个分区进行计算
RDD 之间的依赖关系
RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系
分区器
当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区
首选位置
计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
RDD创建
从内存中创建
从集合中创建RDD,Spark 主要提供了两个方法:parallelize 和 makeRDD
makeRDD方法在底层实现时其实就是调用了rdd对象的parallelize方法
package com.topview.bigdata.spark.core.rdd.builder
import org.apache.spark.{SparkConf, SparkContext}
object rdd_01_Memory {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
// TODO 创建环境
//从内存中创建RDD, 将内存中集合的数据作为处理的数据源
val seq = Seq[Int](1,2,3,4)
//parallelize 并行
// sc.parallelize(seq).collect().foreach(println)
//makeRDD方法在底层实现时其实就是调用了rdd对象的parallelize方法
sc.makeRDD(seq).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
从文件中创建
sc.textfile( path )
可以写绝对路径和相对路径
也可以写目录,读取目录下所有文件,也可以用通配符 *
package com.topview.bigdata.spark.core.rdd.builder
import org.apache.spark.{SparkConf, SparkContext}
object rdd_02_file {
def main(args: Array[String]): Unit = {
val sparConf = new SparkConf().setMaster("local").setAppName("wordCount")
val sc = new SparkContext(sparConf)
//可以写绝对路径和相对路径
val rdd1 = sc.textFile("E:\\JavaProject\\topview-classes\\input\\word.txt")
val rdd2 = sc.textFile("input/word.txt")
// rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
// 通配符路径
// input/w*.txt
sc.stop()
}
}
从文件读取数据并且识别数据从哪来
sc.wholeTextFiles
sc.wholeTextFiles("input/word.txt").
collect().
foreach(println)
结果(是一个元组,第一个元素是路径)
(file:/E:/JavaProject/topview-classes/input/word.txt,hello hello word
word
spark 黄国杰
黄国杰 黄国杰)
分区
分区的设定
从内存中读取
makeRDD方法可以传递第二个参数,这个参数表示分区的数量
第二个参数不传则用默认值 :
从配置对象中获取配置参数:spark.default.parallelism,可以在sparkconf设置
如果获取不到就是用当前运行环境的最大可用核数
package com.topview.bigdata.spark.core.rdd.builder
import org.apache.spark.{SparkConf, SparkContext}
object rdd_03_Memory_Par {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
// sparkConf.set("spark.default.parallelism","5")
val sc = new SparkContext(sparkConf)
// TODO 创建环境
//RDD的并行度和分区
//makeRDD方法可以传递第二个参数,这个参数表示分区的数量
//第二个参数不传则用默认值 :
//从配置对象中获取配置参数:spark.default.parallelism
//如果获取不到就是用当前运行环境的最大可用核数
val rdd = sc.makeRDD(List(1, 2, 3, 4))
//将处理的数据保存为分区文件
rdd.saveAsTextFile("output")
// TODO 关闭环境
sc.stop()
}
}
从文件中读取(重点)
读取文件数据时,数据是按照Hadoop 文件读取的规则进行切片分区,而切片规则和数据读取的规则有些差异,具体看代码
import org.apache.spark.{SparkConf, SparkContext}
object rdd_02_file_par {
def main(args: Array[String]): Unit = {
val sparConf = new SparkConf().setMaster("local").setAppName("wordCount")
val sc = new SparkContext(sparConf)
//textFile默认也可以设定分区
// minPartition : 最小分区数量
// 源码中 math.min(defaultParallelism, 2) defaultParallelism是16个
// 默认两个
//也可以通过第二个参数指定分区数
//spark读取文件,底层使用的是Hadoop的读取方式,注意不是分区方式
//分区数量的计算方式
//假设文件总大小 totalSize = 7
//底层计算 goalSize = 7 / 2(设定的分区数) = 3(byte) 即是每个分区放3byte
//根据Hadoop的1.1倍
//7 / 3 = 2 .... 1(1.1) + 1 = 3分区
//所以最后是三分区,而不是设定的二分区
sc.textFile("input/word.txt",2).collect().foreach(println)
sc.stop()
}
}
分区数据的分配
1. 数据以行为单位进行读取
spark读取文件,底层使用的是Hadoop的读取方式,所以是一行一行读取,和字节数没有关系
2. 数据读取时以偏移量为单位,偏移量不会被重新读取
3. 数据分区的偏移量范围
4. 如果数据源为多个文件,那么计算分区时以文件为单位进行分区
RDD方法
转换算子: 功能的补充和封装,将旧的RDD包装成新的RDD
行动算子: 触发任务的调度和作业的执行
RDD方法 =>算子
转换算子
RDD 根据数据处理方式的不同将算子整体上分为Value 类型、双 Value 类型和Key-Value类型
Value类型
map
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(1, 2, 3, 4))
//转换函数
// def mapFunction(num:Int) : Int = {
// num*2
// }
//
// rdd.map(mapFunction).collect().foreach(println)
//简化
rdd.map((num:Int)=>{num*2}).collect().foreach(println)
//一行代码,花括号可以省略
rdd.map((num:Int)=>num*2).collect().foreach(println)
//类型推断,类型省略
rdd.map((num)=>num*2).collect().foreach(println)
//参数只有一个,括号省略
rdd.map(num=>num*2).collect().foreach(println)
//参数只出现一次,参数省略
rdd.map(_*2).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
map计算并行计算演示
1.rdd的计算一个分区内的数据是一个一个执行逻辑
一个分区只有前面的数据执行完才到下一个,这点和flink不一样
分区内的数据执行是有序的
2. 不同分区数据计算是无序的
mapPartitions
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据
- 可以以分区为单位进行数据转换操作
- 但是会将整个分区的数据加载到内存进行引用
- 如果处理完的数据是不会被释放掉,存在对象的引用
- 内存较小,数据量较大的场合下,容易出现内存溢出
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform02 {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(1, 2, 3, 4),2)
// TODO 算子 - mapPartitions
//一次性把分区的数据拿过来
//mapPartitions :可以以分区为单位进行数据转换操作
// 但是会将整个分区的数据加载到内存进行引用
// 如果处理完的数据是不会被释放掉,存在对象的引用
// 内存较小,数据量较大的场合下,容易出现内存溢出
rdd.mapPartitions(
iter => {
println(">>>>>>>>>>>>>")
iter.map(_*2)
}
).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
mapPartitions可以获取每个数据分区的最大值
rdd.mapPartitions(
iter => {
//要求返回迭代器,所以包成迭代器
List(iter.max).iterator
}
)
map和mapPartitions的区别
数据处理角度
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。
功能的角度
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据
性能的角度
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处
理,所以性能较高。但是mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
mapPartitionsWithIndex
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
val RDD1 = RDD.mapPartitionsWithIndex( (index, datas) => {
if( index == 1){
//保存第二个分区
iter
} else {
Nil.iterator
}
}
)
mapPartitionsWithIndex可以获取第二个数据分区的数据
flatmap
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform03 {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd:RDD[List[Int]] = sc.makeRDD(List(
List(1, 2),List(3,4)
))
// TODO 算子 - flatMap
val value = rdd.flatMap(list => {
list
})
value.collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
flatmap可以 将 List(List(1,2),3,List(4,5))进行扁平化操作,只需要用模式匹配,将不是集合的值变成集合
glom
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
val dataRDD = sparkContext.makeRDD(List( 1,2,3,4),1)
val dataRDD1:RDD[Array[Int]] = dataRDD.glom()
小功能:计算所有分区最大值求和(分区内取最大值,分区间最大值求和)
groupBy
将数据根据指定的规则进行分组, 分区默认不变
极限情况下,数据可能被分在同一个分区中,一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
注意:分组和分区没有必然的关系
groupBy会将数据打乱,重新组合,这个操作我们称之为shuffle
有可能会导致数据倾斜
package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform05 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
// TODO 算子 - glom
// groupBy会将数据源中的每一个数据进行分组判断
// 相同的key值的数据会放置在一个组
rdd.groupBy(num => num%2).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
filter
- 将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
- 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform06 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
// TODO 算子 - glom
rdd.filter(_%2!=0).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
sample
根据指定的规则从数据集中抽取数据,需要传三个参数,具体看代码
可以解决数据倾斜,抽取分区的值,看看数据里的哪个值多,然后进行优化
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform07 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd : RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6,7,8,9,0),2)
// TODO 算子 - sample
//sample算子需要传递三个参数
//1. 第一个参数: 抽取数据后是否将数据返回 true返回 flase丢弃
//2. 第二个参数: 数据源中每条数据被抽取的概率
//基准值的概念
//3. 第三个参数: 抽取数据时随机算法的种子
//不传第三个参数,使用系统时间
println(rdd.sample(
true,
0.4,
// 1
).collect().mkString(","))
// TODO 关闭环境
sc.stop()
}
}
distinct
将数据集中重复的数据去重
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform08 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 3, 3, 4, 5, 5, 7, 8, 9, 0), 2)
// TODO 算子 - distinct
println(rdd.distinct().collect().mkString(","))
// TODO 关闭环境
sc.stop()
}
}
//结果
4,0,8,1,3,7,9,5
coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本
第一个参数设置分区数,第二个参数是否shuffle
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform09 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 3)
// TODO 算子 - coalesce
//coalesce方法默认情况不会将分区的数据打乱重新组合
//这种情况下的缩减分区可能会导致数据不均衡, 出现数据倾斜
//如果想让数据集合均衡,可以设置第二个参数进行shuffle
rdd.coalesce(2, true).saveAsTextFile("output")
// TODO 关闭环境
sc.stop()
}
}
repartition
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。
无论是将分区数多的RDD 转换为分区数少的RDD,还是将分区数少的 RDD 转换为分区数多的RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object rdd_operator_Transform09 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0), 3)
// TODO 算子 - reparation
//扩大分区一定要shuffle
rdd.repartition(5).saveAsTextFile("output1")
// TODO 关闭环境
sc.stop()
}
}
sortBy
该操作用于排序数据。 在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原RDD 的分区数一致。中间存在 shuffle 的过程
第二个参数可以改变排序的方式
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform10 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
//1, 2, 3, 4, 5, 6, 7, 8, 9, 0
val rdd: RDD[Int] = sc.makeRDD(List(5,3,6,7,8,2,34), 3)
// TODO 算子 - sortBy
println(rdd.sortBy(
num => num, false).collect().mkString(","))
// TODO 关闭环境
sc.stop()
}
}
结果:
2,3,5,6,7,8,34
双Value类型
- zip不要求两个数据源类型一致,其他需要一样
- 两个数据源要求分区数量要保持一致
- 分区中数据数量保持一致
intersection
对源RDD 和参数RDD 求交集后返回一个新的RDD
union
对源RDD 和参数RDD 求并集后返回一个新的RDD
subtract
以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来
zip
将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
不要求两个数据源类型一致,其他需要一样
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform11 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
// TODO 算子 - 双Value
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
// 交集
println(rdd1.intersection(rdd2).collect().mkString(", "))
// 并集
println(rdd1.union(rdd2).collect().mkString(", "))
// 差集
println(rdd1.subtract(rdd2).collect().mkString(", "))
// 拉链
println(rdd1.zip(rdd2).collect().mkString(", "))
// TODO 关闭环境
sc.stop()
}
}
key-value类型
partitionBy
将数据按照指定Partitioner 重新进行分区。Spark 默认的分区器是HashPartitioner
如果重分区的分区器和当前RDD 的分区器一样,则返回自己
spark可以用两个分区器 : HashPartitioner和RangePartitioner
RangePartitioner一般用在排序,sortBy底层运用到了
//HashPartitioner底层原理
//x % mod
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object rdd_operator_Transform12 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4), 2)
// TODO 算子 - key-value
//HashPartitioner底层原理
//x % mod
rdd.map((_,1)).partitionBy(new HashPartitioner(2)).saveAsTextFile("output")
// TODO 关闭环境
sc.stop()
}
}
reduceByKey
可以将数据按照相同的Key 对Value 进行聚合
reduceByKey中key的数据只有一个,是不会参与运算的
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform13 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("b", 4)),2)
// TODO 算子 - key-value
//reduceByKey中key的数据只有一个,是不会参与运算的
println(rdd.reduceByKey(_ + _).collect().mkString(","))
// TODO 关闭环境
sc.stop()
}
}
groupByKey
将数据源中的数据,相同key的数据分在一个组中, 形成一个对偶元组
元组第一个元素就是key
元组第二个元素就是相同key的value集合
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey() val dataRDD3 = dataRDD1.groupByKey(2)
val dataRDD4 = dataRDD1.groupByKey(new HashPartitioner(2))
reduceByKey和groupByKey的区别
首先,spark中,shuffle操作必须落盘处理,不能再内存中数据等待,会导致内存溢出.shuffle性能很低.
shuffle角度
reduceByKey 和 groupByKey 都存在 shuffle 的操作
- reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,可以减少落盘的数据量(reduceByKey分区内和分区间计算规则是相同的)
- groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高
功能角度
reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合.
- 分组聚合的场合下,推荐使用 reduceByKey
- 分组而不需要聚合。那么还是只能使用groupByKey
aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算
aggregateByKey存在函数柯里化, 有两个参数列表
第一个参数列表需要传递一个参数, 表示初始值
主要用于碰见第一个key的时候,和value进行分区计算
要是没有值大过初始值,那么会用到初始值
第二个参数列表需要传递两个参数
第一个参数表示分区内计算规则
第二个参数表示分区间计算规则
package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform14 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)
rdd.aggregateByKey(0)(
//表示分区内计算规则
(a, b) => math.max(a , b),
//表示分区间计算规则
(a, b) => a + b
).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
foldByKey
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey
和reduceByKey比多了初始值
package com.topview.bigdata.spark.core.operator.transform
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform15 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)
// TODO 算子 - key-value
//当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey
//和reduceByKey多了初始值
rdd.foldByKey(0)(_+_).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
combineByKey
对key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
import org.apache.spark.{SparkConf, SparkContext}
object rdd_operator_Transform16 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(("a", 1),("a", 2),("a", 3),("a", 4)),2)
// TODO 算子 - key-value
//第一个参数: 将相同key的第一个数据进行结构的转换, 实现操作
//第二个参数: 分区内的计算规则
//第三个参数: 分区内的计算规则
rdd.combineByKey(
v => (v, 1),
( t:(Int,Int), v) => {
(t._1 + v, t._2 + 1)
},
( t1:(Int,Int), t2:(Int,Int) )=>{
(t1._1 + t2._1, t1._2 + t2._2)
}
).collect().foreach(println)
// TODO 关闭环境
sc.stop()
}
}
sortByKey
在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序的
join
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素连接在一起的(K, (V,W))的RDD
- 两个不同的数据源的数据,相同的key的value会连接在一起,形成元组
- 如果两个数据源中key没有匹配上,那么数据不会出现在结果中
- 如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔乘积,数据量几何倍增长,性能降低
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6))) rdd.join(rdd1).collect().foreach(println)
leftOuterJoin和rightOuterJoin
左连接和右连接,sql一样,不多解释
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val rdd: RDD[(String, (Int, Option[Int]))] = dataRDD1.leftOuterJoin(dataRDD2)
cogroup
connection+group: 分组连接
在类型为(K,V)和(K,W)的RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("a",2),("c",3)))
val dataRDD2 = sparkContext.makeRDD(List(("a",1),("c",2),("c",3))) val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = dataRDD1.cogroup(dataRDD2)
行动算子
行动算子就是触发作业 (Job) 执行的方法
底层代码调用的是环境对象的runJob方法
底层代码中会创建ActiveJob, 并提交执行
reduce
聚集RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 聚合数据
val reduceResult: Int = rdd.reduce(_+_)
collect
方法会将不同分区的数据按照分区顺序采集到Driver端内存中
在驱动程序中,以数组Array 的形式返回数据集的所有元素
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 收 集 数 据 到 Driver
rdd.collect().foreach(println)
count
返回RDD 中元素的个数
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val countResult: Long = rdd.count()
first
返回RDD 中的第一个元素
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val firstResult: Int = rdd.first() println(firstResult)
take
返回一个由RDD 的前 n 个元素组成的数组
vval rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val takeResult: Array[Int] = rdd.take(2) println(takeResult.mkString(","))
takeOrdered
返回该 RDD 排序后的前 n 个元素组成的数组
val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))
// 返回 RDD 中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
aggregateByKey只会参与分区内计算
aggregate会参与分区内计算,并且参与分区间计算
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)
// 将该 RDD 所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _)
val result: Int = rdd.aggregate(10)(_ + _, _ + _)
fold
折叠操作,aggregate 的简化版操作
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val foldResult: Int = rdd.fold(0)(_+_)
countByKey和countByValue
统计每种 key 或 value的个数
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))
// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
save相关算子
将数据保存到不同格式的文件中
- saveAsTextFile
- saveAsObjectFile
- saveAsSequenceFile
foreach
分布式遍历RDD 中的每一个元素,调用指定函数
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 收集后打印
rdd.map(num=>num).collect().foreach(println) println("****************")
// 分布式打印
rdd.foreach(println)
t.mkString(“,”))
#### takeOrdered
返回该 RDD **排序后**的前 n 个元素组成的数组
```scala
val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))
// 返回 RDD 中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
aggregateByKey只会参与分区内计算
aggregate会参与分区内计算,并且参与分区间计算
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)
// 将该 RDD 所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _)
val result: Int = rdd.aggregate(10)(_ + _, _ + _)
fold
折叠操作,aggregate 的简化版操作
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val foldResult: Int = rdd.fold(0)(_+_)
countByKey和countByValue
统计每种 key 或 value的个数
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))
// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
save相关算子
将数据保存到不同格式的文件中
- saveAsTextFile
- saveAsObjectFile
- saveAsSequenceFile
foreach
分布式遍历RDD 中的每一个元素,调用指定函数
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 收集后打印
rdd.map(num=>num).collect().foreach(println) println("****************")
// 分布式打印
rdd.foreach(println)