Spark中的三种数据结构
Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于
处理不同的应用场景。三大数据结构分别是:
1.RDD:弹性分布式数据集
2.广播变量:分布式只读变量
3.累加器:分布式只写变量
什么是RDD?
RDD是弹性分布式数据集,它是Spark中最基本的数据处理模型,代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。:
什么叫做弹性的分布式数据集?
首先我们先从对弹性这个方面来了解
在生活上什么可以叫做弹性呢?比如一个球,在你向下拍打的时候,他会发生形变,接下来因为球内有一定气体,会使得球会恢复成原来的样子,说明球有一定的弹性
Spark-RDD是一个有弹性的数据集又怎么理解呢?
可以从存储,容错,分片,计算机内考虑
- 存储的弹性:内存与磁盘的自动切换;
- 容错的弹性:数据丢失可以自动恢复;
- 计算的弹性:计算出错重试机制;
- 分片的弹性:可根据需要重新分片
接下来对他的其他属性进行诠释一下
分布式:数据存储在大数据集群不同节点上
数据集:RDD 封装了计算逻辑,并不保存数据
数据抽象:RDD 是一个抽象类,需要子类具体实现
不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在新的 RDD 里面封装计算逻辑
Spark-RDD - 转换算子
转换算子就是在创建了一个新的RDD的时候,你想要改变这个RDD,就会创建一个新的RDD去替换旧的RDD,在值得注意的是RDD本身是不会存储数据的,这个时候可能会有疑问?不是说RDD有容错吗?你说他不存储数据,那他发生出错的时候,怎么去撤回上一步操作呢?这个时候就会说到RDD的一个血缘关系,具体的会在后续发出来,转换算子本身是不会去加载到内存中的,只有遇到行动算子,才会加载到内存中,再去被执行
那么我们先首先去学习转换算子
转换算子总汇表
Map | MapPartitions | FlatMap | groupBy | filter | distinct |
---|---|---|---|---|---|
sortBy | mapPartitionsWithIndex | glom | sample | coalesce | repartition |
intersection | union | subtract | zip | partitionBy | reduceByKey |
groupByKey | aggregateByKey | foldByKey | combineByKey | sortByKey | join |
leftOuterJoin | cogroup |
注意!!!!
1.Map*
➢函数签名
def map[U: ClassTag](f: T => U): RDD[U]
➢函数说明:
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
注:这个算子能改变RDD的结构,是一个比较常用的算子
案列
修改rdd的数据结构为(_,1)
object Spark_RDD_Map_Operator {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Partition")
//setMaster("local[*]")设置Spark运行模式,当前设置的是本地模式,至于后面的[*]是指
//多少给内核[*]是给电脑的全部内核数
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List("Spark","Kafka","Hbase"))
val rdd2 = rdd.map((_,1))
println(rdd2.collect().mkString(","))
sc.stop()
}
}
**2. **MapPartitions
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
➢函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
案例
取出分区内最小的数
val rdd = sc.makeRDD(List(1, 2, 3, 4),2)
val rdd2 = rdd.mapPartitions(data => {
List(data.min).iterator
})
**3. *FlatMap
➢函数说明:
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
➢函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
案例
val rdd = sc.makeRDD(List(List(1, 2),List(3,4)))
val rdd2 = rdd.flatMap(list => list)
4.groupBy*
➢函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
➢函数说明
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样
的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中
案例:
val rdd = sc.makeRDD(List("Hadoop", "Java", "Json", "HDFS"))
val rdd2 = rdd.groupBy(_.charAt(0))
5.filter*
➢函数签名
def filter(f: T => Boolean): RDD[T]
➢函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
案例
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val rdd2: RDD[Int] = rdd.filter(_ > 2)
6.distinct*
➢函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
➢函数说明
去重
这个算子比较简单就不做过多的解释了,直接看案例就好了
案例
val dataRDD2 = sc.makeRDD(List(1, 1, 2, 4, 5, 7, 7, 8))
val rdd = dataRDD2.distinct()j
7.sortBy*
➢函数签名
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
➢函数说明
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理
的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一
致。中间存在 shuffle 的过程
案例
val dataRDD = sc.makeRDD(List(("1",2),("11",4),("2",5)))
val rdd = dataRDD.sortBy(tuple => tuple._1.toInt,ascending = true)
//设置了排序是按升序
//排序是按tuple第一元素来排的
//为什么要toInt:原因是因为如果是String的话默认比较第一个字母,所以会导致11在2的前面,
"11"中的"1"的ASII码比"2"的ASII码小
8.mapPartitionsWithIndex
➢函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
➢函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
案例
val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
//只获取1分区的
val rdd2 = rdd.mapPartitionsWithIndex((index, datas) => {
datas.map(num => (num,index))
}
)
println(rdd2.collect().mkString(","))
9.glom*
➢函数签名
def glom(): RDD[Array[T]]
➢函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
小功能:计算所有分区最大值求和(分区内取最大值,分区间最大值求和)
val rdd1 = sc.makeRDD(List(1, 2, 3, 4), 2)
val rdd2: RDD[Array[Int]] = rdd1.glom()
val MaxRdd: RDD[Int] = rdd2.map(
array => {
array.max
}
)
println(MaxRdd.collect().sum)
10.sample
➢ 函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
➢ 函数说明
根据指定的规则从数据集中抽取数据
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
// 抽取数据不放回(伯努利算法)
// 伯努利算法:又叫 0、1 分布。例如扔硬币,要么正面,要么反面。
// 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不
要
// 第一个参数:抽取的数据是否放回,false:不放回
// 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
// 第三个参数:随机数种子
val dataRDD1 = dataRDD.sample(false, 0.5)
// 抽取数据放回(泊松算法)
// 第一个参数:抽取的数据是否放回,true:放回;false:不放回
// 第二个参数:重复数据的几率,范围大于等于 0.表示每一个元素被期望抽取到的次数
// 第三个参数:随机数种子
val dataRDD2 = dataRDD.sample(true, 2)
11.coalesce
➢ 函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
➢ 函数说明
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少
分区的个数,减小任务调度成本
val dataRDD = sparkContext.makeRDD(List(1,2,3,4,1,2),6)
val dataRDD1 = dataRDD.coalesce(2)
12.repartition
➢ 函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
➢ 函数说明
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition操作都可以完成,因为无论如何都会经 shuffle 过程。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.repartition(4)
13.intersection*
➢ 函数签名
def intersection(other: RDD[T]): RDD[T]
➢ 函数说明
对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
14.union*
➢ 函数签名
def union(other: RDD[T]): RDD[T]
➢ 函数说明
对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
15.subtract*
➢ 函数签名
def subtract(other: RDD[T]): RDD[T]
➢ 函数说明
以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集
16.zip*
➢ 函数签名
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
➢ 函数说明
将两个 RDD 中**的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD
中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
//intersection ----> 交集
//intersection ----> 交集
val dataRDD1 = sc.makeRDD(List(1, 2, 3, 4))
val dataRDD2 = sc.makeRDD(List(3, 4, 5, 6))
val dataRDD = dataRDD1.intersection(dataRDD2)
//3,4,
// dataRDD.collect().foreach(x => print(x + ","))
println(dataRDD.collect().mkString(","))
//union ----> 并集
val dataRDD3 = dataRDD1.union(dataRDD2)
//1,2,3,4,3,4,5,6
// dataRDD3.collect().foreach(println)
println(dataRDD3.collect().mkString(","))
//subtract ----> 差
//1,2,
val dataRDD4 = dataRDD1.subtract(dataRDD2)
// dataRDD4.collect().foreach(x => print(x + ","))
println(dataRDD4.collect().mkString(","))
//拉链 ---->(1,3),(2,4),(3,5),(4,6),
val dataRDD6: RDD[(Int, Int)] = dataRDD1.zip(dataRDD2)
17.partitionBy*
➢ 函数签名
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
➢ 函数说明
将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc"),(4,"ddd")),2)
//val rawMod = x % mod
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
//当前再次的话 返回还是自己
rdd.partitionBy(new HashPartitioner(2))
rdd2.saveAsTextFile("output")
18.reduceByKey*
➢ 函数签名
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
➢ 函数说明
可以将数据按照相同的 Key 对 Value 进行聚合 -> 这个也是比较常用的
val dataRDD = sc.makeRDD(List(("1", 2), ("1", 4), ("2", 5), ("2", 6)))
//相同的key分组 -- 参数二可以对聚合后的结果进行分区
val reduceRDD = dataRDD.reduceByKey((x,y) => x+y)
19.groupByKey*
➢ 函数签名
//按key来进行分组
def groupByKey(): RDD[(K, Iterable[V])]
//分组后的分区
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
//分组后可以指定分区的分区器
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
➢ 函数说明
将数据源的数据根据 key 对 value 进行分组
val dataRDD = sc.makeRDD(List(("1", 2), ("1", 4), ("2", 5), ("2", 6)))
//相同的key分组 -- 参数二可以对分组后的结果进行分区
val reduceRDD = dataRDD.groupByKey(2).map(
iter => {
(iter._1,iter._2.sum)
}
)
20.aggregateByKey*
➢ 函数签名
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
➢ 函数说明
将数据根据不同的规则进行分区内计算和分区间计算
val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 5)), 2)
//存在函数的柯里化
//参数说明
//1.参数1指的是初始值
//2.参数2指的是区间内的计算规则
//3.参数3指的是区间间的计算规则
val rdd3: RDD[(String, Int)] = rdd.aggregateByKey(0)(
(x, y) => math.max(x, y),
(x, y) => x + y
)
➢图解说明
21.foldByKey*
➢ 函数签名
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
➢函数说明
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.foldByKey(0)(_+_)
22.combineByKey*
➢ 函数签名
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
➢ 函数说明
最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于 aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2)
//相同key的平均值
//combineByKey有三个参数
//1.表示将数据进行一定格式的转换
//2.按照前面的参数进行比较规则aggerateByKey的参数类型一样
//数据类型因为是动态的 所以要注意类型
val RDD: RDD[(String, (Int, Int))] = rdd.combineByKey(v=>(v,1),(t:(Int,Int), v) => {
//当是A时 (0+1,0+1)->(1+2,1+1)
(t._1 + v, t._2 + 1)
//第二个参数是将分区间的数据进行相加
}, (t1:(Int,Int), t2:(Int,Int)) => {
(t1._1 + t2._1, t1._2 + t2._2)
})
//(b,4)
//(a,3)
RDD.mapValues(t => t._1 / t._2).collect().foreach(println)
23.sortByKey*
➢ 函数签名
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)]
➢函数说明
在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序 的
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(true)
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(false)//降序
24.join*
➢ 函数签名
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
➢函数说明
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的 (K,(V,W))的 RDD
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)
25.leftOuterJoin
➢ 函数签名
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
➢ 函数说明
类似于 SQL 语句的左外连接
val dataRDD = sc.makeRDD(List(("1",2),("11",4)))
val rdd2 = sc.makeRDD(List(("1",3),("11",5),("2",9)))
//表的左右连接 相同的key进行value的连接 主要是取决于谁是主表
val leftOuterJoin = dataRDD.leftOuterJoin(rdd2)
val rightOuterJoin = dataRDD.rightOuterJoin(rdd2)
26.cogroup
➢ 函数签名
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
➢ 函数说明
就是相当于connection(连接)+group(分组)
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)
后续行动算子也做出了总结
行动算子的连接就在这里!点一下就进去了