代码之家  ›  专栏  ›  技术社区  ›  codebox

使用Spark Collection Accumulator时的ConcurrentModificationException异常

  •  7
  • codebox  · 技术社区  · 7 年前

    我正试图在Azure HDInsight按需集群上运行一个基于Spark的应用程序,并看到许多SparkException(由ConcurrentModificationException引起)被记录。启动本地Spark实例时,应用程序运行时没有这些错误。

    我看过关于 similar errors when using accumulators 我的代码确实使用了CollectionAccumulator,但是我在使用它的任何地方都放置了同步块,这没有什么区别。与累加器相关的代码如下所示:

    class MySparkClass(sc : SparkContext) {
        val myAccumulator = sc.collectionAccumulator[MyRecord]
    
        override def add(record: MyRecord) = {
            synchronized {
                myAccumulator.add(record)
            }
        }
    
        override def endOfBatch() = {
            synchronized {
                myAccumulator.value.asScala.foreach((record: MyRecord) => {
                    processIt(record)
                })
            }
        }
    }
    

    但是,异常不会导致应用程序失败 endOfBatch 调用时,代码尝试从累加器中读取值。累加器为空且 processIt 从来没有人打过电话。

    我们正在使用 HDInsight version 3.6 使用Spark 2.3.0版

    18/11/26 11:04:37 WARN Executor: Issue communicating with driver in heartbeater
    org.apache.spark.SparkException: Exception thrown in awaitResult: 
        at org.apache.spark.util.ThreadUtils$.awaitResult(ThreadUtils.scala:205)
        at org.apache.spark.rpc.RpcTimeout.awaitResult(RpcTimeout.scala:75)
        at org.apache.spark.rpc.RpcEndpointRef.askSync(RpcEndpointRef.scala:92)
        at org.apache.spark.executor.Executor.org$apache$spark$executor$Executor$$reportHeartBeat(Executor.scala:785)
        at org.apache.spark.executor.Executor$$anon$2$$anonfun$run$1.apply$mcV$sp(Executor.scala:814)
        at org.apache.spark.executor.Executor$$anon$2$$anonfun$run$1.apply(Executor.scala:814)
        at org.apache.spark.executor.Executor$$anon$2$$anonfun$run$1.apply(Executor.scala:814)
        at org.apache.spark.util.Utils$.logUncaughtExceptions(Utils.scala:1988)
        at org.apache.spark.executor.Executor$$anon$2.run(Executor.scala:814)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
    Caused by: java.util.ConcurrentModificationException
        at java.util.ArrayList.writeObject(ArrayList.java:770)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
        at java.util.Collections$SynchronizedCollection.writeObject(Collections.java:2081)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:43)
        at org.apache.spark.rpc.netty.RequestMessage.serialize(NettyRpcEnv.scala:565)
        at org.apache.spark.rpc.netty.NettyRpcEnv.ask(NettyRpcEnv.scala:231)
        at org.apache.spark.rpc.netty.NettyRpcEndpointRef.ask(NettyRpcEnv.scala:523)
        at org.apache.spark.rpc.RpcEndpointRef.askSync(RpcEndpointRef.scala:91)
        ... 13 more
    

    下面的代码是一个更加独立的示例,重现了这个问题。 MyRecord 是一个只包含数值的简单case类。代码在本地运行时不会出错,但在HDInsight集群上会产生上述错误。

    object MainDemo {
        def main(args: Array[String]) {
            val sparkContext = SparkSession.builder.master("local[4]").getOrCreate().sparkContext
            val myAccumulator = sparkContext.collectionAccumulator[MyRecord]
    
            sparkContext.binaryFiles("/my/files/here").foreach(_ => {
                for(i <- 1 to 100000) {
                    val record = MyRecord(i, 0, 0)
                    myAccumulator.add(record)
                }
            })
    
            myAccumulator.value.asScala.foreach((record: MyRecord) => {
                // we expect this to be called once for each record that we 'add' above,
                // but it is never called
                println(record)
            })
        }
    }
    
    2 回复  |  直到 7 年前
        1
  •  1
  •   H Roy    7 年前

    我怀疑同步块是否真的有帮助。CustomeAccumerator或所有其他累加器都不是线程安全的。自达格事件以来,他们真的不必这么做。UpdateAccumerators方法spark驱动程序用于在任务完成(成功或失败)后更新累加器的值,该方法仅在运行调度循环的单个线程上执行。除此之外,它们是工人的只写数据结构,工人有自己的本地累加器引用,而访问累加器的值只有司机允许。 当你说它在本地模式下工作,因为它是单个JVM,但在集群模式下,它们是不同的JVM和java实例,PRC调用被触发以启用通信。

    MyRecord对象的外观,以及是否以。值,而不是在其上使用迭代器将有所帮助。试试看。

    myAccumulator.value
    
        2
  •  -1
  •   simpadjo    7 年前

    只有在RDD上的某些操作被调用之后,才读取累加器是有意义的( collect 或 count ).

    此外,不需要在累加器上进行同步,因为每个分区都会分配累加器的独立副本。

    推荐文章