如何将RDD,Dataframe或Dataset直接转换为Broadcast变量而不收集?

时间:2016-07-12 13:04:05

标签: scala apache-spark dataframe apache-spark-sql

是否有任何方式(或任何计划)能够将Spark分布式集合(RDD s,DataframeDataset s)直接转换为Broadcast个变量而无需需要collect?公共API似乎没有任何“开箱即用”的东西,但可以在较低级别完成某些事情吗?

我可以想象这些操作有2倍的加速潜力(或更多?)。为了解释我的意思,让我们通过一个例子来解释:

val myUberMap: Broadcast[Map[String, String]] =
  sc.broadcast(myStringPairRdd.collect().toMap)

someOtherRdd.map(someCodeUsingTheUberMap)

这会导致所有数据被收集到驱动程序,然后广播数据。这意味着数据通过网络发送了两次。

这样的事情会很好:

val myUberMap: Broadcast[Map[String, String]] =
  myStringPairRdd.toBroadcast((a: Array[(String, String)]) => a.toMap)

someOtherRdd.map(someCodeUsingTheUberMap)

这里Spark可以完全绕过收集数据,只是在节点之间移动数据。

奖金

此外,对于combineByKey.toMap上的任何操作都很昂贵的情况,可能会出现类似Monoid的API(有点像Array[T]),但可以在平行。例如。构造某些Trie结构可能很昂贵,这种功能可能会为算法设计带来很棒的范围。当IO运行时也可以运行此CPU活动 - 当前广播机制阻塞(即所有IO,然后是所有CPU,然后是所有IO)。

澄清

加入不是(主要)用例,可以假设我稀疏地使用广播的数据结构。例如,someOtherRdd中的键绝不会涵盖myUberMap中的键,但在遍历someOtherRdd之前我不知道需要哪些键,并假设我使用myUberMap多个键次。

我知道所有听起来都有点模糊,但重点是更一般的机器学习算法设计。

2 个答案:

答案 0 :(得分:6)

虽然从理论上讲这是一个有趣的想法,但我认为虽然理论上可行,但实际应用却非常有限。显然我不能代表PMC发言,所以我不能说是否有任何计划实施这种类型的广播机制。

可能的实施

由于Spark已经提供了torrent broadcasting机制,其行为描述如下:

  

驱动程序将序列化对象分成小块和   将这些块存储在驱动程序的BlockManager中。

     

在每个执行程序上,执行程序首先尝试从其BlockManager获取对象。   如果它不存在,则它使用远程提取从驱动程序和/或获取小块   其他执行者(如果有)。

     

一旦获得了块,就会将块放在自己的块中   BlockManager,准备好让其他执行者从中获取。

应该可以重复使用相同的机制进行直接的节点到节点广播。

值得注意的是,这种方法不能完全消除驱动程序通信。即使可以在本地创建块,您仍然需要一个单一的事实来源来宣传要获取的一组块。

有限的申请

广播变量的一个问题是相当昂贵。即使您可以消除驱动程序瓶颈,仍然存在两个问题:

  • 在每个执行程序上存储反序列化对象所需的内存。
  • 将广播数据传输给每个执行人的费用。

第一个问题应该是比较明显的。它不仅涉及直接内存使用,还涉及GC成本及其对总体延迟的影响。第二个是相当微妙的。我在对Why my BroadcastHashJoin is slower than ShuffledHashJoin in Spark的回答中部分地阐述了这一点,但让我们进一步讨论这个问题。

从网络流量角度来看,广播整个数据集几乎相当于创建笛卡尔积。因此,如果数据集大到足以让驱动程序成为瓶颈,那么它就不太可能成为广播的理想候选者,并且在实践中可能更喜欢散列连接等有针对性的方法。

<强>替代

有一些方法可用于实现与上面列举的直接广播和地址问题类似的结果,包括:

  • 通过分布式文件系统传递数据。
  • 使用与工作节点并置的复制数据库。

答案 1 :(得分:0)

我不知道我们是否可以为RDD做这件事,但你可以为Dataframe做这件事

import org.apache.spark.sql.functions

val df:DataFrame = your_data_frame

val broadcasted_df = functions.broadcast(df)

现在您可以使用变量 broadcasted_df ,它将被广播到执行者。

确保 broadcasted_df 数据框不是太大,可以发送给执行者。

broadcasted_df 将成为广播公司,例如

other_df.join(broadcasted_df)

并且在这种情况下 join()操作执行得更快,因为每个执行程序都有1个 other_df 分区和整个 broadcasted_df

对于你的问题,我不确定你能做你想做的事。你不能在另一个rdd的#map()方法中使用一个rdd,因为spark不允许在转换中进行转换。在你的情况下,你需要调用 collect()方法从你的RDD创建地图,因为你只能在#map()方法中使用通常的地图对象,你不能在那里使用RDD。