谁可以在Spark中对“combineByKey”做出明确的解释?

时间:2015-11-26 11:34:40

标签: python apache-spark

我正在学习火花,但我无法理解这个功能combineByKey

>>> data = sc.parallelize([("A",1),("A",2),("B",1),("B",2),("C",1)] )
>>> data.combineByKey(lambda v : str(v)+"_", lambda c, v : c+"@"+str(v), lambda c1, c2 : c1+c2).collect()

输出结果为:

[('A', '1_2_'), ('C', '1_'), ('B', '1_2_')]

首先,我很困惑:第二步@中的lambda c, v : c+"@"+v在哪里?我无法从结果中找到@

其次,我阅读了combineByKey的函数描述,但我对算法流程感到困惑。

3 个答案:

答案 0 :(得分:5)

groupByKey调用不会尝试合并/组合值,因此这是一项昂贵的操作。

因此combineByKey调用就是这样的优化。当使用combineByKey值在每个分区合并为一个值时,每个分区值将合并为单个值。值得注意的是,组合值的类型不必与原始值的类型匹配,并且通常不会与原始值的类型相匹配。 combineByKey函数将3个函数作为参数:

  1. 创建组合器的函数。在aggregateByKey函数中,第一个参数只是一个初始零值。在combineByKey中,我们提供了一个函数,它将接受我们当前的值作为参数,并返回将与其他值合并的新值。

  2. 第二个函数是一个合并函数,它接受一个值并将其合并/组合成先前收集的值。

  3. 第三个功能将合并后的值组合在一起。基本上这个函数接受在分区级别产生的新值并将它们组合起来,直到我们得到一个奇异值。

  4. 换句话说,要理解combineByKey,考虑它如何处理它处理的每个元素是有用的。当combineByKey遍历分区中的元素时,每个元素都有一个以前没有见过的键,或者与前一个元素具有相同的键。

    如果它是一个新元素,combineByKey使用我们提供的函数(称为createCombiner())来为该键创建累加器的初始值。重要的是要注意,这是在第一次在每个分区中找到密钥时发生的,而不是仅在第一次在RDD中找到密钥时发生。

    如果它是我们在处理该分区时看到的值,它将使用提供的函数mergeValue(),使用该键的累加器的当前值和新值。

    由于每个分区都是独立处理的,因此我们可以为同一个密钥设置多个累加器。当我们合并来自每个分区的结果时,如果两个或多个分区具有相同密钥的累加器,我们使用用户提供的mergeCombiners()函数合并累加器。

    <强>参考文献:

答案 1 :(得分:1)

“ @”仅添加到每个分区内。在您的示例中,似乎每个分区中只有一个元素。 试试:

data.combineByKey(lambda v : str(v)+"_", lambda c, v : c+"@"+str(v), lambda c1, c2 : c1+'$'+c2).collect() $

看看区别

答案 2 :(得分:0)

这是CombineByKey的示例。目的是找到每个输入数据的平均数。

scala> val kvData = Array(("a",1),("b",2),("a",3),("c",9),("b",6))
kvData: Array[(String, Int)] = Array((a,1), (b,2), (a,3), (c,9), (b,6))

scala> val kvDataDist = sc.parallelize(kvData,5)
kvDataDist: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[0] at parallelize at <console>:26

scala> val keyAverages = kvDataDist.combineByKey(x=>(x,1),(a: (Int,Int),x: Int)=>(a._1+x,a._2+1),(b: (Int,Int),c: (Int,Int))=>(b._1+c._1,b._2+c._2))
keyAverages: org.apache.spark.rdd.RDD[(String, (Int, Int))] = ShuffledRDD[4] at combineByKey at <console>:25

scala> keyAverages.collect
res0: Array[(String, (Int, Int))] = Array((c,(9,1)), (a,(4,2)), (b,(8,2)))

scala> val keyAveragesFinal = keyAverages.map(x => (x._1,x._2._1/x._2._2))
keyAveragesFinal: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[3] at map at <console>:25

scala> keyAveragesFinal.collect
res1: Array[(String, Int)] = Array((c,9), (a,2), (b,4))

combineByKey以3个函数作为参数:

  1. 功能1 = createCombiner:每个分区中的每个键“ k”调用一次

    • 输入:与键“ k”关联的值
    • 输出:基于程序逻辑的任何所需的输出类型'O'。此输出类型将被自动使用。 在此示例中,选择的输出类型为(Int,Int)。 该对中的第一个元素对值求和,第二个元素跟踪组成该和的元素数。
  2. 函数2 = mergeValue:调用次数与分区中键“ k”的出现次数相同-1

    • 输入:(createCombiner的输出:O,此分区中与键“ k”关联的后续值)
    • 输出:(输出:O)
  3. 功能3 = mergeCombiners:被调用次数与存在键的分区次数相同

    • 输入:(分区X的mergeValue输出:O,分区Y的mergeValue输出:O)
    • 输出:(输出:O)