使用异步HTTP调用进行Spark作业

时间:2016-03-09 18:36:04

标签: scala apache-spark future

我从url列表构建一个RDD,然后尝试使用一些异步http调用来获取数据。 在进行其他计算之前我需要所有结果。 理想情况下,我需要在不同节点上进行http调用以进行缩放考虑。

我做了类似的事情:

//init spark
val sparkContext = new SparkContext(conf)
val datas = Seq[String]("url1", "url2")

//create rdd
val rdd = sparkContext.parallelize[String](datas)

//httpCall return Future[String]
val requests = rdd.map((url: String) => httpCall(url))

//await all results (Future.sequence may be better)
val responses = requests.map(r => Await.result(r, 10.seconds))

//print responses
response.collect().foreach((s: String) => println(s))

//stop spark
sparkContext.stop()

这项工作,但Spark工作永远不会完成!

所以我想知道使用Spark(或Future [RDD])处理Future的最佳实践是什么。

我认为这个用例看起来很常见,但还没有找到任何答案。

祝你好运

4 个答案:

答案 0 :(得分:8)

  

这个用例看起来很常见

不是真的,因为它根本无法正常工作(可能)。由于每个任务都在标准Scala Iterators上运行,因此这些操作将被压缩在一起。这意味着所有操作都将在实践中阻塞。假设您有三个URL [“x”,“y”,“z”],您的代码将按以下顺序执行:

Await.result(httpCall("x", 10.seconds))
Await.result(httpCall("y", 10.seconds))
Await.result(httpCall("z", 10.seconds))

您可以轻松地在本地重现相同的行为。如果要异步执行代码,则应使用mapPartitions显式处理此代码:

rdd.mapPartitions(iter => {
  ??? // Submit requests
  ??? // Wait until all requests completed and return Iterator of results
})

但这相对比较棘手。无法保证给定分区的所有数据都适合内存,因此您可能也需要一些批处理机制。

所有这一切都说我无法重现你所描述的问题可能是一些配置问题或httpCall本身的问题。

在旁注上允许单个超时杀死整个任务看起来不是一个好主意。

答案 1 :(得分:2)

我无法找到实现这一目标的简单方法。但经过几次迭代重试之后,这就是我所做的,它为大量的查询工作。基本上我们使用它来进行批量操作,以便对多个子查询进行大量查询。

// Break down your huge workload into smaller chunks, in this case huge query string is broken 
// down to a small set of subqueries
// Here if needed to optimize further down, you can provide an optimal partition when parallelizing
val queries = sqlContext.sparkContext.parallelize[String](subQueryList.toSeq)

// Then map each one those to a Spark Task, in this case its a Future that returns a string
val tasks: RDD[Future[String]] = queries.map(query => {
    val task = makeHttpCall(query) // Method returns http call response as a Future[String]
    task.recover { 
        case ex => logger.error("recover: " + ex.printStackTrace()) }
    task onFailure {
        case t => logger.error("execution failed: " + t.getMessage) }
    task
})

// Note:: Http call is still not invoked, you are including this as part of the lineage

// Then in each partition you combine all Futures (means there could be several tasks in each partition) and sequence it
// And Await for the result, in this way you making it to block untill all the future in that sequence is resolved

val contentRdd = tasks.mapPartitions[String] { f: Iterator[Future[String]] =>
   val searchFuture: Future[Iterator[String]] = Future sequence f
   Await.result(searchFuture, threadWaitTime.seconds)
}

// Note: At this point, you can do any transformations on this rdd and it will be appended to the lineage. 
// When you perform any action on that Rdd, then at that point, 
// those mapPartition process will be evaluated to find the tasks and the subqueries to perform a full parallel http requests and 
// collect those data in a single rdd. 

如果您不想对内容执行任何转换,例如解析响应有效负载等,那么您可以使用foreachPartition代替mapPartitions来立即执行所有这些http调用。

答案 2 :(得分:1)

这不行。

您不能指望请求对象被分发,并且其他节点通过群集收集响应。如果你这样做,那么未来的火花召唤永远不会结束。在这种情况下,期货将无法运作。

如果你的map()发出同步(http)请求,那么请在同一个动作/转换调用中收集响应,然后对结果(响应)进行进一步的map / reduce / other调用。

在你的情况下,请重写逻辑收集每个呼叫同步的响应并删除未来的概念然后一切都应该没问题。

答案 3 :(得分:1)

我终于使用scalaj-http而不是Dispatch。 调用是同步的,但这符合我的用例。

我认为Spark Job从未使用Dispatch完成,因为Http连接未正确关闭。

最好的问候