深度解读 | 通过FD耗尽实验谈谈使用HttpClient的正确姿势

0 1一段问题代码实验

在进行网络编程时,正确关闭资源是一件很重要的事。在高并发场景下,未正常关闭的资源数逐渐积累会导致系统资源耗尽,影响系统整体服务能力,但是这件重要的事情往往又容易被忽视。我们进行一个简单的实验,使用HttpClient-3.x编写一个demo请求指定的url,看看如果不正确关闭资源会发生什么事。

public String doGetAsString(String url) {	
        GetMethod getMethod = null;	
        String is = null;	
        InputStreamReader inputStreamReader = null;	
        BufferedReader br = null;	
        try {	
            HttpClient httpclient = new HttpClient();//问题标记①	
            getMethod = new GetMethod(url);	
            httpclient.executeMethod(getMethod);	
            if (HttpStatus.SC_OK == getMethod.getStatusCode()) {	
                ......//对返回结果进行消费,代码省略	
            }	
            return is;	
        } catch (Exception e) {	
            if (getMethod != null) {	
                getMethod.releaseConnection();  //问题标记②              	
            }            	
        } finally {	
            inputStreamReader.close();	
            br.close();	
            ......//关闭流时的异常处理代码省略	
        }	
        return null;	
    }

这段代码逻辑很简单, 先创建一个HttpClient对象,用url构建一个GetMethod对象,然后发起请求。但是用这段代码并发地以极高的QPS去访问外部的url,很快就会在日志中看到“打开文件太多,无法打开文件”的错误,后续的http请求都会失败。这时我们用lsof -p \${javapid}命令去查看java进程打开的文件数,发现达到了655350这么多。

分析上面的代码片段,发现存在以下2个问题: (1)初始化方式不对。标记①直接使用new HttpClient()的方式来创建HttpClient,没有显示指定HttpClient connection manager,则构造函数内部默认会使用SimpleHttpConnectionManager,而SimpleHttpConnectionManager的默认参数中alwaysClose的值为false,意味着即使调用了releaseConnection方法,连接也不会真的关闭。 (2)在未使用连接池复用连接的情况下,代码没有正确调用releaseConnection。catch块中的标记②是唯一调用了releaseConnection方法的代码,而这段代码仅在发生异常时才会走到,大部分情况下都走不到这里,所以即使我们前面用正确的方式初始化了HttpClient,由于没有手动释放连接,也还是会出现连接堆积的问题。

可能有同学会有以下疑问:

(1)明明是发起Http请求,为什么会打开这么多文件呢?为什么是655350这个上限呢?

(2)正确的HttpClient使用姿势是什么样的呢?

这就涉及到linux系统中fd的概念。

0 2什么是fd

在linux系统中有“一切皆文件”的概念。打开和创建普通文件、Socket(套接字)、Pipeline(管道)等,在linux内核层面都需要新建一个文件描述符来进行状态跟踪和使用。我们使用HttpClient发起请求,其底层需要首先通过系统内核创建一个Socket连接,相应地就需要打开一个fd。

为什么我们的应用最多只能创建655350个fd呢?这个值是如何控制的,能否调整呢?事实上,linux系统对打开文件数有多个层面的限制:(1)限制单个Shell进程以及其派生子进程能打开的fd数量。用ulimit命令能查看到这个值。(2)限制每个user能打开的文件总数。具体调整方法是修改/etc/security/limits.conf文件,比如下图中的红框部分就是限制了userA用户只能打开65535个文件,userB用户只能打开655350个文件。由于我们的应用在服务器上是以userB身份运行的,自然就受到这里的限制,不允许打开多于655350个文件。

# /etc/security/limits.conf	
#	
#<domain>      <type>  <item>     <value>	
userA          -   nofile      65535	
userB             -       nofile      655350	
# End of file

(3)系统层面允许打开的最大文件数限制,可以通过“cat /proc/sys/fs/file-max”查看。

前文demo代码中错误的HttpClient使用方式导致连接使用完成后没有成功断开,连接长时间保持CLOSE_WAIT状态,则fd需要继续指向这个套接字信息,无法被回收,进而出现了本文开头的故障。

0 3再识HttpClient

我们的代码中错误使用common-httpclient-3.x导致后续请求失败,那这里的common-httpclient-3.x到底是什么东西呢?相信所有接触过网络编程的同学对HttpClient都不会陌生,由于java.net中对于http访问只提供相对比较低级别的封装,使用起来很不方便,所以HttpClient作为Jakarta Commons的一个子项目出现在公众面前,为开发者提供了更友好的发起http连接的方式。

然而目前进入Jakarta Commons HttpClient官网,会发现页面最顶部的“End of life”栏目,提示此项目已经停止维护了,它的功能已经

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值