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

Apache HttpClient 5.x配置请求超时后仍收到超时时长外响应的问题排查求助

Apache HttpClient 5.x配置请求超时后仍收到超时时长外响应的问题排查求助

我开发了一个服务端应用,实现了队列机制,会故意将入站请求延迟5秒再返回响应。

在客户端侧,我使用的是最新版Apache HttpClient 5.3.1,并且配置了4秒的请求超时。但我观察到一个奇怪的现象:有些请求明明服务端会hold住5秒,客户端却还是在4秒超时后收到了响应,这个问题是随机出现的——有时候请求会正常在4秒时超时,但其他时候客户端依然能在5秒后收到响应,完全没有规律可循。

问题现象

  • 该问题随机发生:部分请求会在4秒时正确超时,但另一些请求会在5秒后收到服务端响应
  • 没有固定的触发规律,无法预测何时超时会生效、何时会收到延迟的响应

客户端代码实现

package com.vendor;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.apache.hc.core5.util.Timeout;

public class AsyncHttpClientBulkRequests {

    public static void main(String[] args) throws InterruptedException {
        // Number of requests
        AtomicLong successCounter = new AtomicLong(0);
        AtomicLong failCounter = new AtomicLong(0);
        AtomicLong cancelCounter = new AtomicLong(0);
        AtomicLong deadlineCounter = new AtomicLong(0);
        
        int totalRequests = 10000;
        
        try {
            IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                    .setSoTimeout(Timeout.ofMilliseconds(4000))
                    .setIoThreadCount(Runtime.getRuntime().availableProcessors()).build();
            
            CloseableHttpAsyncClient asyncclient = null;
            
            HttpAsyncClientBuilder builder = HttpAsyncClients.custom();

            setConnectionProperties(builder, createConnectionmanager());

            asyncclient = builder.setIOReactorConfig(ioReactorConfig).build();

            asyncclient.start();

            ExecutorService executorService = Executors.newFixedThreadPool(10);

            List<Future<SimpleHttpResponse>> futures = new ArrayList<>();

            for (int i = 0; i < totalRequests; i++) {
                int requestId = i; // For tracking
                
                URIBuilder uribuilder = new URIBuilder("http://localhost:18888/dynamicResponseThreadWait");

                URI uri = uribuilder.build();

                HttpPost httpPost = new HttpPost(uri);
                SimpleHttpRequest httpRequest = SimpleRequestBuilder.copy(httpPost).build();
                
                 RequestConfig requestConfig = RequestConfig.custom()
                        .setConnectTimeout(4000, TimeUnit.MILLISECONDS)
                        .setResponseTimeout(4000, TimeUnit.MILLISECONDS).build();
                httpRequest.setConfig(requestConfig);
                
                 String requestBody ="{\r\n"
                        + "    \"threadWaitInMilliSeconds\": \"5000\",\r\n"
                        + "    \"ResponseKey\": \"1999999990\",\r\n"
                        + "    \"ResponseData\": {\r\n"
                        + "        \"1999999990\": "+requestId+"\r\n"
                        + "    }\r\n"
                        + "}";
                
                httpRequest.setBody(requestBody, ContentType.APPLICATION_JSON);
                
                long startTime = System.currentTimeMillis(); // Record start time

                /// Submit each request to the client and add a callback
                futures.add(asyncclient.execute(httpRequest, new FutureCallback<>() {
                    @Override
                    public void completed(SimpleHttpResponse response) {
                        
                         long endTime = System.currentTimeMillis();
                         long responseTimeMs = endTime - startTime;

                        successCounter.incrementAndGet();
                        System.err.println("Request:"+ requestId+ " completed with status: "+response.getCode() + ", TimeDiff:"+responseTimeMs);
                    }

                    @Override
                    public void failed(Exception ex) {
                         long endTime = System.currentTimeMillis();
                         long responseTimeMs = endTime - startTime;
                         failCounter.incrementAndGet();
                         
                        //System.out.println("Request "+requestId+ " failed: " +ex.getMessage()+", responseTimeMs:"+responseTimeMs);
                    }

                    @Override
                    public void cancelled() {
                        cancelCounter.incrementAndGet();
                        System.out.printf("Request %d cancelled%n", requestId);
                    }
                    
                    
                }));
                
            }

         // Wait for all requests to complete
            for (Future<SimpleHttpResponse> future : futures) {
                try {
                    future.get(); // Wait for completion
                } catch (ExecutionException | InterruptedException e) {
                   //System.err.println("Error waiting for request to complete: " + e.getMessage());
                }
            }

            executorService.shutdown();
            executorService.awaitTermination(1, TimeUnit.MINUTES);

            System.out.println("All requests completed! TotalCount : " + totalRequests);
            System.out.println("SuccessCounter: "+ successCounter.get());
            System.out.println("FailCounter: "+ failCounter.get());
            System.out.println("CancelCounter: " + cancelCounter.get());
            System.out.println("DeadlineCounter: " + deadlineCounter.get());
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
   protected static PoolingAsyncClientConnectionManager createConnectionmanager() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
        
        TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] certificate, String authType) {
                return true;
            }
        };

        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        return PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(new DefaultClientTlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE)).build();
    }
   
   protected static void setConnectionProperties(HttpAsyncClientBuilder builder, PoolingAsyncClientConnectionManager connectionManager) {
        
        if (connectionManager != null) {
            connectionManager.setDefaultMaxPerRoute(100);
            connectionManager.setMaxTotal(1000);
            builder.setConnectionManager(connectionManager);
        }
    }
}

预期结果

如果请求处理时间超过4秒,客户端应该触发超时,不会收到任何响应。

实际结果

部分请求会在5秒后收到服务端响应,例如以下日志输出:

Request:109 completed with status: 200, TimeDiff:9145
Request:115 completed with status: 200, TimeDiff:9143
Request:105 completed with status: 200, TimeDiff:9146
Request:116 completed with status: 200, TimeDiff:9143

备注:内容来源于stack exchange,提问作者manish parihar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 10:44:33