RestTemplate参数优化 高并发场景 性能调优

本文详细介绍如何在Spring Boot项目中优化RestTemplate的HTTP连接配置,包括调整连接池大小、超时时间、连接回收策略,以及处理NoHttpResponseException异常,提供具体代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在高并发的HTTP调用场景下,尝试优化 RestTemplate的http连接相关参数,比如超时时间,连接池配置,空闲连接回收等。

项目中也遇到了NoHttpResponseException异常,针对这些情况,尝试着做了优化,分享个完整的示例给大家参考。

项目中每隔几个小时就会出现一些这样的异常:

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://xxx.yyy.com/zzz": http://xxx.yyy.com:80 failed to respond; nested exception is org.apache.http.NoHttpResponseException: http://xxx.yyy.com:80 failed to respond
	at org.springframework.web.client.RestTemplate.doExecute$original$GahflF7J(RestTemplate.java:696)
	at org.springframework.web.client.RestTemplate.doExecute$original$GahflF7J$accessor$F3E1NXY9(RestTemplate.java)
	at org.springframework.web.client.RestTemplate$auxiliary$yfdQHtiq.call(Unknown Source)
	at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java)

项目是springboot的,这里直接贴上RestTemplateConfig类的代码:

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.Header;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.slf4j.LoggerFactory.getLogger;

@Configuration
public class RestTemplateConfig {
    private final Logger logger = getLogger(this.getClass());
    
    //可以在yml或者nacos中配置此值,不配默认为200
    @Value("${restTemplate.pool.max:200}")
    private int poolMax;

    public RestTemplateConfig() {
    }

    /**
     * http连接管理器: 针对单个http connection的池化配置
     */
    @Bean
    public HttpClientConnectionManager poolingHttpClientConnectionManager() {
        /*// 注册http和https请求
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/

        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        // 连接池里面的最大连接数
        poolingHttpClientConnectionManager.setMaxTotal(poolMax);
        // 单个路由的默认最大并发数(即允许同一个host:port最多有几个活跃连接)
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(poolMax);
        // 指定某个路由的最大连接数,示例:
        /*HttpHost localhost = new HttpHost("www.baidu.com", 80);
        poolingHttpClientConnectionManager.setMaxPerRoute(new HttpRoute(localhost), 50);*/

        //在从连接池获取连接时,连接不活跃多长时间后需要进行一次验证,默认为2s
        //poolingHttpClientConnectionManager.setValidateAfterInactivity(2_000);
        //自定义线程,用于关闭池中失效的http连接
        Thread evictThread = new Thread(new IdleConnectionEvictor(poolingHttpClientConnectionManager));
        evictThread.setDaemon(true);
        evictThread.start();

        return poolingHttpClientConnectionManager;
    }

    /**
     * 单个http connection的参数配置
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 设置http连接管理器
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager)
                //httpClient共享模式,这个共享是指与其它httpClient是否共享
                .setConnectionManagerShared(false)
                //回收过期连接
                .evictExpiredConnections()
                //回收空闲连接
                .evictIdleConnections(10, TimeUnit.SECONDS)
                //连接存活时间,如果不设置,则根据长连接信息决定
                .setConnectionTimeToLive(20, TimeUnit.SECONDS)
                //长连接Keep-Alive设置
                .setKeepAliveStrategy(((response, context) -> Duration.ofSeconds(20).toMillis()))
                //禁用重试次数
                .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false));

        // 设置默认请求头
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        httpClientBuilder.setDefaultHeaders(headers);

        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        //JVM停止或重启时,关闭连接池释放掉连接
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    logger.info("ShutdownHook --> closing http client : {}", closeableHttpClient.toString());
                    closeableHttpClient.close();
                    logger.info("ShutdownHook --> closed http client");
                } catch (Exception e) {
                    logger.error("ShutdownHook closing http client error={}", ExceptionUtils.getStackTrace(e));
                }
            }
        });
        return closeableHttpClient;
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        // httpClient创建器
        clientHttpRequestFactory.setHttpClient(httpClient);
        // 连接超时时间/毫秒(连接上服务器(握手成功)的时间,超出抛出connect timeout)
        clientHttpRequestFactory.setConnectTimeout(5 * 100);
        // 数据读取超时时间(socketTimeout)/毫秒(务器返回数据(response)的时间,超过抛出read timeout)
        clientHttpRequestFactory.setReadTimeout(1000);
        // 连接池获取请求连接的超时时间,不宜过长,必须设置/毫秒(超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool)
        clientHttpRequestFactory.setConnectionRequestTimeout(1000);
        return clientHttpRequestFactory;
    }

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        // boot中可使用RestTemplateBuilder.build创建
        RestTemplate restTemplate = new RestTemplate();
        // 配置请求工厂
        restTemplate.setRequestFactory(clientHttpRequestFactory);
        return restTemplate;
    }

    public static class IdleConnectionEvictor extends Thread {
        private final Logger logger = getLogger(this.getClass());

        private final HttpClientConnectionManager connMgr;
        private volatile boolean stop;

        public IdleConnectionEvictor(HttpClientConnectionManager connMgr) {
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!stop) {
                    synchronized (this) {
                        wait(5000);
                        logger.debug("IdleConnectionEvictor --> 每隔5秒执行一次,关闭过期和空闲超过30秒的http连接");
                        //关闭失效链接
                        connMgr.closeExpiredConnections();
                        //关闭空闲链接
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                logger.error("IdleConnectionEvictor thread stop --> catched InterruptedException:{}", ExceptionUtils.getStackTrace(ex));
                shutdown();
            }
        }

        public void shutdown() {
            stop = true;
            synchronized (this) {
                notifyAll();
            }
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值