本文共 10218 字,大约阅读时间需要 34 分钟。
本文基于Spring Boot框架,选择了以下关键依赖:
org.springframework.boot spring-boot-starter org.apache.http httpclient 4.5.12 com.alibaba fastjson 1.2.70 org.springframework.boot spring-boot-starter-test test
com├── example│ ├── httpclientdemo│ │ ├── biz│ │ │ ├── provider│ │ │ └── impl│ │ ├── common│ │ │ └── utils│ │ └── service│ │ ├── impl│ │ └── provider│ └── config│ └── http│ └── client
# HttpClient配置httpClient: retryCount: 3 requestSentRetryEnabled: true pool: maxTotal: 200 defaultMaxPerRoute: 50 validateAfterInactivity: 1000 idleTimeOut: 3 socketCfg: tcpNoDelay: true soReuseAddress: true soTimeOut: 500 soLinger: 60 soKeepAlive: true
package com.example.httpclientdemo.common.config.http.client;import org.apache.http.config.Registry;import org.apache.http.config.RegistryBuilder;import org.apache.http.config.SocketConfig;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.PoolingHttpClientConnectionManager;import org.apache.http.ssl.SSLContexts;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configurationpublic class HttpClientConfig { @Bean public CloseableHttpClient closeableHttpClient(PoolingHttpClientConnectionManager httpClientPoolManager, @Value("${httpClient.retryCount}") int retryCount, @Value("${httpClient.requestSentRetryEnabled}") boolean requestSentRetryEnabled) { return HttpClients.custom() .setDefaultCookieStore(new BasicCookieStore()) .setDefaultCredentialsProvider(new BasicCredentialsProvider()) .setConnectionManager(httpClientPoolManager) .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, requestSentRetryEnabled)) .build(); } @Bean public SocketConfig defaultSocketConfig( @Value("${httpClient.pool.socketCfg.tcpNoDelay}") boolean tcpNoDelay, @Value("${httpClient.pool.socketCfg.soReuseAddress}") boolean soReuseAddress, @Value("${httpClient.pool.socketCfg.soTimeOut}") int soTimeOut, @Value("${httpClient.pool.socketCfg.soLinger}") int soLinger, @Value("${httpClient.pool.socketCfg.soKeepAlive}") boolean soKeepAlive) { return SocketConfig.custom() .setTcpNoDelay(tcpNoDelay) .setSoReuseAddress(soReuseAddress) .setSoTimeout(soTimeOut) .setSoLinger(soLinger) .setSoKeepAlive(soKeepAlive) .build(); } @Bean public PoolingHttpClientConnectionManager httpClientPoolManager( @Value("${httpClient.pool.maxTotal}") int maxTotal, @Value("${httpClient.pool.defaultMaxPerRoute}") int defaultMaxPerRoute, @Value("${httpClient.pool.validateAfterInactivity}") int validateAfterInactivity, @Value("${httpClient.pool.idleTimeOut}") long idleTimeOut, SocketConfig defaultSocketConfig) { Registry registry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", new SSLConnectionSocketFactory(SSLContexts.createSystemDefault())) .build(); PoolingHttpClientConnectionManager poolManager = new PoolingHttpClientConnectionManager(registry); poolManager.setMaxTotal(maxTotal); poolManager.setDefaultMaxPerRoute(defaultMaxPerRoute); poolManager.setValidateAfterInactivity(validateAfterInactivity); poolManager.closeIdleConnections(idleTimeOut, TimeUnit.SECONDS); poolManager.setDefaultSocketConfig(defaultSocketConfig); return poolManager; }} package com.example.httpclientdemo.biz.service;public interface HttpClientService { void requestGet(String url); void requestPost(String url, Object obj);} package com.example.httpclientdemo.biz.service.impl;import com.example.httpclientdemo.biz.provider.HttpClientProvider;import com.example.httpclientdemo.biz.service.HttpClientService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class HttpClientServiceImpl implements HttpClientService { @Autowired private HttpClientProvider httpClientProvider; @Override public void requestGet(String url) { httpClientProvider.requestGet(url); } @Override public void requestPost(String url, Object obj) { httpClientProvider.requestPost(url, obj); }} package com.example.httpclientdemo.biz.provider;public interface HttpClientProvider { void requestGet(String url); void requestPost(String url, Object obj);} package com.example.httpclientdemo.biz.provider.impl;import com.alibaba.fastjson.JSON;import com.example.httpclientdemo.biz.provider.HttpClientProvider;import com.example.httpclientdemo.common.utils.http.client.HttpClientUtils;import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HTTPPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.io.IOException;import java.io.InputStream;import java.nio.charset.StandardCharsets;@Servicepublic class HttpClientProviderImpl implements HttpClientProvider { private final Logger logger = LoggerFactory.getLogger(HttpClientProviderImpl.class); @Resource(name = "closeableHttpClient") private CloseableHttpClient httpClient; @Override public void requestGet(String url) { HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response = null; InputStream inputStream = null; try { response = HttpClientUtils.get(httpClient, httpGet); HttpEntity httpEntity = response.getEntity(); inputStream = HttpClientUtils.getContent(httpEntity); String respString = HttpClientUtils.getContentString(inputStream); logger.debug("请求地址: {}, 响应内容: {}", url, respString); } catch (Exception e) { e.fillInStackTrace(); } finally { try { HttpClientUtils.close(response, inputStream); } catch (IOException ioException) { ioException.printStackTrace(); } } } @Override public void requestPost(String url, Object obj) { HTTPPost httpPost = new HTTPPost(url); String jsonString = JSON.toJSONString(obj); httpPost.setEntity(new StringEntity(jsonString, StandardCharsets.UTF_8)); CloseableHttpResponse response = null; InputStream inputStream = null; try { response = HttpClientUtils.post(httpClient, httpPost); HttpEntity httpEntity = response.getEntity(); inputStream = HttpClientUtils.getContent(httpEntity); String respString = HttpClientUtils.getContentString(inputStream); logger.debug("请求地址: {}, 请求参数: {}, 响应内容: {}", url, jsonString, respString); } catch (Exception e) { e.fillInStackTrace(); } finally { try { HttpClientUtils.close(response, inputStream); } catch (IOException ioException) { ioException.printStackTrace(); } } }} package com.example.httpclientdemo.biz.service;import org.junit.jupiter.api.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.atomic.AtomicInteger;import static org.junit.jupiter.api.Assertions.*;@SpringBootTestpublic class HttpClientServiceTest { private final static Logger logger = LoggerFactory.getLogger(HttpClientServiceTest.class); @Autowired private HttpClientService httpClientService; private static final String URL_GET_PATH = "https://www.baidu.com"; private final AtomicInteger count = new AtomicInteger(); private static final ExecutorService workStealingPool = Executors.newWorkStealingPool(1 << 6); @Test void requestGet() throws InterruptedException { for (int i = 0; i < 1000; i++) { workStealingPool.execute(() -> { httpClientService.requestGet(URL_GET_PATH); logger.warn("requestGet, times: {}, betMils: {}, betNanos: {}", count.getAndIncrement(), System.currentTimeMillis() - startMils, System.nanoTime() - statNanos); }); } Thread.sleep(20000); } @Test void requestPost() { // 后续可根据实际需求添加测试用例 }} 通过对比不同配置参数下的性能表现,可以看出以下结论:
| 参数 | 50 线程 | 200 线程 |
|---|---|---|
| defaultMaxPerRoute | 50 | 50 |
| validateAfterInactivity | 1000 | 1000 |
| betMils | 5270 | 5245 |
| betNanos | 5269994599 | 5269405201 |
通过对比可以发现,在不同线程数下,响应时间的波动范围较小,主要受网络条件的影响较大。
本文是关于HttpClient客户端配置的学习与实践,通过实际项目的配置和优化,总结了高可用、高并发下的HttpClient配置方案。未来将持续更新本博客内容,并欢迎技术爱好者交流与讨论。
转载地址:http://kywyz.baihongyu.com/