【问题标题】:Akka HTTP Connection Pool Hangs After Couple of HoursAkka HTTP 连接池在几个小时后挂起
【发布时间】:2017-02-27 09:19:09
【问题描述】:

我有一个 HTTP 连接池在运行几个小时后挂起:

private def createHttpPool(host: String): SourceQueue[(HttpRequest, Promise[HttpResponse])] = {
    val pool = Http().cachedHostConnectionPoolHttps[Promise[HttpResponse]](host)
    Source.queue[(HttpRequest, Promise[HttpResponse])](config.poolBuffer, OverflowStrategy.dropNew)
      .via(pool).toMat(Sink.foreach {
        case ((Success(res), p)) => p.success(res)
        case ((Failure(e), p)) => p.failure(e)
      })(Keep.left).run
  }

我将项目排入队列:

private def enqueue(uri: Uri): Future[HttpResponse] = {
    val promise = Promise[HttpResponse]
    val request = HttpRequest(uri = uri) -> promise

    queue.offer(request).flatMap {
      case Enqueued => promise.future
      case _ => Future.failed(ConnectionPoolDroppedRequest)
    }
}

并像这样解决响应:

private def request(uri: Uri): Future[HttpResponse] = {
    def retry = {
      Thread.sleep(config.dispatcherRetryInterval)
      logger.info(s"retrying")
      request(uri)
    }

    logger.info("req-start")
    for {
      response <- enqueue(uri)

      _ = logger.info("req-end")

      finalResponse <- response.status match {
        case TooManyRequests => retry
        case OK => Future.successful(response)
        case _ => response.entity.toStrict(10.seconds).map(s => throw Error(s.toString, uri.toString))
      }
    } yield finalResponse
}

如果 Future 成功,则此函数的结果总是被转换:

def get(uri: Uri): Future[Try[JValue]] = {
  for {
    response <- request(uri)
    json <- Unmarshal(response.entity).to[Try[JValue]]
  } yield json
}

在一段时间内一切正常,然后我在日志中看到的只是 req-start 而没有 req-end。

我的akka​​配置是这样的:

akka {
  actor.deployment.default {
    dispatcher = "my-dispatcher"
  }
}

my-dispatcher {
  type = Dispatcher
  executor = "fork-join-executor"

  fork-join-executor {
    parallelism-min = 256
    parallelism-factor = 128.0
    parallelism-max = 1024
  }
}

akka.http {
  host-connection-pool {
    max-connections = 512
    max-retries = 5
    max-open-requests = 16384
    pipelining-limit = 1
  }
}

我不确定这是配置问题还是代码问题。我的并行度和连接数非常高,因为没有它我的请求/秒速率非常差(我想尽可能快地请求 - 我有其他速率限制代码来保护服务器)。

【问题讨论】:

    标签: scala akka akka-http


    【解决方案1】:

    您没有使用从服务器返回的响应的实体。引用以下文档:

    消耗(或丢弃)请求的实体是强制性的!如果 意外留下既没有消耗也没有丢弃 Akka HTTP 将假定 传入的数据应保持背压,并将停止 通过 TCP 背压机制传入的数据。客户应该 无论 HttpResponse 的状态如何,都使用实体。

    实体以Source[ByteString, _] 的形式出现,需要运行它以避免资源匮乏。

    如果您不需要读取实体,消耗实体字节的最简单方法是丢弃它们,通过使用

    res.discardEntityBytes()
    

    (您可以通过添加 - 例如 - .future().map(...) 来附加回调)。

    This page in the docs 描述了所有替代方案,包括如何在需要时读取字节。

    --- 编辑

    提供更多代码/信息后,很明显资源消耗不是问题。在这个实现中还有一个很大的危险信号,即重试方法中的Thread.sleep。 这是一个阻塞调用,很可能会使底层 Actor 系统的线程基础设施饿死。

    docs 中提供了关于为什么这是危险的完整解释。

    尝试更改它并使用akka.pattern.after (docs)。下面的例子:

    def retry = akka.pattern.after(200 millis, using = system.scheduler)(request(uri))
    

    【讨论】:

    • 在收到响应后,我实际上确实使用了实体。我会用更多信息更新帖子。
    • 刚刚更改了我的代码以使用 akka.pattern.after,如果问题再次出现,将发布更新。不过,我确实分析了早期的 Thread.sleep 代码,并且分析器显示当它停止工作时没有任何线程处于睡眠状态。每当我收到 429 时,jvisualvm 都会显示其中一个线程正在休眠大约 500 毫秒,然后该线程再次开始运行,所以我有点怀疑使用调度程序是否会修复它。尽管如此,使用 Thread.sleep 确实很糟糕 - 感谢您为我提供了一个很好的解决方案来解决这个问题。
    • 我遇到了同样的问题。这是线程转储:gist.github.com/pradyuman/bf83a8f3a293d8c679fcb6dc5f566a80
    猜你喜欢
    • 2017-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多