【问题标题】:Apache HttpClient PoolingHttpClientConnectionManager leaking connections?Apache HttpClient PoolingHttpClientConnectionManager 泄漏连接?
【发布时间】:2014-04-11 00:12:51
【问题描述】:

我在 Scala 应用程序中使用 Apache Http 客户端。

该应用程序具有相当高的吞吐量和高并行性。

我不确定,但我想我可能正在泄漏连接。似乎每当使用客户端的代码部分变得繁忙时,应用程序就会变得无响应。我怀疑我正在泄漏套接字或导致应用程序的其他方面停止工作的东西。它也可能不是泄漏连接,而是没有足够快地关闭它们。

对于更多上下文,有时,某些操作会导致此代码每分钟并行执行数百次。发生这种情况时,应用程序的 Rest API (Spray) 变得无响应。应用程序的其他区域也以高并行度运行,并且不会导致应用程序响应性出现问题。

减少这部分代码的并行性似乎确实可以缓解问题,但不是一个可行的长期解决方案。

是我忘记配置了,还是配置不正确?

我使用的代码是这样的:

class SomeClass {
  val connectionManager = new PoolingHttpClientConnectionManager()
  connectionManager.setDefaultMaxPerRoute(50)
  connectionManager.setMaxTotal(500)
  val httpClient = HttpClients.custom().setConnectionManager(connectionManager).build()

  def postData() {
    val post = new HttpPost("http://SomeUrl") // Typically this URL is fixed.  It doesn't vary much if at all.
    post.setEntity(new StringEntity("Some Data"))
    try {
      val response = httpClient.execute(post)
      try {
        // Check the response
      } finally {
        response.close()
      }
    } finally {
      post.releaseConnection()
    }
  }
}

编辑

我可以看到我在 TIME_WAIT 状态下建立了很多连接。我尝试将 DefaultMaxPerRoute 和 MaxTotal 调整为各种值,但没有明显效果。似乎我遗漏了一些东西,因此连接没有被重新使用,但我找不到任何表明我遗漏的文档。重用这些连接至关重要。

编辑 2

通过进一步调查,使用 lsof -p,我可以看到,如果我将 MaxPerRoute 设置为 10,实际上有 10 个连接被列为“ESTABLISHED”。我可以看到端口号没有改变。在我看来,这似乎意味着它实际上是在重用连接。

这不能解释为什么我仍然在这段代码中泄漏连接?以 TIME_WAIT 状态显示的重用连接和泄漏连接(使用 netstat -a 找到)共享相同的基本 url。所以它们肯定是相关的。是否有可能我正在重新使用连接,但不知何故没有正确关闭响应?

编辑 3

找到TIME_WAIT“泄漏”的来源。它位于不相关的代码部分中。所以这与 HttpClient 没有任何关系。然而,在修复该代码后,所有 TIME_WAIT 都消失了,但应用程序在多次点击 HttpClient 代码时仍然变得无响应。仍在调查那部分。

【问题讨论】:

  • 要分析为什么你的 spray 应用程序变得无响应(如果你认为这可能是一个问题),你可能需要收集一些堆栈跟踪来查看 Akka/spray 线程在哪里花费时间。在控制台上使用jpsjstack 来执行此操作。随时在喷雾邮件列表上发布有关它的信息。

标签: scala apache-httpclient-4.x apache-commons-httpclient spray


【解决方案1】:

您真的应该考虑重用 HttpClient 实例或至少是支持它的连接池,而不是为每个新请求执行创建它们。如果您希望继续执行后者,您还应该在它们超出范围之前关闭客户端或关闭连接池。

就泄漏而言,通过运行应用程序并使用上下文日志记录连接管理结果应该相对容易跟踪,如here 所述

【讨论】:

  • 上面的示例代码并没有完全清楚,但上面的类只被创建一次。因此,HttpClient 和 ConnectionManager 被重用于每个请求。
  • 我明白了。每当此类的实例被取消引用或即将超出范围时,您仍然应该关闭连接池。此外,关于连接管理上下文日志的内容仍然存在。如果所有连接都正确释放回池,这至少应该让您了解
【解决方案2】:

IMO - 如果您有效地使用 http,您可以在每个域中使用更少的 maxConnection 数量(比如 5 而不是 50 )并且仍然完全饱和您的网络带宽。

我不是 scala 人(android、java),但在 http 客户端线程池上做了很多优化。 IMO - 盲目地将每个域的连接增加到 50 掩盖了吞吐量的其他一些严重问题。

2 分:

如果您使用共享的“sharedPoolingClientConnManager”,正确地进入每个域的一个小池,并且您遵循将您的 conn 释放回池的推荐方式(您应该能够调试所有这些,查看运行指标每个线程池实例的连接状态)那么你应该很好。

无论 scala 的并行特性如何,您都应该了解域上池中的 5 个相应线程如何共享套接字?来自 android/java 经验的 IMO 是,即使每个线程执行程序都应该在 httpclient.exec 语句的范围内对服务器进行阻塞 I/O,但所涉及的实际通道管理允许非常高的吞吐量,而无需求助于 ASNyC 客户端库http。

Android 体验可能不相关,因为客户端只有 4 个线程。话虽如此,即使您有 64 个或更多线程可用,我只是不明白每个域需要超过 10 个连接才能使您的底层 http 套接字非常非常忙于吞吐量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 2011-02-18
    • 1970-01-01
    • 2023-03-06
    相关资源
    最近更新 更多