本文为博主原创,转载请注明出处:
项目中存在第三方系统之间的服务调用通信,且会进行频繁调用,由于很早之前实现的调用方式为每调用一次外部接口,就需要新建一个HttpClient 对象。由于频繁调用,会存在性能问题。
针对这种场景,进行优化,使用httpClient 连接池,避免重复频繁创建httpClient 造成性能问题。以下为简单实现的demo:
1. 对 httpClient 的属性及常用配置封装 HttpPoolProperties
package com.example.demo.config; import lombok.Data; import org.springframework.stereotype.Component; @Component //@ConfigurationProperties(prefix = "http.pool.conn") // 可在配置文件中进行配置 @Data public class HttpPoolProperties { // 最大连接数 private Integer maxTotal = 20; // 同路由并发数 private Integer defaultMaxPerRoute =20 ; private Integer connectTimeout = 2000; private Integer connectionRequestTimeout=2000; private Integer socketTimeout= 2000; // 线程空闲多久后进行校验 private Integer validateAfterInactivity= 2000; // 重试次数 private Integer retryTimes = 2; // 是否开启充实 private boolean enableRetry = true; // 重试的间隔:可实现 ServiceUnavailableRetryStrategy 接口 private Integer retryInterval= 2000; }
2. 创建httpClient 连接池,并对RestTemplate 指定httpClient 及连接池
package com.example.demo.util; import com.example.demo.config.HttpPoolProperties; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class HttpClientPoolUtils { @Autowired private HttpPoolProperties httpPoolProperties; /** * 首先实例化一个连接池管理器,设置最大连接数、并发连接数 * @return */ @Bean(name = "httpClientConnectionManager") public PoolingHttpClientConnectionManager getHttpClientConnectionManager(){ Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); //最大连接数 httpClientConnectionManager.setMaxTotal(httpPoolProperties.getMaxTotal()); //并发数 httpClientConnectionManager.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute()); httpClientConnectionManager.setValidateAfterInactivity(httpPoolProperties.getValidateAfterInactivity()); return httpClientConnectionManager; } /** * 实例化连接池,设置连接池管理器。 * 这里需要以参数形式注入上面实例化的连接池管理器 * @param httpClientConnectionManager * @return */ @Bean(name = "httpClientBuilder") public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){ //HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象 HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); httpClientBuilder.setConnectionManager(httpClientConnectionManager); if (httpPoolProperties.isEnableRetry()){ // 重试次数 httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpPoolProperties.getRetryTimes(), true)); // 若需要自定义http 的重试策略,可以重新实现ServiceUnavailableRetryStrategy 或 HttpRequestRetryHandler接口,比如对指定异常或制定状态码进行重试,并指定充实的次数。 }else { httpClientBuilder.disableAutomaticRetries(); } // 另外httpClientBuilder 可以设置长连接策略,dns解析器,代理,拦截器以及UserAgent等等。可根据业务需要进行实现 return httpClientBuilder; } /* 注入连接池,用于获取httpClient * @param httpClientBuilder * @return */ @Bean("httpClient") public CloseableHttpClient httpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){ return httpClientBuilder.build(); } /** * Builder是RequestConfig的一个内部类 * 通过RequestConfig的custom方法来获取到一个Builder对象 * 设置builder的连接信息 * 这里还可以设置proxy,cookieSpec等属性。有需要的话可以在此设置 * @return */ @Bean(name = "builder") public RequestConfig.Builder getBuilder(){ RequestConfig.Builder builder = RequestConfig.custom(); return builder.setConnectTimeout(httpPoolProperties.getConnectTimeout()) //连接上服务器(握手成功)的时间,超出抛出connect timeout //从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool .setConnectionRequestTimeout(httpPoolProperties.getConnectionRequestTimeout()) //服务器返回数据(response)的时间,超过抛出read timeout .setSocketTimeout(httpPoolProperties.getSocketTimeout()); } /** * 使用builder构建一个RequestConfig对象 * @param builder * @return */ @Bean public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){ return builder.build(); } /** * RestTemplate 指定httpClient 及连接池 * * @param httpClient * @return */ @Bean(name = "httpClientTemplate") public RestTemplate restTemplate(@Qualifier("httpClient") CloseableHttpClient httpClient) { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setHttpClient(httpClient); RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(factory); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); return restTemplate; } }
3。 创建清理线程对httpClient 空闲线程,失效线程进行清理
package com.example.demo.util; import org.apache.http.conn.HttpClientConnectionManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class IdleConnectionEvictor extends Thread { @Autowired private HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionEvictor() { super(); super.start(); } @Override public void run() { try { while (!shutdown) { synchronized (this) { wait(5000); // 关闭失效的连接 connMgr.closeExpiredConnections(); } } } catch (InterruptedException ex) { // 结束 } } //关闭清理无效连接的线程 public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } }
4. 单元测试
package com.example.demo; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.web.client.RestTemplate; import java.io.IOException; @Slf4j @SpringBootTest public class HttpTest { @Autowired private RestTemplate httpClientTemplate; @Autowired private CloseableHttpClient httpClient; @Test void test() throws IOException {
String result = httpClientTemplate.getForObject("https://www.baidu.com/",String.class); System.out.println("httpClientTemplate==="+result);
// 声明 http get 请求 String url = "https://www.baidu.com/"; HttpGet httpGet = new HttpGet(url); // 发起请求 CloseableHttpResponse response = this.httpClient.execute(httpGet); System.out.println("httpClient==="+response); } }
测试方法分别通过CloseableHttpClient httpClient 进行http调用与自定义的RestTemplate httpClientTemplate 进行 http 调用。
执行结果如下: