You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache HttpClients连接超时为何是配置值的2倍?

Apache HttpClients ConnectTimeout 实际超时为配置值2倍的原因及解决办法

问题描述

我将Apache HttpClients的ConnectTimeout设置为1000毫秒,禁用了自动重试与重定向,请求一个不存在的URL(http://dns.google/a.mp4),预期1秒后超时,但实际耗时约2秒才抛出NoRouteToHostException。无论修改ConnectTimeout的值,实际超时时间始终是配置值的2倍,使用版本为4.5.13,这是什么原因?

示例代码

public static void main(String[] args) throws Exception {
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(1_000)
            .setSocketTimeout(5_000)
            .setConnectionRequestTimeout(60_000)
            .build();
    
    CloseableHttpClient httpClient = HttpClients.custom()
            .disableAutomaticRetries()
            .disableRedirectHandling()
            .setDefaultRequestConfig(requestConfig)
            .build();
    
    HttpHead httpHead = new HttpHead("http://dns.google/a.mp4");
    httpHead.setHeader("Accept-Encoding", "identity");
    
    final long startTime = System.currentTimeMillis();
    try (CloseableHttpResponse httpResponse = httpClient.execute(httpHead)) {
        final long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Success after " + elapsedTime + " ms");
    } catch (Exception e) {
        final long elapsedTime = System.currentTimeMillis() - startTime;
        System.err.println("Error after " + elapsedTime + " ms");
        e.printStackTrace();
    }
}

报错信息

Error after 2064 ms
java.net.NoRouteToHostException: No route to host
    at java.base/sun.nio.ch.Net.connect0(Native Method)
    at java.base/sun.nio.ch.Net.connect(Net.java:579)
    at java.base/sun.nio.ch.Net.connect(Net.java:568)
    at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:593)
    at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
    at java.base/java.net.Socket.connect(Socket.java:633)
    at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)

原因分析

核心问题在于DNS解析返回了多个IP地址(比如dns.google通常会同时返回IPv4和IPv6地址),而Apache HttpClient的setConnectTimeout是针对单个IP地址的连接超时时间,并非整体的连接阶段超时。

HttpClient默认会按照DNS返回的IP顺序,依次尝试连接每个地址,每个IP都等待你设置的超时时间。当第一个IP连接失败后,会立刻尝试第二个IP,再等待一次配置的超时时间,两次超时叠加后就出现了“2倍配置值”的总耗时,最后才抛出NoRouteToHostException。

需要注意的是,你禁用的自动重试是针对请求失败后的重试逻辑,和连接阶段的多IP尝试无关。

解决办法

1. 强制使用单一协议(例如IPv4)

自定义连接套接字工厂,限制HttpClient只使用IPv4地址,这样只会尝试一个IP,超时时间就是你配置的数值:

// 创建仅支持IPv4的连接套接字工厂
PlainConnectionSocketFactory ipv4SocketFactory = new PlainConnectionSocketFactory() {
    @Override
    public Socket createSocket(final HttpContext context) throws IOException {
        return new Socket(AddressFamily.INET);
    }
};

Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", ipv4SocketFactory)
        .build();

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

CloseableHttpClient httpClient = HttpClients.custom()
        .disableAutomaticRetries()
        .disableRedirectHandling()
        .setConnectionManager(connManager)
        .setDefaultRequestConfig(requestConfig)
        .build();

2. 自定义DNS解析策略

实现自己的DnsResolver,只返回一个IP地址,让HttpClient仅尝试一次连接:

DnsResolver singleIpResolver = host -> {
    InetAddress[] addresses = InetAddress.getAllByName(host);
    // 仅返回第一个解析到的IP
    return new InetAddress[]{addresses[0]};
};

CloseableHttpClient httpClient = HttpClients.custom()
        .disableAutomaticRetries()
        .disableRedirectHandling()
        .setDefaultRequestConfig(requestConfig)
        .setDnsResolver(singleIpResolver)
        .build();

3. 调整JVM的IPv6优先级

在JVM启动参数中添加-Djava.net.preferIPv4Stack=true,让JVM优先使用IPv4协议,这样DNS解析会优先返回IPv4地址,若网络环境不支持IPv6则只会尝试一个IP。

内容的提问来源于stack exchange,提问作者Dasmowenator

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 22:44:52