Mongodb &引用;IllegalStateException:状态应为“打开”;将mapPartitions与Mongo连接器一起使用时 设置
我有一个简单的Spark应用程序,它使用Mongodb &引用;IllegalStateException:状态应为“打开”;将mapPartitions与Mongo连接器一起使用时 设置,mongodb,apache-spark,Mongodb,Apache Spark,我有一个简单的Spark应用程序,它使用mapPartitions转换RDD。作为此转换的一部分,我从Mongo数据库检索一些必要的数据。从Spark worker到Mongo数据库的连接使用Spark()的MongoDB连接器进行管理 我使用的是mapPartitions而不是更简单的map,因为存在一些相对昂贵的设置,一个分区中的所有元素只需要一次。如果改用map,则必须对每个元素分别重复此设置 问题 当源RDD中的一个分区变得足够大时,转换将失败,并显示消息 IllegalStateExc
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.setProperty(“spark.mongodb.keep_alive_ms”,desiredValue)
或命令行选项-Dspark.mongodb.keep_alive_ms=desiredValue
设置。然后,MongoConnector
singleton对象读取该值,并将其传递给MongoClientCache
。但是,设置此属性的两种方法实际上都不起作用:
- 从驱动程序调用
,仅在JVM中为Spark驱动程序设置值,而在Spark worker的JVM中需要该值System.setProperty()
- 从辅助程序调用
仅在System.setProperty()
读取值后才设置该值MongoConnector
- 再次将命令行选项
传递给Spark选项-Dspark.mongodb.keep_alive_ms
,仅设置驱动程序JVM中的值Spark.driver.extraJavaOptions
- 将命令行选项传递给Spark选项
会导致来自Spark的错误消息:Spark.executor.extraJavaOptions
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 => ... }
}
}
}