spark - 如何减少JavaPairRDD的shuffle大小<integer,integer [] =“”>?

时间:2017-03-11 09:29:00

标签: java scala apache-spark kryo

我有JavaPairRDD<Integer, Integer[]>我想要执行groupByKey操作。

groupByKey动作给了我一个:

  

org.apache.spark.shuffle.MetadataFetchFailedException:缺少shuffle的输出位置

如果我没有弄错的话,

实际上是一个OutOfMemory错误。这只发生在大数据集中(在我的情况下,#34; Shuffle Write&#34;在Web UI中显示为~96GB)。

我已经设定:

  

spark.serializer org.apache.spark.serializer.KryoSerializer

$SPARK_HOME/conf/spark-defaults.conf中的

,但我不确定Kryo是否用于序列化我的JavaPairRDD。

除了设置此conf参数之外,我还应该做些什么才能使用Kryo序列化我的RDD?我可以在serialization instructions中看到:

  

Spark自动包含Kryo序列化程序,用于来自Twitter chill库的AllScalaRegistrar中涵盖的许多常用核心Scala类。

那:

  

从Spark 2.0.0开始,我们在使用简单类型,简单类型数组或字符串类型对RDD进行混洗时,内部使用Kryo序列化程序。

我还注意到当我将spark.serializer设置为Kryo时,Web UI中的Shuffle Write从~96GB(默认序列化器)增加到243GB!

编辑:在评论中,我被问及我的程序的逻辑,以防groupByKey可以用reduceByKey替换。我不认为这是可能的,但无论如何它在这里:

  • 输入格式为:

    • key:index bucket id,
    • value:此存储桶中的整数实体ID数组
  • shuffle write操作以以下形式生成对:

    • ENTITYID
    • 同一个存储桶中所有实体ID的整数数组(称为邻居)
  • groupByKey操作收集每个实体的所有邻居数组,其中一些可能出现多次(在许多存储桶中)。

  • groupByKey操作之后,我为每个桶保留一个权重(基于它包含的负实体ID的数量),并且对于每个邻居id,我总结了它所属的桶的权重。

  • 我将每个邻居id的分数标准化为另一个值(让我们说出它给出的)并且每个实体发出前3个邻居。

我得到的不同密钥的数量大约是1000万(大约500万个正实体ID和500万个负数)。

EDIT2 :我尝试分别使用Hadoop的Writables(VIntWritable和VIntArrayWritable扩展ArrayWritable)而不是Integer和Integer [],但是shuffle大小仍然比默认的JavaSerializer大。

然后我将spark.shuffle.memoryFraction从0.2增加到0.4(即使在版本2.1.0中已弃用,也没有描述应该使用的内容)并启用了offHeap内存,并且shuffle size减少了〜 20GB。即使这符合标题的要求,我也希望采用更算法的解决方案,或者包含更好压缩的解决方案。

3 个答案:

答案 0 :(得分:1)

我认为这里可以推荐的最佳方法(没有输入数据的更多具体知识)通常是在输入RDD上使用持久性API。

作为第一步,我尝试在输入上调用.persist(MEMORY_ONLY_SER),RDD以降低内存使用量(虽然在一定的CPU开销下,这不应该是一个很大的问题。在您的情况下int。)

如果这还不够,你可以尝试.persist(MEMORY_AND_DISK_SER),或者如果你的shuffle仍然占用了大量内存,那么输入数据集需要在内存.persist(DISK_ONLY)上变得更容易,可能是一个选项,但是这会严重恶化表现。

答案 1 :(得分:1)

简答:使用fastutil,可能会增加spark.shuffle.memoryFraction

更多详情: 这个RDD的问题是Java需要存储Object引用,这比基本类型消耗更多的空间。在此示例中,我需要存储Integer s而不是int值。 Java Integer占用16个字节,而原始Java int占用4个字节。另一方面,Scala的Int类型是32位(4字节)类型,就像Java的int一样,这就是为什么使用Scala的人可能没有遇到类似的东西。

除了将spark.shuffle.memoryFraction增加到0.4之外,另一个不错的解决方案就是使用fastutil librarySpark's tuning documentation中的建议:

  

减少内存消耗的第一种方法是避免增加开销的Java功能,例如基于指针的数据结构和包装器对象。有几种方法可以做到这一点:设计数据结构以优先选择对象数组和基本类型,而不是标准的Java或Scala集合类(例如HashMap)。 fastutil库为与Java标准库兼容的基本类型提供了方便的集合类。

这使得我的RDD对的int数组中的每个元素都可以存储为int类型(即,对于数组的每个元素,使用4个字节而不是16个字节)。在我的情况下,我使用了IntArrayList而不是Integer[]。这使得shuffle大小显着下降,并允许我的程序在集群中运行。我还在代码的其他部分使用了这个库,我正在制作一些临时的Map结构。总的来说,通过将spark.shuffle.memoryFraction增加到0.4并使用fastutil库,使用默认的Java序列化程序(而不是Kryo)将shuffle大小从96GB降低到50GB(!)。

替代方法我还尝试对rdd对的每个int数组进行排序,并使用Hadoop的VIntArrayWritable类型存储增量(较小的数字比较大的数字使用的空间更少),但这也需要注册VIntWritable和Kryo​​中的VIntArrayWritable,毕竟没有节省任何空间。总的来说,我认为Kryo只会让事情变得更快,但不会减少所需的空间,但我仍然不确定。

我还没有把这个答案标记为已被接受,因为其他人可能有更好的想法,因为我毕竟没有使用Kryo,正如我的OP所要求的那样。我希望阅读它,能帮助其他人解决同样的问题。如果我设法进一步减少随机播放的大小,我会更新这个答案。

答案 2 :(得分:1)

仍然不确定你想做什么。但是,因为您使用groupByKey并且说使用reduceByKey无法做到这一点,这让我更加困惑。

我认为你有rdd = (Integer, Integer[]),而你希望使用(Integer, Iterable[Integer[]])之类的东西,这就是你使用groupByKey的原因。 无论如何,我对Spark中的Java并不是很熟悉,但在Scala中我会使用reduceByKey来避免混乱 rdd.mapValues(Iterable(_)).reduceByKey(_++_)。基本上,您希望将值转换为数组列表,然后将列表组合在一起。