使用Spark Cassandra Connector

时间:2018-06-26 14:41:41

标签: apache-spark cassandra spark-cassandra-connector

我在Cassandra的DataStax Spark连接器上遇到问题。我的应用程序包含一个Spark操作,该操作在Cassandra数据库上执行许多单记录查询。这些查询中的许多查询将成功,但是在某些时候,其中一个查询将失败,并显示一条NoHostAvailableException,并显示消息All host(s) tried for query failed (no host was tried)

堆栈跟踪

2018-06-26 12:32:09 ERROR Executor:91 - Exception in task 0.3 in stage 0.0 (TID 6)
com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (no host was tried)
    at com.datastax.driver.core.exceptions.NoHostAvailableException.copy(NoHostAvailableException.java:84)
    at com.datastax.driver.core.exceptions.NoHostAvailableException.copy(NoHostAvailableException.java:37)
    at com.datastax.driver.core.DriverThrowables.propagateCause(DriverThrowables.java:37)
    at com.datastax.driver.core.DefaultResultSetFuture.getUninterruptibly(DefaultResultSetFuture.java:245)
    at com.datastax.driver.core.AbstractSession.execute(AbstractSession.java:68)
    at sun.reflect.GeneratedMethodAccessor10.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.datastax.spark.connector.cql.SessionProxy.invoke(SessionProxy.scala:40)
    at com.sun.proxy.$Proxy15.execute(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor10.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.datastax.spark.connector.cql.SessionProxy.invoke(SessionProxy.scala:40)
    at com.sun.proxy.$Proxy16.execute(Unknown Source)
    at [line that contains the session.execute() call]
    [...]
Caused by: com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (no host was tried)
    at com.datastax.driver.core.RequestHandler.reportNoMoreHosts(RequestHandler.java:211)
    at com.datastax.driver.core.RequestHandler.access$1000(RequestHandler.java:46)
    at com.datastax.driver.core.RequestHandler$SpeculativeExecution.findNextHostAndQuery(RequestHandler.java:275)
    at com.datastax.driver.core.RequestHandler.startNewExecution(RequestHandler.java:115)
    at com.datastax.driver.core.RequestHandler.sendRequest(RequestHandler.java:95)
    at com.datastax.driver.core.SessionManager.executeAsync(SessionManager.java:132)
    ... 32 more

为了分析这个问题,我成功地在一个简单的环境中重现了它:

  • 一台运行Cassandra,Spark master和spark worker的机器
  • 一个仅包含100条记录的简单表(10个分区,每个分区10条记录)

下面是我可以用来重现该问题的最少代码。

代码

val pkColumn1Value = 1L
val pkColumn2Values: Dataset[Long] = sparkSession.createDataset(1L to 19 by 2)
val connector: CassandraConnector = [...]

val results: Dataset[SimpleValue] = pkColumn2Values.mapPartitions { iterator =>
    connector.withSessionDo { session =>
        val clusteringKeyValues = Seq(...)

        val preparedStatement = session.prepare("select * from simple_values where pk_column_1_value = ? and pk_column_2_value = ? and clustering_key_value = ?")

        iterator.flatMap { pkColumn2Value =>
            val boundStatements = clusteringKeyValues.iterator.map(clusteringKeyValue =>
                preparedStatement.bind(
                    pkColumn1Value.asInstanceOf[AnyRef]
                    , pkColumn2Value.asInstanceOf[AnyRef]
                    , clusteringKeyValue.asInstanceOf[AnyRef]
                )
            )

            boundStatements.map { boundStatement =>
                val record = try {
                    session.execute(boundStatement).one()
                } catch {
                    case noHostAvailableException: NoHostAvailableException =>
                        log.error(s"Encountered NHAE, getErrors: ${noHostAvailableException.getErrors}")
                        throw noHostAvailableException
                    case exception =>
                        throw exception
                }

                log.error(s"Retrieved record $record")
                // Sleep to simulate an operation being performed on the value.
                Thread.sleep(100)

                record
            }
        }
    }
}

log.error(s"Perfunctory log statement that triggers an action: ${results.collect().last}")

我注意到了一些有趣的事情

  • 我正在使用Dataset#mapPartitions()来为每个分区仅准备一次select语句。当我吞下自豪感而改用Dataset#map()Dataset#flatMap()时,问题消失了,但是我想使用Dataset#mapPartitions()来获得(表面上的)性能优势(每个数据集分区仅准备一次查询) 。
  • 在执行第一个查询之后,NoHostAvailableException似乎发生了固定的时间。一些调查确认,该时间量等于连接器属性spark.cassandra.connection.keep_alive_ms的值。将此属性设置为一个高得离谱的值显然可以解决问题,但这似乎是一种肮脏的解决方法,而不是明智的解决方案。

this GitHub issue中的连接器中,评论者pkolaczk提到了一个潜在的问题,该问题可能导致连接器在与Cassandra的初始连接中成功,并在以后尝试建立其他连接时失败。这听起来很有希望,因为它与上述几点相符(这表明问题只会在原始连接关闭后才会发生,如果为数据集中的每个元素分别重新建立连接就永远不会发生);但是,我找不到任何迹象表明我配置了IP地址或任何其他可能的原因导致此现象(甚至没有确认该现象实际上是造成此问题的原因)。

我已经检查和/或尝试过的某些事情

  • 多个在线资源表明,NoHostAvailableException之前总是有其他错误。我已经多次检查日志,但是找不到其他错误消息或堆栈跟踪。
  • 另一个StackOverflow问题的答案建议调用NoHostAvailableException#getErrors以获得对该问题的更详细说明,但是此方法始终为我返回一个空映射。
  • 当我使用RDD而不是数据集时,问题仍然存在(包括仅在使用mapPartitions时才会发生的事实,而在使用map时不会发生)。
  • 连接器属性spark.cassandra.connection.local_dc最初未设置。将此属性设置为适当的数据中心名称不会对该问题产生明显影响。
  • 我尝试将连接器属性spark.cassandra.connection.timeout_msspark.cassandra.read.timeout_ms设置为高得离谱的值;这对该问题没有明显影响。

某些版本号

  • 火花:重现了2.1.1和2.3.0的问题
  • 卡桑德拉:3.11
  • 连接器:重现了2.0.3和2.3.0的问题
  • 斯卡拉:2.11

任何表示导致这些错误的原因或解决此问题的方法的表示,将不胜感激。

1 个答案:

答案 0 :(得分:0)

我将此问题交叉发布到了连接器的Google用户组(https://groups.google.com/a/lists.datastax.com/d/msg/spark-connector-user/oWrP7qeHJ7k/pmgnF_kbBwAJ),其贡献者之一证实了没有理由不为spark.cassandra.connection.keep_alive_ms赋予很高的价值。我已经将该值提高到可以确定没有任何操作可以通过它的地步,并且此后也没有任何问题。