我有一个大约10个平面文件的应用程序,每个文件的价值超过200MM +记录。业务逻辑涉及按顺序连接所有这些逻辑。
我的环境: 1个主站 - 3个从站(用于测试我为每个节点分配了1GB内存)
大多数代码只对每个联接执行以下操作
RDD1 = sc.textFile(file1).mapToPair(..)
RDD2 = sc.textFile(file2).mapToPair(..)
join = RDD1.join(RDD2).map(peopleObject)
任何调整建议,如重新分区,并行化......?如果是这样,有什么最好的做法可以为重新分配提供良好的数字?
使用当前配置,作业需要一个多小时,我看到几乎每个文件的随机写入都是> 3GB
答案 0 :(得分:3)
实际上,对于大型数据集(每个5,100G +),我发现在您开始加入一系列连接之前对所有RDD进行共同分区时,连接效果最佳。
RDD1 = sc.textFile(file1).mapToPair(..).partitionBy(new HashPartitioner(2048))
RDD2 = sc.textFile(file2).mapToPair(..).partitionBy(new HashPartitioner(2048))
.
.
.
RDDN = sc.textFile(fileN).mapToPair(..).partitionBy(new HashPartitioner(2048))
//start joins
RDD1.join(RDD2)...join(RDDN)
<小时/> 边注: 我将这种连接称为树连接(每个RDD使用一次)。理论基础在以下来自Spark-UI的精美图片中展示:
答案 1 :(得分:2)
如果我们总是加入一个RDD(比如说rdd1)和其他所有RDD,那么我们的想法就是将RDD分区然后再保留它。
这是sudo-Scala实现(可以很容易地转换为Python或Java):
val rdd1 = sc.textFile(file1).mapToPair(..).partitionBy(new HashPartitioner(200)).cache()
到此为止,我们将rdd1分成200个分区。它将第一次被评估,它将被持久化(缓存)。
现在让我们阅读另外两个rdds并加入它们。
val rdd2 = sc.textFile(file2).mapToPair(..)
val join1 = rdd1.join(rdd2).map(peopleObject)
val rdd3 = sc.textFile(file3).mapToPair(..)
val join2 = rdd1.join(rdd3).map(peopleObject)
请注意,对于重新生成的RDD,我们不对它们进行分区,也不对它们进行缓存。
Spark会看到rdd1已经是哈希分区,它将为所有剩余的连接使用相同的分区。所以rdd2和rdd3将把它们的密钥改组到rdd1的密钥所在的相同位置。
为了更清楚,我们假设我们不进行分区,我们使用问题所示的相同代码;每次我们进行连接时,两个rdds都将被洗牌。这意味着如果我们有n个连接到rdd1,非分区版本将将rdd1洗牌N次。分区方法只会将rdd1洗牌一次。