当前位置: 首页 > news >正文

HttpClient通讯时间过久

问题背景

数据枢纽调用第三方服务时,同一个服务有时候耗时短,有时候耗时长,故做排查
系统配置:
* HttpClient 单路由的最大连接数200、总最大连接数1000
* HttpClient 获取连接请求超时时间(从连接池中获取连接的时间)1000ms、连接超时时间(与数据源请求建立连接的时间)250ms、SocketTimeout(读取超时时间)1000ms
问题现象:

  • 监控告警超时时间达到了2000ms,那我们其实预期的是1000ms超时的。

针对项目中HTTP客户端工具类调用第三方服务耗时较长的问题,以下是逐步排查方案:


1. 确认连接池问题

检查连接池配置
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(1000);       // 总最大连接数
connManager.setDefaultMaxPerRoute(200); // 每个路由(目标主机)的最大连接数
  • 现象:若MaxTotalDefaultMaxPerRoute设置过低,高并发时会出现等待连接。
  • 排查方法
    • 添加连接池监控日志:
      HttpClientContext context = HttpClientContext.create();
      try (CloseableHttpResponse response = httpClient.execute(request, context)) {
          HttpConnectionMetrics metrics = context.getConnection().getMetrics();
          System.out.println("连接请求耗时: " + metrics.getRequestCount());
      }
      
    • 监控连接池状态:
      connManager.getTotalStats().getAvailable(); // 可用连接数
      connManager.getTotalStats().getLeased();    // 被租用的连接数
      
启用连接池警告日志

在log4j或logback中配置:

<!-- HttpClient 连接池日志 -->
<Logger name="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" level="DEBUG"/>
  • 当日志中出现Connection request timeoutTimeout waiting for connection时,说明连接池不足。

2. 分析各阶段耗时(代码级监控)

添加自定义请求拦截器
httpClient = HttpClients.custom()
        .addInterceptorFirst(new HttpRequestInterceptor() {
            @Override
            public void process(HttpRequest request, HttpContext context) {
                context.setAttribute("startTime", System.currentTimeMillis());
            }
        })
        .addInterceptorLast(new HttpResponseInterceptor() {
            @Override
            public void process(HttpResponse response, HttpContext context) {
                long startTime = (Long) context.getAttribute("startTime");
                long dnsTime = (Long) context.getAttribute("dnsTime"); // 需要结合其他拦截器
                long tcpHandshakeTime = ... ; 
                System.out.println("总耗时: " + (System.currentTimeMillis() - startTime));
            }
        })
        .build();
使用Apache HttpClient内置计时

启用详细日志:

<!-- 启用Wire日志(原始网络数据,慎用) -->
<Logger name="org.apache.http.wire" level="DEBUG"/>
<!-- 请求执行时间日志 -->
<Logger name="org.apache.http.impl.execchain" level="TRACE"/>

日志会输出类似:

DEBUG org.apache.http.impl.execchain.MainClientExec - Connection request: 2000 ms
DEBUG org.apache.http.impl.execchain.MainClientExec - Connection established: 150 ms
DEBUG org.apache.http.impl.execchain.MainClientExec - Request sent: 50 ms

3. 网络层抓包分析

使用tcpdump/Wireshark
# 抓取所有进出eth0网卡、目标端口为80或443的流量
tcpdump -i eth0 -w http_capture.pcap 'tcp port 80 or tcp port 443'
  • 分析重点
    • TCP三次握手:SYN到SYN-ACK的时间(判断网络延迟)。
    • SSL/TLS握手:ClientHello到ServerHelloDone的时间(若使用HTTPS)。
    • HTTP请求/响应时间:最后一个请求包到第一个响应包的时间(服务端处理时间)。
使用tcptraceroute检测路由
tcptraceroute -p 443 api.target.com
  • 检测是否存在异常路由节点。

4. 服务端处理时间验证

手动发送测试请求
# 使用curl带时间统计
curl -w '
DNS解析: %{time_namelookup}s\n
TCP连接: %{time_connect}s\n
SSL握手: %{time_appconnect}s\n
请求开始到响应首包: %{time_starttransfer}s\n
总时间: %{time_total}s\n' \
-o /dev/null -s 'https://api.target.com/endpoint'
  • 对比工具类中的耗时,确认问题是否在客户端。

5. 高级工具诊断

使用JProfiler/YourKit分析
  • 重点监控:
    • PoolingHttpClientConnectionManagerleaseConnection方法耗时。
    • SocketInputStream.readSocketOutputStream.write的阻塞时间。
启用JFR(Java Flight Recorder)
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder ...
  • 监控HttpClient相关方法调用栈及耗时。

6. 针对性优化方案

连接池不足
  • 调整MaxTotalDefaultMaxPerRoute,根据压测结果动态设置。
DNS延迟
  • 启用客户端DNS缓存:
    System.setProperty("networkaddress.cache.ttl", "60"); // 设置DNS缓存时间(秒)
    
SSL握手慢
  • 使用更快的加密算法(如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)。
  • 预加载SSL上下文:
    SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(...).build();
    sslContext.createSSLEngine().getSupportedCipherSuites(); // 提前初始化
    

总结排查步骤

  1. 检查连接池日志 → 确认是否因连接池满导致等待。
  2. 添加拦截器或启用TRACE日志 → 定位耗时在连接获取、发送还是读取阶段。
  3. 使用curl对比测试 → 确认是否为服务端问题。
  4. 抓包分析TCP/SSL时间 → 定位网络层问题。
  5. 调整参数复测 → 验证优化效果。

通过以上步骤,可精准定位是连接池瓶颈、网络延迟还是服务端处理慢导致的问题。

相关文章:

  • 计算机网络技术服务管理基于Spring Boot-SSM
  • 前端流式输出实现详解:从原理到实践
  • 模型部署实战:PyTorch生产化指南
  • git clone项目报错fatal: fetch-pack: invalid index-pack output问题
  • 红日靶场(二)——个人笔记
  • TCP心跳消息
  • Multisim学习-01 特点安装使用和第一个仿真实例
  • 计算机组成原理 第六章 总线
  • 图像分割的mask有空洞怎么修补
  • tldr命令助记
  • Qt 控件概述 QLCDNumber 和 Progressbar
  • 手动集成sqlite的方法
  • 我开发的PDF转WORD免费工具
  • 【LangChain入门 4 Prompts组件】提示词追加示例 FewShotPromptTemplate和示例选择器ExampleSelector
  • Vision-R1:用 “冷启动 + 强化学习” 解锁多模态模型的推理能力
  • AI音乐创作原理:解锁创意与算法的完美结合
  • 【AVRCP】蓝牙AVRCP协议中的L2CAP互操作性要求深度解析
  • Servlet介绍(详细)
  • C# 事件机制详解:定义、订阅、触发与应用实践
  • 大数据学习(78)-spark streaming与flink
  • 学习教育期间违规吃喝,李献林、叶金广等人被通报
  • 比特币挖矿公司GRYP股价涨超171%:将与特朗普儿子创设的公司合并
  • 全国汽车以旧换新补贴申请量突破1000万份
  • 中医的千年传承:网络科学描绘其演化之路|PNAS速递
  • 2024年度全国秋粮收购达3.45亿吨
  • 全国层面首次!《防震减灾基本知识与技能大纲》发布