1. spark源码学习分享:reduceByKey

零、前置  (已经了解的看官可以跳过第0章)

  

    spark的rdd支持两种类型的操作,分别是transformation和action操作。简单来说,transformation操作就是通过现有的rdd作一些变换之后得到一个新的rdd(例如map操作);action操作则是在rdd上作一些计算,然后将结果返回给drvier(例如reduce操作)。具体哪些操作属于transformation,哪些操作属于action可以参照官方文档(http://spark.apache.org/docs/latest/programming-guide.html)。当spark解析到一个transformation类型的方法时,spark并不会立马执行这个transformation操作,而是会将该transformation操作作用在哪个rdd上记录下来,然后等到解析到action类型的方法时才会一并去执行前面的transformation方法。

    默认情况下,每次执行到action类型的方法都会把它所依赖的transformation方法重新执行一遍(哪怕两个action方法依赖了同一个transformation方法)。除非你调用cache或者presist方法将产生的中间rdd缓存起来。

    本文将从transformation操作开始,以一个job执行的过程为主线来走读源码。这里选择一个比较有代表性的transformation类型方法——reduceByKey。

reduceByKey函数的作用可以参照官方文档,这里不在赘述。

    在阅读这部分源码的过程中可以验证二个问题(答案参照文中标粗的部分):

    1、transformation操作究竟会不会立马执行

    2、经过transformation操作后生成的rdd和其父rdd的partition个数是什么关系



    spark的reduceByKey方法有三种重载形式:

def reduceByKey(partitioner: Partitioner, func: JFunction2[V, V, V]): JavaPairRDD[K, V]

    def reduceByKey(func: JFunction2[V, V, V], numPartitions: Int): JavaPairRDD[K, V]

def reduceByKey(func: JFunction2[V, V, V]): JavaPairRDD[K, V]

    前两种形式除了允许用户传入聚合函数以外,还允许用户指定partitioner或者指定reduceByKey后生成的rdd的partition个数

  def reduceByKey(func: JFunction2[V, V, V]): JavaPairRDD[K, V] = {
    fromRDD(reduceByKey(defaultPartitioner(rdd), func))
  }


一、Partitioner的获取

   

    其中,当用户没有指定partitioner以及partition的个数时,spark会调用defaultPartitioner(rdd)函数去获取一个默认的partitioner。defaultPartitioner的源码如下:

  /**
   * Choose a partitioner to use for a cogroup-like operation between a number of RDDs.
   *
   * If any of the RDDs already has a partitioner, choose that one.
   *
   * Otherwise, we use a default HashPartitioner. For the number of partitions, if
   * spark.default.parallelism is set, then we'll use the value from SparkContext
   * defaultParallelism, otherwise we'll use the max number of upstream partitions.
   *
   * Unless spark.default.parallelism is set, the number of partitions will be the
   * same as the number of partitions in the largest upstream RDD, as this should
   * be least likely to cause out-of-memory errors.
   *
   * We use two method parameters (rdd, others) to enforce callers passing at least 1 RDD.
   */
  def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
    val bySize = (Seq(rdd) ++ others).sortBy(_.partitions.length).reverse
    for (r <- bySize if r.partitioner.isDefined && r.partitioner.get.numPartitions > 0) {
      return r.partitioner.get
    }
    if (rdd.context.conf.contains("spark.default.parallelism")) {
      new HashPartitioner(rdd.context.defaultParallelism)
    } else {
      new HashPartitioner(bySize.head.partitions.length)
    }
  }

      该方法的注释中,描述了这个方法的大致逻辑(英语好的看官可以自行看上面的注释):

    该方法允许传入两个rdd,调用该方法时候最少需要传入一个rdd。方法首先会将传入的两个rdd合并成一个数组,然后依据rdd中partition的个数进行降序排序。之后,遍历这个数组,从有partitioner的rdd中,挑选出partition个数最多的rdd,将其partitioner返回。如果传入的rdd都没有partitioner,那么久会返回一个HashPartitioner,其中,如果spark配置了spark.default.parallelism参数,则partition的个数为该参数的值。否则,新生成的rdd中partition的个数取与其依赖的父rdd中partition个数的最大值。

    再进一步,我们来看看HashPartitioner(HashPartitioner是Partitioner的一个内部类)的划分规则是怎么样的。先上源码:

/**
 * A [[org.apache.spark.Partitioner]] that implements hash-based partitioning using
 * Java's `Object.hashCode`.
 *
 * Java arrays have hashCodes that are based on the arrays' identities rather than their contents,
 * so attempting to partition an RDD[Array[_]] or RDD[(Array[_], _)] using a HashPartitioner will
 * produce an unexpected or incorrect result.
 */
class HashPartitioner(partitions: Int) extends Partitioner {
  require(pa
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值