Spark学习笔记(二)

※ 转换算子

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)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值