在Apache Spark cogroup中,如何确保不移动1个> 2个操作数的RDD?

时间:2017-07-10 15:03:46

标签: scala apache-spark join shuffle

在组合转换中,例如RDD1.cogroup(RDD2,...),我曾经假设Spark只会随机播放/移动RDD2并保留RDD1的分区和内存存储,如果:

  1. RDD1有一个明确的分区程序
  2. RDD1已缓存。
  3. 在我的其他项目中,大多数改组行为似乎与此假设一致。所以昨天我写了一个简短的scala程序,一劳永逸地证明:

    // sc is the SparkContext
    val rdd1 = sc.parallelize(1 to 10, 4).map(v => v->v)
      .partitionBy(new HashPartitioner(4))
    rdd1.persist().count()
    val rdd2 = sc.parallelize(1 to 10, 4).map(v => (11-v)->v)
    
    val cogrouped = rdd1.cogroup(rdd2).map {
      v =>
        v._2._1.head -> v._2._2.head
    }
    
    val zipped = cogrouped.zipPartitions(rdd1, rdd2) {
      (itr1, itr2, itr3) =>
        itr1.zipAll(itr2.map(_._2), 0->0, 0).zipAll(itr3.map(_._2), (0->0)->0, 0)
          .map {
            v =>
              (v._1._1._1, v._1._1._2, v._1._2, v._2)
          }
    }
    
    zipped.collect().foreach(println)
    

    如果rdd1没有移动,zipped的第一列应该与第三列具有相同的值,所以我运行了程序,oops:

    (4,7,4,1)
    (8,3,8,2)
    (1,10,1,3)
    (9,2,5,4)
    (5,6,9,5)
    (6,5,2,6)
    (10,1,6,7)
    (2,9,10,0)
    (3,8,3,8)
    (7,4,7,9)
    (0,0,0,10)
    

    假设不正确。 Spark可能做了一些内部优化,并决定重新生成rdd1的分区比将它们保存在缓存中要快得多。

    所以问题是:如果我的程序要求不移动RDD1(并保持缓存)是因为速度之外的其他原因(例如资源局部性),或者在某些情况下Spark内部优化不是优选的,有没有办法明确地指示框架不要在所有类似cogroup的操作中移动操作数?这还包括join,outer join和groupWith。

    非常感谢你的帮助。到目前为止,我使用广播连接作为一个不那么可扩展的临时解决方案,在崩溃我的集群之前不会持续很长时间。我期待与分布式计算主体一致的解决方案。

1 个答案:

答案 0 :(得分:2)

  

如果rdd1没有移动,则zipped的第一列应该与第三列具有相同的值

这个假设是不正确的。创建CoGroupedRDD不仅仅是关于shuffle,还涉及生成匹配相应记录所需的内部结构。内部Spark将使用自己的ExternalAppendOnlyMap,它使用自定义开放哈希表实现(AppendOnlyMap),但不提供任何排序​​保证。

如果检查调试字符串:

zipped.toDebugString
(4) ZippedPartitionsRDD3[8] at zipPartitions at <console>:36 []
 |  MapPartitionsRDD[7] at map at <console>:31 []
 |  MapPartitionsRDD[6] at cogroup at <console>:31 []
 |  CoGroupedRDD[5] at cogroup at <console>:31 []
 |  ShuffledRDD[2] at partitionBy at <console>:27 []
 |      CachedPartitions: 4; MemorySize: 512.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 +-(4) MapPartitionsRDD[1] at map at <console>:26 []
    |  ParallelCollectionRDD[0] at parallelize at <console>:26 []
 +-(4) MapPartitionsRDD[4] at map at <console>:29 []
    |  ParallelCollectionRDD[3] at parallelize at <console>:29 []
 |  ShuffledRDD[2] at partitionBy at <console>:27 []
 |      CachedPartitions: 4; MemorySize: 512.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 +-(4) MapPartitionsRDD[1]...

您会看到Spark确实使用CachedPartitions来计算zipped RDD。如果您还跳过删除分区程序的map转换,您会看到coGroup重新使用rdd1提供的分区程序:

rdd1.cogroup(rdd2).partitioner == rdd1.partitioner
Boolean = true