Spark:groupBy需要花费大量时间

时间:2015-01-16 04:13:06

标签: aggregate apache-spark reduce

在我的应用程序中,当使用性能数字时,groupby正在吃掉很多时间。

我的RDD低于strcuture:

JavaPairRDD<CustomTuple, Map<String, Double>>

CustomTuple: 此对象包含有关RDD中当前行的信息,如周,月,城市等。

public class CustomTuple implements Serializable{

private Map hierarchyMap = null;
private Map granularMap  = null;
private String timePeriod = null;
private String sourceKey  = null;
}

地图

此地图包含有关该行的统计数据,例如投资额,GRP数量等等。

<"Inv", 20>

<"GRP", 30>

我在这个RDD上的DAG下执行

  1. 对此RDD应用过滤器并将相关行限定为:过滤器
  2. 对此RDD应用过滤器并将相关行限定为:过滤器
  3. 加入RDD:加入
  4. 应用地图阶段计算投资:地图
  5. 应用GroupBy阶段根据所需视图对数据进行分组:GroupBy
  6. 应用地图阶段按照上一步骤中的分组聚合数据(比如跨时间段的视图数据),并根据希望收集的结果集创建新对象:Map
  7. 收集结果:收集
  8. 因此,如果用户想要查看跨时间段的投资,则返回List以下(这是在上面的步骤4中实现的):

    <timeperiod1, value> 
    

    当我检查操作时间时,GroupBy占用了执行整个DAG的90%的时间。

    IMO,我们可以通过sing reduce替换GroupBy和后续的Map操作。 但是,reduce将适用于JavaPairRDD类型的对象&gt;。 所以我的reduce会像T reduce(T,T,T),其中T将是CustomTuple,Map。

    或者也许在DAG上面的第3步之后我运行另一个map函数,它返回一个需要聚合的度量标准的RDD类型,然后运行reduce。

    另外,我不确定聚合函数的工作原理,在这种情况下能否帮助我。

    其次,我的应用程序将收到不同密钥的请求。在我当前的RDD设计中,每个请求都要求我在此密钥上重新分区或重新分组我的RDD。这意味着对于每个请求,分组/重新分区将占用我95%的时间来计算作业。

    <"market1", 20>
    <"market2", 30>
    

    这非常令人沮丧,因为没有Spark的应用程序的当前性能比使用Spark的性能好10倍。

    感谢任何见解。

    [编辑]我们也注意到JOIN花了很多时间。也许这就是为什么groupby需要时间。[编辑]

    TIA!

2 个答案:

答案 0 :(得分:5)

Spark的文档鼓励您避免操作groupBy操作,而是建议使用combineByKey或其衍生操作(reduceByKey或aggregateByKey)。您必须使用此操作才能在随机播放之前和之后进行聚合(如果我们使用Hadoop术语,则在Map&#39; s和Reduce&#39; s阶段),这样您的执行时间将会改善(我不会&# 39;如果它会好10倍但是它必须更好的话,那就知道了。

如果我理解您的处理我认为您可以使用单个combineByKey操作以下代码对scala代码进行了解释,但您可以在不费力的情况下转换为Java代码

combineByKey有三个参数: combineByKey [C]( createCombiner :( V)⇒C, mergeValue :( C,V)⇒C, mergeCombiners :( C,C )⇒C):RDD [(K,C)]

  • createCombiner :在此操作中,您将创建一个新类,以便合并您的数据,以便将CustomTuple数据聚合到新的Class CustomTupleCombiner中(我不知道是否你只想赚一笔钱,或者你想要对这些数据应用一些过程但是可以在这个操作中做出选择)

  • mergeValue :在此操作中,您必须描述CustomTuple如何与另一个CustumTupleCombiner相加(我再次假设一个简单的汇总操作)。例如,如果你想按键对数据求和,你将在CustumTupleCombiner类中有一个Map,所以操作应该是这样的:CustumTupleCombiner.sum(CustomTuple),它使CustumTupleCombiner.Map(CustomTuple.key) - &gt; CustomTuple.Map(CustomTuple.key)+ CustumTupleCombiner.value

  • mergeCombiners :在此操作中,您必须定义如何在我的示例中合并两个Combiner类CustumTupleCombiner。所以这就像CustumTupleCombiner1.merge(CustumTupleCombiner2),就像CustumTupleCombiner1.Map.keys.foreach(k - &gt; CustumTupleCombiner1.Map(k)+ CustumTupleCombiner2.Map(k))之类的东西

pated代码未经证明(这甚至不会编译,因为我使用vim编写)但我认为这可能适用于您的场景。

我希望这会有用

答案 1 :(得分:0)

通过[K,V]对的密钥或repartition()呼叫的任何更改来触发随机播放。基于K(键)值计算分区。默认情况下,使用密钥的哈希值计算分区,由hashCode()方法实现。在您的情况下,您的密钥包含两个Map实例变量。 hashCode()方法的默认实现也必须计算这些映射的hashCode(),导致迭代在所有元素上发生,从而再次计算这些元素的hashCode()

解决方案是:

  1. 请勿在密钥中包含Map个实例。这似乎很不寻常。
  2. 实施并覆盖您自己的hashCode(),避免浏览Map实例变量。
  3. 您可以完全避免使用Map个对象。如果它是在多个元素之间共享的东西,您可能需要考虑在spark中使用广播变量。在改组过程中序列化地图的开销也可能是一个重要因素。
  4. 通过在两个连续的分组之间调整哈希来避免任何改组。
  5. 通过选择一个在连续使用期间保持分区本地的亲和力的分区程序来保持本地节点的混乱。
  6. hashCode()上的好读物,包括Josh Bloch引用的引用,可以在wiki找到。