HttpClient ConnectionEvictor线程过多问题的排查

最近监控发现服务运行一段时间之后,线程数会缓慢升高,于是通过jstack命令将线程堆栈信息打印出来,根据线程名称进行统计发现Connection evictor线程居然有两百多个,如下:

通过排查该线程是用来清理httpclient过期连接的,每生成一个Apache Httpclient对象就会对应生成一个Connection evictor线程,该线程不会主动释放,除非手动调用httpclient.close(),该线程才会关闭。

系统中共有几十处创建httpclient的地方,远远达不到两百多个,因此该情况肯定是不正常的,那么到底是哪个地方在不断的创建httpclient呢?通过思考和代码审查,并未发现异常的地方,测试环境因流量较少或未触发相关逻辑,并未出现线程数缓慢升高的情况。因此问题变得棘手起来,使用HttpClient有几十处且第三方jar包也有使用HttpClient的情况,范围太广,实在无从下手。

那么我们是否可以从线程创建的源头下手呢?如找到该线程创建的源头,然后将堆栈信息打印出来,这样的话我们就能够知道到底是哪些地方在创建该线程。

说干就干。

首先,我们需要在源码中找到创建该线程的地方,该线程是由HttpClientBuilder创建,在HttpClientBuilder的build方法中有如下代码:

        if (!this.connManagerShared) {
            if (closeablesCopy == null) {
                closeablesCopy = new ArrayList<Closeable>(1);
            }
            final HttpClientConnectionManager cm = connManagerCopy;

            if (evictExpiredConnections || evictIdleConnections) {
                final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
                        maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,
                        maxIdleTime, maxIdleTimeUnit);
                closeablesCopy.add(new Closeable() {

                    @Override
                    public void close() throws IOException {
                        connectionEvictor.shutdown();
                        try {
                            connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
                        } catch (final InterruptedException interrupted) {
                            Thread.currentThread().interrupt();
                        }
                    }

                });
                connectionEvictor.start();
            }
            closeablesCopy.add(new Closeable() {

                @Override
                public void close() throws IOException {
                    cm.shutdown();
                }

            });
        }
该方法在每次创建HttpClient的时候就会创建一个IdleConnectionEvictor对象,该对象就会启动一个名称为Connection evictor的线程。

找到了创建线程的地方,我们就可以通过埋点的方式监控线程的生成情况。从Github中下载自己项目中所使用的HttpClient对应版本的源码,找到HttpClientBuilder源码,

首先,我们需要添加如下的堆栈打印方法,并将打印的堆栈保存到文件之中:

   public static void printStackTrace() {
        // 使用PrintWriter打印堆栈跟踪信息
        try (PrintWriter writer = new PrintWriter(new FileWriter("logs/stacktrace.txt", true))) {
            writer.println("-----------------stack-begin------------------");
            for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
                writer.println(element);
            }
            writer.println("-----------------stack-end------------------");
        } catch (Exception e) {
            e.printStackTrace();  // 如果发生异常,打印异常信息
        }
    }

然后,我们需要在build方法中引入该方法:

public CloseableHttpClient build() {
    //获取当前线程的堆栈跟踪信息
    printStackTrace();
    //接下来是源码中的代码,不要动
    .....
}

之后,我们需要修改maven坐标,对修改之后的jar包进行编译,并上传到私服。

修改pom文件,引入修改之后的jar包,并排除项目中对老的httpclient jar包依赖,使我们引入的新jar包生效。因本次修改不会对代码产生任何影响,直接更新线上一台服务器进行排查。注意:上线之前要在测试环境测试,堆栈打印是否生效,多测试,避免产生问题。

最后,等服务器产生堆栈日志,下载下来分析就好了,这样就能通过堆栈信息轻松定位到了生成HttpClient最多的代码,并进行修改,如此就能轻松解决了。

如果感觉有收获,麻烦点赞+关注!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
HttpClient是一个线程安全的HTTP客户端库,可以在多线程环境下使用。如果需要在多线程环境下使用HttpClient,可以将HttpClient实例化为单例模式或使用连接池来管理连接。 以下是使用HttpClient进行多线程的示例代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; public class HttpClientMultiThreadDemo { private static final String URL = "https://www.example.com"; public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executorService.execute(() -> { try { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpGet request = new HttpGet(URL); HttpResponse response = httpClient.execute(request); System.out.println(response.getStatusLine().getStatusCode()); } catch (Exception e) { e.printStackTrace(); } }); } executorService.shutdown(); } } ``` 在这个示例中,使用了`ExecutorService`来管理线程池,每个线程都会执行一个HTTP GET请求。HttpClient的实例是在每个线程中创建的,因此可以保证线程安全。在使用完HttpClient实例后,需要关闭它,以释放连接和资源。 注意,使用HttpClient进行多线程时,需要注意连接池的配置,以避免因为连接过多而导致性能问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值