我有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替换。我不认为这是可能的,但无论如何它在这里:
输入格式为:
shuffle write操作以以下形式生成对:
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。即使这符合标题的要求,我也希望采用更算法的解决方案,或者包含更好压缩的解决方案。
答案 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 library,Spark'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(_++_)
。基本上,您希望将值转换为数组列表,然后将列表组合在一起。