Mongodb &引用;IllegalStateException:状态应为“打开”;将mapPartitions与Mongo连接器一起使用时 设置

Mongodb &引用;IllegalStateException:状态应为“打开”;将mapPartitions与Mongo连接器一起使用时 设置,mongodb,apache-spark,Mongodb,Apache Spark,我有一个简单的Spark应用程序,它使用mapPartitions转换RDD。作为此转换的一部分,我从Mongo数据库检索一些必要的数据。从Spark worker到Mongo数据库的连接使用Spark()的MongoDB连接器进行管理 我使用的是mapPartitions而不是更简单的map,因为存在一些相对昂贵的设置,一个分区中的所有元素只需要一次。如果改用map,则必须对每个元素分别重复此设置 问题 当源RDD中的一个分区变得足够大时,转换将失败,并显示消息 IllegalStateExc

我有一个简单的Spark应用程序,它使用
mapPartitions
转换RDD。作为此转换的一部分,我从Mongo数据库检索一些必要的数据。从Spark worker到Mongo数据库的连接使用Spark()的MongoDB连接器进行管理

我使用的是
mapPartitions
而不是更简单的
map
,因为存在一些相对昂贵的设置,一个分区中的所有元素只需要一次。如果改用
map
,则必须对每个元素分别重复此设置

问题 当源RDD中的一个分区变得足够大时,转换将失败,并显示消息

IllegalStateException: state should be: open
或者,偶尔,与

IllegalStateException: The pool is closed
代码 下面是一个简单的Scala应用程序的代码,我可以用它重现这个问题:

package my.package

import com.mongodb.spark.MongoConnector
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import org.bson.Document

object MySparkApplication {
    def main(args: Array[String]): Unit = {
        val sparkSession: SparkSession = SparkSession.builder()
            .appName("MySparkApplication")
            .master(???) // The Spark master URL
            .config("spark.jars", ???) // The path at which the application's fat JAR is located.
            .config("spark.scheduler.mode", "FAIR")
            .config("spark.mongodb.keep_alive_ms", "86400000")
            .getOrCreate()

        val mongoConnector: MongoConnector = MongoConnector(Map(
            "uri" -> ??? // The MongoDB URI.
            , "spark.mongodb.keep_alive_ms" -> "86400000"
            , "keep_alive_ms" -> "86400000"
        ))

        val localDocumentIds: Seq[Long] = Seq.range(1L, 100L)
        val documentIdsRdd: RDD[Long] = sparkSession.sparkContext.parallelize(localDocumentIds)

        val result: RDD[Document] = documentIdsRdd.mapPartitions { documentIdsIterator =>
            mongoConnector.withMongoClientDo { mongoClient =>
                val collection = mongoClient.getDatabase("databaseName").getCollection("collectionName")
                // Some expensive query that should only be performed once for every partition.
                collection.find(new Document("_id", 99999L)).first()

                documentIdsIterator.map { documentId =>
                    // An expensive operation that does not interact with the Mongo database.
                    Thread.sleep(1000)
                    collection.find(new Document("_id", documentId)).first()
                }
            }
        }

        val resultLocal = result.collect()
    }
}
堆栈跟踪 下面是运行上述应用程序时Spark返回的堆栈跟踪:

Driver stacktrace:
    [...]
    at my.package.MySparkApplication.main(MySparkApplication.scala:41)
    at my.package.MySparkApplication.main(MySparkApplication.scala)
    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 org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:775)
    at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:180)
    at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:205)
    at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:119)
    at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)
Caused by: java.lang.IllegalStateException: state should be: open
    at com.mongodb.assertions.Assertions.isTrue(Assertions.java:70)
    at com.mongodb.connection.BaseCluster.getDescription(BaseCluster.java:152)
    at com.mongodb.Mongo.getConnectedClusterDescription(Mongo.java:885)
    at com.mongodb.Mongo.createClientSession(Mongo.java:877)
    at com.mongodb.Mongo$3.getClientSession(Mongo.java:866)
    at com.mongodb.Mongo$3.execute(Mongo.java:823)
    at com.mongodb.FindIterableImpl.first(FindIterableImpl.java:193)
    at my.package.MySparkApplication$$anonfun$1$$anonfun$apply$1$$anonfun$apply$2.apply(MySparkApplication.scala:36)
    at my.package.MySparkApplication$$anonfun$1$$anonfun$apply$1$$anonfun$apply$2.apply(MySparkApplication.scala:33)
    at scala.collection.Iterator$$anon$11.next(Iterator.scala:409)
    at scala.collection.Iterator$class.foreach(Iterator.scala:893)
    at scala.collection.AbstractIterator.foreach(Iterator.scala:1336)
    at scala.collection.generic.Growable$class.$plus$plus$eq(Growable.scala:59)
    at scala.collection.mutable.ArrayBuffer.$plus$plus$eq(ArrayBuffer.scala:104)
    at scala.collection.mutable.ArrayBuffer.$plus$plus$eq(ArrayBuffer.scala:48)
    at scala.collection.TraversableOnce$class.to(TraversableOnce.scala:310)
    at scala.collection.AbstractIterator.to(Iterator.scala:1336)
    at scala.collection.TraversableOnce$class.toBuffer(TraversableOnce.scala:302)
    at scala.collection.AbstractIterator.toBuffer(Iterator.scala:1336)
    at scala.collection.TraversableOnce$class.toArray(TraversableOnce.scala:289)
    at scala.collection.AbstractIterator.toArray(Iterator.scala:1336)
    at org.apache.spark.rdd.RDD$$anonfun$collect$1$$anonfun$13.apply(RDD.scala:936)
    at org.apache.spark.rdd.RDD$$anonfun$collect$1$$anonfun$13.apply(RDD.scala:936)
    at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2069)
    at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2069)
    at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:87)
    at org.apache.spark.scheduler.Task.run(Task.scala:108)
    at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:338)
    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)
我所做的研究 我发现有几个人在问这个问题,似乎在他们所有的案例中,问题都是他们在Mongo客户端关闭后使用它。据我所知,这在我的应用程序中并没有发生-打开和关闭连接应由Mongo Spark连接器处理,我希望只有在将函数传递给
mongoConnector.withMongoClientDo
返回后,客户端才会关闭

我确实发现RDD中的第一个元素并没有出现问题。取而代之的是,似乎有许多元素正在成功地被处理,并且只有在处理过程花费了一定的时间之后,才会发生失败。这段时间大概是5到15秒

上述情况使我相信,一旦客户端处于活动状态一段时间后,即使它仍在使用,它也会自动关闭客户端

从我的代码可以看出,我发现Mongo Spark连接器公开了一个配置
Spark.mongodb.keep_alive_ms
,根据连接器文档,该配置控制“MongoClient可用于共享的时间长度”。它的默认值是5秒,所以这似乎是一个有用的尝试。在上面的应用程序中,我尝试用三种不同的方式将其设置为一整天,并且没有任何效果。文档中确实说明了此特定属性“只能通过系统属性进行配置”。我想这就是我正在做的(通过在初始化Spark会话和/或Mongo连接器时设置属性),但我不能完全确定。Mongo连接器初始化后,似乎无法验证设置

另一个StackOverflow问题提到我应该尝试在
MongoClientOptions
中设置
maxConnectionIdleTime
选项,但据我所知,无法通过连接器设置这些选项

作为一种合理性检查,我尝试用功能上等价的
map
替换
mapPartitions
的使用。问题消失了,这可能是因为针对RDD的每个单独元素重新初始化了与Mongo数据库的连接。但是,如上所述,这种方法的性能会显著降低,因为我最终会对RDD中的每个元素重复昂贵的设置工作

出于好奇,我还尝试将对
mapPartitions
的调用替换为对
foreachPartition
的调用,并将对
documentidIterator.map
的调用替换为
documentidIterator.foreach
。这个问题在本案中也消失了。我不知道为什么会这样,但因为我需要转换我的RDD,这也是一种不可接受的方法

我正在寻找的答案
  • “您实际上是在过早地关闭客户端,这里是:[…]”
  • “这是Mongo Spark connector中的一个已知问题,下面是指向其问题追踪器的链接:[……]”
  • “您将
    spark.mongodb.keep_alive_ms
    属性设置错误,您应该这样做:[……]”
  • “可以验证Mongo连接器上的
    spark.mongodb.keep_alive_ms
    的值,方法如下:[…]”
  • “可以通过Mongo连接器设置
    MongoClient
    ,例如
    maxConnectionIdleTime
    ,方法如下:[…]
编辑 进一步调查得出以下结论: 连接器文档中使用的短语“System property”指的是Java系统属性,使用
System.setProperty(“spark.mongodb.keep_alive_ms”,desiredValue)
或命令行选项
-Dspark.mongodb.keep_alive_ms=desiredValue
设置。然后,
MongoConnector
singleton对象读取该值,并将其传递给
MongoClientCache
。但是,设置此属性的两种方法实际上都不起作用:

  • 从驱动程序调用
    System.setProperty()
    ,仅在JVM中为Spark驱动程序设置值,而在Spark worker的JVM中需要该值
  • 从辅助程序调用
    System.setProperty()
    仅在
    MongoConnector
    读取值后才设置该值
  • 再次将命令行选项
    -Dspark.mongodb.keep_alive_ms
    传递给Spark选项
    Spark.driver.extraJavaOptions
    ,仅设置驱动程序JVM中的值
  • 将命令行选项传递给Spark选项
    Spark.executor.extraJavaOptions
    会导致来自Spark的错误消息:
引发此错误的Spark代码位于
org.apache.Spark.SparkConf#validateSettings
,其中
Exception in thread "main" java.lang.Exception: spark.executor.extraJavaOptions is not allowed to set Spark options (was '-Dspark.mongodb.keep_alive_ms=desiredValue'). Set them directly on a SparkConf or in a properties file when using ./bin/spark-submit.
val result: RDD[Document] = documentIdsRdd.mapPartitions { documentIdsIterator =>
  val mongoConnector: MongoConnector = MongoConnector(Map(
    "uri" -> ??? // The MongoDB URI.
    , "spark.mongodb.keep_alive_ms" -> "86400000"
    , "keep_alive_ms" -> "86400000"
  ))
  mongoConnector.withMongoClientDo { mongoClient =>
   ...
 }
}
documentIdsRdd.mapPartitions { documentIdsIterator =>
   mongoConnector.withMongoClientDo { mongoClient =>
      // Do some expensive operation
      ...

      // Return the lazy collection
      documentIdsIterator.map { documentId => 
         // Loan the mongoClient
         mongoConnector.withMongoClientDo { mongoClient => ... }
      }
   }
 }