如何确保我的 HttpClient 4.1 不会泄漏套接字?

2022-09-04 03:15:55

我的服务器使用来自内部 Web 服务的数据来构造其响应,基于每个请求。我正在使用Apache HttpClient 4.1来发出请求。每个初始请求将产生对 Web 服务的大约 30 个请求。其中,4 - 8个将最终将插槽卡在CLOSE_WAIT中,这些插槽永远不会被释放。最终,这些卡住的套接字超过了我的ulimit,我的进程用完了文件描述符。

我不想只是提出我的ulimit(1024),因为这只会掩盖问题。

我迁移到HttpClient的原因是java.net.HttpUrlConnection的行为方式相同。

我尝试过根据每个请求移动到 SingleClientConnManager,并在其上调用 client.getConnectionManager().shutdown(),但套接字仍然卡住。

我应该尝试解决这个问题,以便在没有运行请求的情况下最终得到0个打开的套接字,还是应该专注于请求持久性和池化?

为了清楚起见,我包括了一些可能相关的细节:

操作系统: Ubuntu 10.10

JRE: 1.6.0_22

语言: Scala 2.8

示例代码:

val cleaner = Executors.newScheduledThreadPool(1) 
private val client = {
    val ssl_ctx = SSLContext.getInstance("TLS")
    val managers = Array[TrustManager](TrustingTrustManager)
    ssl_ctx.init(null, managers, new java.security.SecureRandom())
    val sslSf = new org.apache.http.conn.ssl.SSLSocketFactory(ssl_ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
    val schemeRegistry = new SchemeRegistry()
    schemeRegistry.register(new Scheme("https", 443, sslSf))
    val connection = new ThreadSafeClientConnManager(schemeRegistry)
    object clean extends Runnable{ 
        override def run = {
            connection.closeExpiredConnections
            connection.closeIdleConnections(30, SECONDS)
        }
    }
    cleaner.scheduleAtFixedRate(clean,10,10,SECONDS)
    val httpClient = new DefaultHttpClient(connection)
    httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), new UsernamePasswordCredentials(username,password))
    httpClient
}
val get = new HttpGet(uri)
val entity = client.execute(get).getEntity
val stream = entity.getContent
val justForTheExample = IOUtils.toString(stream)
stream.close()

测试:netstat -a |grep {myInternalWebServiceName} |格雷普CLOSE_WAIT

(列出处于CLOSE_WAIT状态的进程的套接字)

发表评论讨论:

此代码现在演示了正确的用法。


答案 1

需要主动从连接池中逐出过期/空闲的连接,因为在阻塞 I/O 模型中,除非从中读取/写入,否则连接无法对 I/O 事件做出反应。有关详细信息,请参阅

http://hc.apache.org/httpcomponents-client-dev/tutorial/html/connmgmt.html#d4e631


答案 2

我已将oleg的答案标记为正确,因为它突出显示了有关HttpClient连接池的重要使用点。

但是,为了回答我最初的具体问题,即“我应该尝试解决0个未使用的套接字还是尝试最大化池?

现在,池化解决方案已准备就绪且工作正常,应用程序吞吐量增加了约 150%。我将此归因于不必重新协商SSL和多次握手,而是根据HTTP 1.1重用持久连接。

绝对值得按照预期利用池化,而不是试图在每个请求后调用ThreadSafeClientConnManager.shutdown()等等。另一方面,如果您调用任意主机并且没有像我一样重用路由,您可能会很容易发现有必要进行这种黑客攻击,因为如果您不经常进行垃圾收集,JVM可能会让您感到惊讶,CLOSE_WAIT指定套接字的长寿命。


推荐