如何使用Spark以并行方式有效地将数据发送到REST端点?

时间:2016-07-26 23:49:33

标签: apache-spark parallel-processing okhttp3

我有一个存储在HDFS中的巨大文件mydata.txt,其中每行包含必须提交给REST端点的数据。我想知道如何有效地分组/分区数据(文件中的行),然后使用OkHttp将它们提交到REST端点。我想对数据进行分组/分区,因为我不想创建太多的HTTP客户端,也不想分配工作负载。

例如,我目前有类似的内容。

val sc = new SparkContext(new SparkConf())
val client = new OkHttpClient
val input = "hdfs://myserver/path/to/mydata.txt"

sc.textFile(input)
 .foreach(line => {
  val request = new Request.Builder()
   .url("http://anotherserver/api/data")
   .post(RequestBody.create(MediaType.parse("application/json"), line))
   .build()
  client.newCall(request).execute()
 })

据我了解,foreachAction所以它在驱动程序上调用,因此,client不必序列化并可用于所有数据(线)。当然,这种解决方案不是并行化的。

我也考虑过分区,但我认为foreachPartition也是Action

sc.textFile(input)
 .map(line => (Random.nextInt(10), line))
 .partitionBy(new HashPartitioner(10))
 .foreachPartition(iter => {
  while(iter.hasNext) {
   val item = iter.next()
   val line = item._2
   //submit to REST endpoint
  }
 })

有关如何使用Spark并行化向REST端点提交数据的工作的想法吗?

编辑事实证明OkHttpClient不可序列化,甚至无法在foreach循环中使用。

1 个答案:

答案 0 :(得分:1)

解决这些类型问题的典型方法如下:

  1. 确保您要使用的REST库可供所有执行程序使用。这样就无需担心序列化。

  2. 按核心数选择并发级别。

  3. 重新分区您的数据,以便#partititions> = k * #executors。当访问具有可变吞吐量的外部服务时,我使用较大的k,例如5-10,以减少一批"缓慢"的可能性。输入减缓了整个工作。

  4. map()数据并在映射函数体内设置客户端,从而消除了序列化问题。返回一对输入和成功/失败以及任何诊断信息。

  5. 过滤失败并决定如何处理它们,例如重新处理它们(您甚至可以保留重试次数)。

  6. 如果设置HTTP客户端的费用很高,请使用mapPartitions()代替map(),因为它允许您设置客户端一次并使用它处理许多输入。

    基本版本:

    def restCall(url: String): MyResultOrError = ...
    val numCoresPerExecutor = ...
    val numCores = numCoresPerExecutor * (sc.getExecutorStorageStatus.length - 1)
    val result = rdd
      .repartition(5 * numCores)
      .map(url => (url, restCall(url)))