※ 转换算子
1. distinct:去除重复数据
val value: RDD[Int] = rdd.distinct()
去重的原理:
map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)
2. coalesce:缩减分区,默认不会打乱数据的分区组合,可能会导致数据倾斜,所以可以进行shuffle处理。当然也可以进行增加分区的操作,需要和shuffle操作一起,否则不起作用。因为新增加的是个空白的分区。
val value: RDD[Int] = rdd.coalesce(2) // 传缩减到多少个分区数
shuffle处理:
val value: RDD[Int] = rdd.coalesce(2, true)
3. repartition:扩大分区,底层调用的就是coalesce。
val value: RDD[Int] = rdd.repartition(2)
4. sortBy:根据指定规则排序,不会改变分区,但是会shuffle。
// 排序规则, 指定是否是升序
val groups: RDD[Int] = rdd.sortBy(num => num, true)
5. 交、并、差集,拉链
val rdd1: RDD[Int] = context.makeRDD(List(1,2, 3, 3,4),2) // numSlices 代表分区数
val rdd2: RDD[Int] = context.makeRDD(List(1,2, 5,6),2) // numSlices 代表分区数
// 交集
val groups: RDD[Int] = rdd1.intersection(rdd2)
// 并集
val groups: RDD[Int] = rdd1.union(rdd2)
// 差集
val groups: RDD[Int] = rdd1.subtract(rdd2)
// 拉链 需要每个分区的数据元素个数相同,否则报错
val groups = rdd1.zip(rdd2)
6. partitionBy 根据指定条件进行分区,用两种分区器 HashPartitioner 和 RangePartitioner。
// 这里是RDD转换成了pairRddFunction
val groups: RDD[(Int, Int)] = data.partitionBy(new HashPartitioner(2))
当RDD转换成了pairRddFunction,就触发了隐式转化
object RDD {
implicit def rddToPairRDDFunctions[K, V](rdd: RDD[(K, V)])
(implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null): PairRDDFunctions[K, V] = {
new PairRDDFunctions(rdd)
}
}
HashPartitioner底层计算分区
class HashPartitioner(partitions: Int) extends Partitioner {
def getPartition(key: Any): Int = key match {
case null => 0
case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
}
}
------------------------------------------------------------
// 底层是取模运算
def nonNegativeMod(x: Int, mod: Int): Int = {
val rawMod = x % mod
rawMod + (if (rawMod < 0) mod else 0)
}
如果重分区和当前分区RDD一样会怎么做?
def partitionBy(partitioner: Partitioner): RDD[(K, V)] = self.withScope {
// 在这里会进行判断两个分区,这里用的是重写的equals方法,如果相等就返回自身不会生成新的RDD
if (self.partitioner == Some(partitioner)) {
self
} else {
new ShuffledRDD[K, V, V](self, partitioner)
}
}
-----------------------------------------------------------------
class HashPartitioner(partitions: Int) extends Partitioner {
override def equals(other: Any): Boolean = other match {
case h: HashPartitioner => // 通过模式匹配判断类型和数量是否相等
h.numPartitions == numPartitions
case _ =>
false
}
}
7. reduceByKey:相同key的数据可以根据value进行聚合,如果key的数据只有一个是不会进行计算的。分区内和分区间的操作规则相同。
// 对value值进行的操作,已经通过key分组
val value: RDD[(String, Int)] = wordsTuple.reduceByKey(_ + _)
8. groupByKey:将数据源中的数据通过key进行分组,形成一个对偶元组
val groups: RDD[(Int, Iterable[Int])] = data.groupByKey()
reduceByKey 和 groupByKey的区别:
groupByKey会导致数据打乱,存在shuffle的操作。Spark中shuffle操作会落盘操作,不能在内存中等待,否则会造成内存溢出,所以shuffle操作性能低。groupByKey是先将数据分好组以后写到文件中,然后再读入道后续的RDD中。
reduceByKey 会在分区中先聚合一次,从而减少shuffle操作的IO,然后在进行数据的落盘分组操作。从而提升性能。
9. aggregateByKey 按照Key进行聚合,分区间进行计算。
val data: RDD[(String, Int)] = context.makeRDD(List(("a ", 1), ("a", 2), ("b", 1), ("a", 3), ("b", 2), ("b", 3)), 2)
1. 需要传两个参数列表
2. 第一个参数列表 初始值,在每个分区碰见第一个key的时候用到初始值
3. 第二个参数列表需要传两个函数,第一个是分区内的计算规则,第二个是分区间的计算规则
// 同样是对value进行操作
val groups: RDD[(String, Int)] = data.aggregateByKey(0)((x, y) => {
x + y
}, (x, y) => {
math.max(x, y)
})
// 当前是在分区内做加法,在分区间做取最大值的操作
>>> ("a", 3) ("b", 5)
初始值传入什么类型,就会返回什么类型,下面看个小例子
1. 求下面相同key的value的平均值
val data: RDD[(String, Int)] = context.makeRDD(List(("a", 1), ("a", 2), ("b", 1), ("a", 3), ("b", 2), ("b", 3)), 2)
// 初始值是tuple,返回的就是tuple
val groups: RDD[(String, (Int, Int))] = data.aggregateByKey((0, 0))(
// 第一个参数是初始值
(init, value) => {
(init._1 + value, init._2 + 1)
},
// t1, t2代表每个分区中相同key的tuple
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2)
}
)
// mapvalues 只对值进行操作
val value: RDD[(String, Int)] = groups.mapValues((t) => {
t._1 / t._2
})
10. foldByKey 如果aggregateByKey的分区内和分区间的规则一样,可以用foldByKey
val groups: RDD[(String, Int)] = data.foldByKey (0)(_+_)
11. combineBykey 对于上面的小例子,进行优化而来
val data: RDD[(String, Int)] = context.makeRDD(List(("a", 1), ("a", 2), ("b", 1), ("a", 3), ("b", 2), ("b", 3)), 2) // numSlices 代表分区数
// 需要三个参数
// 第一个参数 将相同key的第一个数据进行结构转换
// 第二个参数 分区内的计算规则
// 第三个参数 分区间的计算规则
val groups: RDD[(String, (Int, Int))] = data.combineByKey(init =>
(init, 1), // init对第一个参数进行结构转换
(init: (Int, Int), value) => {
(init._1 + value, init._2 + 1)
},
(t1: (Int, Int), t2: (Int, Int)) => {
(t1._1 + t2._1, t2._2 + t1._2)
})
val value: RDD[(String, Int)] = groups.mapValues((t) => {
t._1 / t._2
})
reduceByKey、combineByKey、foldByKey、aggregateByKey的区别: 底层调用的方法一样,参数的传递不一样
reduceByKey:
combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
foldByKey:
combineByKeyWithClassTag[V]((v: V) => cleanedFunc(createZero(), v),
cleanedFunc, cleanedFunc, partitioner)
aggregateByKey:
combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),
cleanedSeqOp, combOp, partitioner)
combineByKey:
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
12. join 返回一个相同的key对应的所有元素连接在一起、如果没有匹配到则不会出现。
val data1: RDD[(String, Int)] = context.makeRDD(Array(("a", 1), ("a", 2), ("b", 1)))
val data2: RDD[(String, Int)] = context.makeRDD(Array(("a", 3), ("b", 2), ("b", 3)))
val value: RDD[(String, (Int, Int))] = data1.join(data2)
13.. leftOuterJoin rightOuterJoin 类似于sql中的左外连接和右外连接
// 没有匹配到的也会出现
val value: RDD[(String, (Int, Option[Int]))] = data1.leftOuterJoin(data2)
val value: RDD[(String, (Int, Option[Int]))] = data1.rightOuterJoin(data2)
14. cogroup:等价于connect + group
val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = data1.cogroup(data2)