在组合转换中,例如RDD1.cogroup(RDD2,...),我曾经假设Spark只会随机播放/移动RDD2并保留RDD1的分区和内存存储,如果:
在我的其他项目中,大多数改组行为似乎与此假设一致。所以昨天我写了一个简短的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。
非常感谢你的帮助。到目前为止,我使用广播连接作为一个不那么可扩展的临时解决方案,在崩溃我的集群之前不会持续很长时间。我期待与分布式计算主体一致的解决方案。
答案 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