Apache HttpClient连接池泄露问题排查

本文详细记录了Apache HttpClient连接池泄露问题的排查过程,从问题背景到源码分析,揭示了由于未完全消费响应内容导致的死锁现象。通过单元测试和源码追踪,发现问题在于Feign请求处理时未正确释放响应输入流,特别是在处理HTTP Chunked响应时。最终,提出了修复方案,即确保在处理响应时正确释放资源。
摘要由CSDN通过智能技术生成

Apache HttpClient连接池泄露问题排查

问题背景

  • 业务系统主要的业务是一个数据聚合管理平台,其中系统有一个功能是同步所有资源(简称 大同步)

  • 业务同步数据请求数据工具是适配 Apache HttpClientFeign ,这种请求封装是我当时根据业务适配业务封装请求 api

  • Feign 版本: 10.10.1

问题来源

  • 在生产环境,大同步功能(20多个任务)发现跑了一半多的任务时候卡住,在测试环境并没有发现这个问题

同步接口

public interface SyncHelper {
   

    Order syncOrder();

    void syncAllAccount();
    
    void syncSingleAccount(Long accountId);
    
    default boolean enableSync() {
   
        return true;
    }
}

大同步功能实现

@Slf4j
@Component
public class SyncAccountResourceListener {
   
    @Autowired
    private final List<SyncHelper> helpers;
    // 单线程线程池
    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
            .setDaemon(false)
            .setNameFormat("h3c-sync-resource-%d")
            .build()
    );
    
    public void sync(){
   
        for (SyncHelper helper : helpers) {
   
            if (Thread.currentThread().isInterrupted()) {
   
                log.error("[{}] sync task interrupted,account:[{}]", className, accountId);
                continue;
            }
            Future<?> future = EXECUTOR.submit(() -> helper.syncSingleAccount(accountId));
            try {
   
                future.get(helper.getTimeOut(), TimeUnit.SECONDS);
            } catch (InterruptedException e) {
   
                Thread.currentThread().interrupt();
            } catch (ExecutionException|TimeoutException e) {
   
                log.error("[{}] sync error,account:[{}]", className, accountId, e);
            } finally {
   
                future.cancel(true);
            }
        }
    }
}

排查步骤

本想着以最快速度解决问题,系统上同步进度列表 显示都卡在同一个同步类,然后粗略看了一下相关同步类的代码,发现并没有相关可能导致死循环的代码

尝试复现

  • 在测试环境测试大同步,发现没问题(包括请求来回数据日志、数据库sql打印日志),顺利完成所有的同步任务
  • 那就针对卡住的同步类做单元测试反复执行多次,结果发现也并没有问题

至此,问题就更加疑惑。并无法在测试环境和本地单元测试复现,生产怎么就会有相关的问题?

死锁

一开始没去排查死锁问题,因为大部分同步都没有用到多线程

可能原因

  • 用到多线程在大同步资源使用单线程的线程池跑任务,然后任务超时 TimeOut 没做好任务中断的处理,导致后面任务全部阻塞
  • 看到有同事同步数据用了多线程,用的不是很合理,类似以下代码:
        List<SysDept> deptList = ......
        List<CompletableFuture<CmdbUsageReport>> futureList = new ArrayList<>();
        deptList.forEach(t -> futureList.add(
                CompletableFuture.supplyAsync(() -> {
   
                    // 耗时任务
                    return report;
                }, ioPool)));
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{
   })).join();
        List<CmdbUsageReport> reportList = futureList.stream().map(CompletableFuture::join).filter(Objects::nonNull).collect(toList());
        // ......

既然排查线程的问题,直接使用相关的分析工具去分析,看一下到底是怎么回事

分析线程状态

使用 阿里 arthas 或者 visualvm 查看同步任务的线程状态

启动 arthas attach 相应进程

java -jar arthas-boot.jar

thread --all 查看所有线程简单信息

arthas查看所有线程信息

使用单线程线程池跑同步任务,执行线程池线程也有自定义名称,名称为 `h3c-sync-resource-0`(进程 ID 为 250 ,线程状态为 `WAITING` )

thread 250 查看同步信息进程的详细信息
arthas查看线程信息

"h3c-sync-resource-0" - Thread t@195
        java.lang.Thread.State: WAITING
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for <66bb3d00> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值