火花aggregatebykey与集合作为​​zerovalue

时间:2015-03-12 13:28:11

标签: apache-spark aggregate

我正在使用元组[k,v(日期,标签)]的rdd,我正在尝试获取所有不同的标签和每个键的最小日期。

我结束了这段代码:

aggregateByKey((new DateTime(), new mutable.HashSet[String]()))((acc: (DateTime, mutable.HashSet[String]), v: (DateTime, String)) => (if (acc._1.isBefore(v._1)) acc._1 else v._1, acc._2 + v._2), (acc1: (DateTime, mutable.HashSet[String]), acc2: (DateTime, mutable.HashSet[String])) => (if (acc1._1.isBefore(acc2._1)) acc1._1 else acc2._1, acc1._2 ++ acc2._2))

我得到一个OutOfMemoryError:

ERROR ActorSystemImpl: Uncaught fatal error from thread [sparkDriver-akka.actor.default-dispatcher-19] shutting down ActorSystem [sparkDriver]
java.lang.OutOfMemoryError: Java heap space
at com.google.protobuf_spark.ByteString.copyFrom(ByteString.java:90)
at com.google.protobuf_spark.CodedInputStream.readBytes(CodedInputStream.java:289)
at akka.remote.ContainerFormats$SelectionEnvelope$Builder.mergeFrom(ContainerFormats.java:551)
at akka.remote.ContainerFormats$SelectionEnvelope$Builder.mergeFrom(ContainerFormats.java:349)
at com.google.protobuf_spark.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:300)
at com.google.protobuf_spark.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:238)
at com.google.protobuf_spark.AbstractMessageLite$Builder.mergeFrom(AbstractMessageLite.java:162)
at com.google.protobuf_spark.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:716)
at com.google.protobuf_spark.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:238)
at com.google.protobuf_spark.AbstractMessageLite$Builder.mergeFrom(AbstractMessageLite.java:153)
at com.google.protobuf_spark.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:709)
at akka.remote.ContainerFormats$SelectionEnvelope.parseFrom(ContainerFormats.java:283)
at akka.remote.serialization.MessageContainerSerializer.fromBinary(MessageContainerSerializer.scala:57)
at akka.serialization.Serialization$$anonfun$deserialize$1.apply(Serialization.scala:104)
at scala.util.Try$.apply(Try.scala:161)
at akka.serialization.Serialization.deserialize(Serialization.scala:98)
at akka.remote.MessageSerializer$.deserialize(MessageSerializer.scala:23)
at akka.remote.DefaultMessageDispatcher.payload$lzycompute$1(Endpoint.scala:55)
at akka.remote.DefaultMessageDispatcher.payload$1(Endpoint.scala:55)
at akka.remote.DefaultMessageDispatcher.dispatch(Endpoint.scala:73)
at akka.remote.EndpointReader$$anonfun$receive$2.applyOrElse(Endpoint.scala:764)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:498)
at akka.actor.ActorCell.invoke(ActorCell.scala:456)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:237)
at akka.dispatch.Mailbox.run(Mailbox.scala:219)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:386)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

似乎aggregateByKey不是输出集合的正确选择,而groupByKey与此任务更相关,但我必须多次传递数据。

有没有办法用aggregateByKey函数实现我的目标? groupByKey和aggregateByKey之间的实现有什么区别,这可以解释我得到一个OutOfMemoryError而不是另一个? 与溢出到磁盘策略有关的东西?

1 个答案:

答案 0 :(得分:2)

好的,这里有很多事情要发生,所以让我们逐一进行:

  1. groupByKey只是将一个密钥的所有数据混合到单个执行程序中,将其加载到内存中并使其可用于执行任何操作(聚合与否)。如果存在大量与任何给定键相关的数据(偏斜数据),则这可能是OutOfMemoryErrors的直接原因。

  2. aggregateByKey会尝试变得更聪明。因为它知道它是聚合的,所以它会在洗牌之前尝试在本地聚合。您提供的方法和零值是序列化到多个执行程序,以实现这一目的。因此,即使对于相同的密钥,您的聚合逻辑也将被分发。只有累加器才会被序列化和合并。总的来说,这种方法在大多数情况下明显更好,但是如果(就像在这种情况下)累加器本身的大小可以无限制地增长,你必须要小心。相关问题:您希望每个键有多少个字符串?这些弦有多大?您希望发生多少重复数据删除?

  3. 您可以做的另一件事是从aggregateByKey的文档中获取这条建议:

  4.   

    为避免内存分配,允许这两个函数修改并返回其第一个参数,而不是创建新的U.

    你似乎宣布你的Set是可变的,但你实际上并没有改变它,你总是生成新的集合。以下是一个可用作参考的示例:

        case class Aggregator(var count: Int, var categs: Set[UUID])
        object Aggregator
        {
           def zero = Aggregator(0, Set[UUID]())
           def addWithLimit(agg: Aggregator, newCategs: Traversable[UUID], limit: Int = 5) =
           {
              for(c <- newCategs)
                 if(agg.categs.size <= limit)
                    agg.categs += c
              agg
           }
    
           def addSample(agg: Aggregator, categ: UUID) =
           {
              agg.count += 1
              addWithLimit(agg, List(categ))
              agg
           }
    
           def merge(agg: Aggregator, other: Aggregator) =
           {
              agg.count += other.count
              addWithLimit(agg, other.categs)
              agg
           }
        }
    

    我做了类似的事情,但在我的情况下,只想保留5个左右的字符串供参考,如果你可以限制字符串集更易于管理,也可能是你的选择。但无论如何,请注意您收到的与第一个参数相同的聚合器是如何返回的。

    1. 如果没有任何效果,您仍然有机会通过减少每个任务的内存需求来解决您的问题。为此,增加分区数量(如何执行此操作的大量资源)。

    2. 尝试小优化。例如,您可以将DateTime替换为long或甚至整数,这通常会显着降低内存分配,并且还可以更快地进行shuffle。作为旁注,请注意键的粒度,如果每毫秒有一个键,则很有可能不会聚合太多!但这与你的记忆问题无关。

    3. 希望它有所帮助!