spark 算子

  什么是spark算子?可以理解成spark RDD的方法,这些方法作用于RDD的每一个partition。
  因为spark的RDD是一个 lazy的计算过程,只有得到特定触发才会进行计算,否则不会产生任何结果。大体上分:

  • Transformation 变换/转换算子,不触发执行
  • Action 行动算子,立马触发执行

  Spark中RDD的定义是一个弹性的分布式数据集,弹性体现在计算上。当一个RDD的Rartition数据是错的,会根据依赖关系重新算出该Partition,不需要再重新全部计算RDD。这里有一个概念,依赖,RDD中实际情况分两种依赖,窄依赖和宽依赖。

  • 窄依赖:一个父RDD的partition最多被子RDD的一个partition使用一次。
  • 宽依赖:一个父RDD的partition可能被子RDD的partition使用多次。
    这里写图片描述
      宽依赖尽量避免,在数据容错恢复中效率比较低,但是宽依赖因为有Shuffle在数据倾斜上有很大作用。

常用算子

一、map

  map是作用于RDD中的每一个每一个元素。这不是一个action算子,也不是一个shuffle算子。对每个元素的操作是在各个Partition中完成,所以可以并行化执行。

val lines = sc.textFile("F:\\test.txt")
var mapRDD = lines.map(line => line.split("\t"))
//结果:Array[Array[String]] = Array(Array(a, b, c), Array(d, e, f)))

二、reduce (Action)

  根据映射函数f,对RDD中的元素进行二元计算,返回计算结果。reduce先在各分区中做操作,随后进行整合。如果查看查看源码会知道reduce默认调用的是reduceLeft操作。

val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8,9,10))
rdd.reduce((a,b) => a+b)
//返回:res2: Int = 55

三、filter

  对每个元素做过滤,匿名函数的返回True就保留该元素,否侧转化成新的RDD就不会包含该元素。

val rdd = sc.parallelize(1 to 10)
rdd.filter(_ % 2 == 0).collect
//返回:Array[Int] = Array(2, 4, 6, 8, 10)

四、mapValues

  字面意思有map和values,这个算子的意思就是对RDD类型是tuple二元或者Map的对其值进行操作转化。

val rdd = sc.parallelize(1 to 3).map((_,1))
rdd.mapValues(_ + 1).collect
//返回:Array[(Int, Int)] = Array((1,2), (2,2), (3,2))

五、flatMap

  看起来像极了map。map是对RDD中的每一个元素的操作,flatMap也是,但是他多了一步扁平化操作。扁平化就是原来是Tuple或者Map的多元化元素压扁成一元形式。

val a = sc.parallelize(List(("a",1),("b",2)))
a.flatMap(x => Array(x._1,x._2)).collect
//结果: Array[Any] = Array(a, 1, b, 2)

六、join (shuffle)

  类似于SQL中的join操作,两个RDD根据Key值相同的关联在一起,这个意思就说明join必须是两个tuple的RDD。这是一个shuffle过程,两个RDD各自按照key shuffle到新的RDD中。join中有左右连接的问题,分别是leftOutJoin,rightOutJoin,其中要注意结果的some,none问题。

val a = sc.parallelize(List(("a","a1"),("b","b1")))
val b = sc.parallelize(List(("a","a2"),("c","c1")))
val c = a.join(b)
c.collect
//结果:Array[(String, (String, String))] = Array((a,(a1,a2)))

七、subtract

  返回在RDD中出现,并且不在otherRDD中出现的元素,去重。相当于减法RDDA-RDDB。

var rdd1 = sc.makeRDD(Seq(1,2,2,3))
var rdd2 = sc.makeRDD(3 to 4)
rdd1.subtract(rdd2).collect
//结果:Array[Int] = Array(1, 2)

八、coalesce (shuffle可选)

  转化RDD,减少partition数量,将当前的RDD中partition数量减小到参数指定的partition数量生成新的RDD,但只能减小,不能变大(其实是可以变大的)。partition从多到少是不会有shuffle的,但是从少到多是要有shuffle。所以partition要变大,就要指定参数shuffle为true,这就是为什么说是可以变大,如果不设置true,不起作用。如果shuffle为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的。
  这个算子有什么作用,RDD在输出(save)的时候,有多少个partition就会输出多少个文件,这个算子常常配合在filter后面做一个文件量(task)减少,适合做小文件合并。有一个坑,如果你用了coalesce(1),会在源头上直接设置partition数量设置成1。过滤的最后的数据量还剩多少,做一个预估,然后再设置这个partition数量。

九、repartition (shuffle)

  repartition,底层调用的是coalesce,shuffle开启。这个算子就是用于少变多。

十、mapPartitions

  mapPartitions是对每一个partition做操作,而Map是对每一个元素操作。这个算子的好处就是说可以在资源,比如数据库上下文做节约,不必为每个元素做上下文,降低开销。

十一、collect (Action)

  这个算子用于返回RDD内的所有元素,以数组的形式呈现。注意,collect出的数据会导入Driver的内存中,所以RDD中大数据量不建议使用。

十二、take (Action)

  take带有一个参数,获取前n个元素,以数组的形式返回。所以,底层take会从第一个Partition获取元素,不够就接着下一个Partition获取数据。

十三、collectAsMap (Action)

  该算子以Key-Value形式返回,所以该算子也只能是PairRDD的算子。所以也是返回所有数据量到Driver,所以也与collect一样,只能用于数据量少的RDD,否则Driver会爆。

十四、groupByKey (shuffle)

  是一个宽依赖会有一个Shuffle,所以就会新生成一个stage。每个Partition中的数据按照Key Shuffle到子Partition中。

十五、reduceByKey (shuffle)

  由于reduceByKey是宽依赖,带shuffle,所以就会新生成一个stage。但不同于groupByKey,reduceByKey会先在每个Partition中做处理,完成后再按照Key Shuffle到子Partition中,所以这样可以减少网络IO。

案例

1 窗口函数

  需求:获取每个区域内产品销量的TopN。
  分析:每个区域是一个窗口函数,在这个窗口内获取产品销量Word Count的TopN。
  步骤1:可以组成一个Tuple,((区域,产品),1)。
  步骤2:然后Reduce计算唯一的区域+产品,得到的结果就是每个区域下每个产品的销售量。
  步骤3:分组,根据区域分组,用到groupBy,结果是(区域,CompactBuffer(((区域,产品),销量),((区域,产品),销量)))
  步骤4:对值进行操作,用到mapValue,mapValue(_.toList.sortBy( _ ._2).reverse.take(N)),这样就能获取每个区域下的产品销量的TopN
  步骤5:这样输出不好看,所以还要map一下,整理一下

发布了63 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/myt0929/article/details/82050780