本文共 34974 字,大约阅读时间需要 116 分钟。
本文是HttpClient的学习博客,RestTemplate是基于HttpClient的封装,feign可基于HttpClient进行网络通信。
那么作为较底层的客户端网络编程框架,该怎么配置使其能高可用,高并发,可支持Https协议呢?通读本文也许你会有答案或者启发。
本文是Maven项目,基于Spring,在本Demo中使用了更方便的SpringBoot。
以后随着理解HttpClient更深入的时候会不定期更新本博客,也欢迎感兴趣的博友交流和讨论。
12 5 6 7org.springframework.boot 3spring-boot-starter 48 12 13 14org.apache.httpcomponents 9httpclient 104.5.12 1115 19 20com.alibaba 16fastjson 171.2.70 1821 org.springframework.boot 22spring-boot-starter-test 23test 24
1 #HttpClient配置 2 httpClient: 3 #重试次数 4 retryCount: 3 5 #重启开关 6 requestSentRetryEnabled: true 7 #HttpClient连接池配置 8 pool: 9 #总连接数10 maxTotal: 20011 #每个路由默认连接数,某一个/每服务每次能并行接收的请求数量12 defaultMaxPerRoute: 5013 #Validate connections after 15 sec of inactivity 1500014 validateAfterInactivity: 100015 #idle超时时间16 idleTimeOut: 317 socketCfg:18 #是否立即发送数据,设置为true会关闭Socket缓冲,默认为false19 tcpNoDelay: true20 #是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口21 soReuseAddress: true22 #接受数据的等待超时时间,单位ms23 soTimeOut: 50024 #关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的25 soLinger: 6026 #开启监视TCP连接是否有效27 soKeepAlive: true
通过配置连接池管理对象PoolingHttpClientConnectionManager,设置两个重要参数maxTotal和defaultMaxPerRoute,和其它参数。本文参数配置参考,文档上面的参数更多更齐全,包括HttpConnectionFactory、DnsResolver、ConnectionConfig、RequestConfig、RequestConfig、HttpClientContext、设置代理。这些参数本文没有配置,使用HttpClient的默认配置,感兴趣想继续深入研究的可以去学习了解。BackoffManager可以在连接池处于闲暇时进行收缩,不过网络上资料较少,目前还没研究出怎么使用和配置。
1 package com.example.httpclientdemo.common.config.http.client; 2 3 import org.apache.http.config.Registry; 4 import org.apache.http.config.RegistryBuilder; 5 import org.apache.http.config.SocketConfig; 6 import org.apache.http.conn.socket.ConnectionSocketFactory; 7 import org.apache.http.conn.socket.PlainConnectionSocketFactory; 8 import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 9 import org.apache.http.impl.client.*; 10 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 11 import org.apache.http.ssl.SSLContexts; 12 import org.springframework.beans.factory.annotation.Value; 13 import org.springframework.context.annotation.Bean; 14 import org.springframework.context.annotation.Configuration; 15 16 import java.util.concurrent.TimeUnit; 17 18 /** 19 * HttpClient客户端配置类 20 * 21 * @author 复姓江山 22 * @date 2021/02/08 23 */ 24 @Configuration 25 public class HttpClientConfig { 26 27 /** 28 * closeableHttpClient连接对象,支持HTTPS使用SSL套接层 29 * 30 * @param httpClientPoolManager HttpClient连接池管理对象 31 * @param retryCount 重试次数 32 * @param requestSentRetryEnabled 重启开关 33 * @return closeableHttpClient连接对象 34 */ 35 @Bean 36 public CloseableHttpClient closeableHttpClient(final PoolingHttpClientConnectionManager httpClientPoolManager, 37 @Value("${httpClient.retryCount}") final int retryCount, 38 @Value("${httpClient.requestSentRetryEnabled}") final 39 boolean requestSentRetryEnabled) { 40 41 return HttpClients.custom() 42 .setDefaultCookieStore(new BasicCookieStore()) 43 .setDefaultCredentialsProvider(new BasicCredentialsProvider()) 44 .setConnectionManager(httpClientPoolManager) 45 .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, requestSentRetryEnabled)) 46 .build(); 47 } 48 49 /** 50 * 默认socket configuration 51 * 52 * @param tcpNoDelay 是否立即发送数据,设置为true会关闭Socket缓冲,默认为false 53 * @param soReuseAddress 是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口 54 * @param soTimeOut 接受数据的等待超时时间,单位ms 55 * @param soLinger 关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的 56 * @param soKeepAlive 开启监视TCP连接是否有效 57 * @return 默认socket configuration 58 */ 59 @Bean 60 public SocketConfig defaultSocketConfig(@Value("${httpClient.pool.socketCfg.tcpNoDelay}") final boolean tcpNoDelay, 61 @Value("${httpClient.pool.socketCfg.soReuseAddress}") final boolean soReuseAddress, 62 @Value("${httpClient.pool.socketCfg.soTimeOut}") final int soTimeOut, 63 @Value("${httpClient.pool.socketCfg.soLinger}") final int soLinger, 64 @Value("${httpClient.pool.socketCfg.soKeepAlive}") final boolean soKeepAlive) { 65 return SocketConfig.custom() 66 .setTcpNoDelay(tcpNoDelay) 67 .setSoReuseAddress(soReuseAddress) 68 .setSoTimeout(soTimeOut) 69 .setSoLinger(soLinger) 70 .setSoKeepAlive(soKeepAlive).build(); 71 } 72 73 /** 74 * HttpClient连接池管理对象 75 * 76 * @param maxTotal 总连接数 77 * @param defaultMaxPerRoute 每个路由默认连接数,某一个/每服务每次能并行接收的请求数量 78 * @param validateAfterInactivity 一次连接保留时长,单位s 79 * @param idleTimeOut idle超时时间 80 * @param defaultSocketConfig 默认socket configuration 81 * @return HttpClient连接池管理对象 82 */ 83 @Bean 84 public PoolingHttpClientConnectionManager httpClientPoolManager( 85 @Value("${httpClient.pool.maxTotal}") final int maxTotal, 86 @Value("${httpClient.pool.defaultMaxPerRoute}") final int defaultMaxPerRoute, 87 @Value("${httpClient.pool.validateAfterInactivity}") final int validateAfterInactivity, 88 @Value("${httpClient.pool.validateAfterInactivity}") final long idleTimeOut, 89 final SocketConfig defaultSocketConfig) { 90 RegistrysocketFactoryRegistry = RegistryBuilder. create() 91 .register("http", PlainConnectionSocketFactory.INSTANCE) 92 .register("https", new SSLConnectionSocketFactory(SSLContexts.createSystemDefault())) 93 .build(); 94 PoolingHttpClientConnectionManager poolManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); 95 poolManager.setMaxTotal(maxTotal); 96 poolManager.setDefaultMaxPerRoute(defaultMaxPerRoute); 97 poolManager.setValidateAfterInactivity(validateAfterInactivity); 98 poolManager.closeIdleConnections(idleTimeOut, TimeUnit.SECONDS); 99 poolManager.setDefaultSocketConfig(defaultSocketConfig);100 return poolManager;101 }102 103 }
1 package com.example.httpclientdemo.common.utils.http.client; 2 3 import org.apache.http.HttpEntity; 4 import org.apache.http.client.methods.*; 5 import org.apache.http.impl.client.CloseableHttpClient; 6 import org.apache.http.impl.execchain.RequestAbortedException; 7 8 import java.io.*; 9 import java.util.stream.Collectors; 10 11 /** 12 * HttpClient工具类 13 * 14 * @author 复姓江山 15 * @date 2021/02/08 16 */ 17 public final class HttpClientUtils { 18 19 /** 20 * header的Content-Type键 21 */ 22 public final static String HEADER_CONTENT_TYPE = "Content-Type"; 23 24 /** 25 * R3C默认Content_Type 26 */ 27 public final static String R3C_DEFAULT_CONTENT_TYPE = "application/vnd.api+json"; 28 29 /** 30 * Authorization 31 */ 32 public final static String HEADER_AUTHORIZATION = "Authorization"; 33 34 private HttpClientUtils() { 35 36 } 37 38 /** 39 * httpClient的响应实体 40 * 41 * @param httpClient httpClient对象 42 * @param request 请求对象 43 * @return 响应实体 44 * @throws IOException IO异常 45 */ 46 private static CloseableHttpResponse httpResponse(CloseableHttpClient httpClient, 47 HttpUriRequest request) throws IOException { 48 return httpClient.execute(request); 49 } 50 51 /** 52 * get请求 53 * 54 * @param httpClient httpClient对象 55 * @param request 请求对象 56 * @return 响应实体 57 * @throws IOException IO异常 58 */ 59 public static CloseableHttpResponse get(CloseableHttpClient httpClient, HttpUriRequest request) throws IOException { 60 assert HttpGet.METHOD_NAME.equals(request.getMethod()); 61 return HttpClientUtils.httpResponse(httpClient, request); 62 } 63 64 /** 65 * post请求 66 * 67 * @param httpClient httpClient对象 68 * @param request 请求对象 69 * @return 响应实体 70 * @throws IOException IO异常 71 */ 72 public static CloseableHttpResponse post(CloseableHttpClient httpClient, HttpUriRequest request) throws IOException { 73 assert HttpPost.METHOD_NAME.equals(request.getMethod()); 74 return HttpClientUtils.httpResponse(httpClient, request); 75 } 76 77 /** 78 * post或patch请求 79 * 80 * @param httpClient httpClient对象 81 * @param request 请求对象 82 * @return 响应实体 83 * @throws IOException IO异常 84 */ 85 public static CloseableHttpResponse postOrPatch(CloseableHttpClient httpClient, HttpUriRequest request) throws IOException { 86 if (request instanceof HttpPost) { 87 assert HttpPost.METHOD_NAME.equals(request.getMethod()); 88 } else if (request instanceof HttpPatch) { 89 assert HttpPatch.METHOD_NAME.equals(request.getMethod()); 90 } else { 91 throw new RequestAbortedException("Not post or patch."); 92 } 93 return HttpClientUtils.httpResponse(httpClient, request); 94 } 95 96 /** 97 * patch请求 98 * 99 * @param httpClient httpClient对象100 * @param request 请求对象101 * @return 响应实体102 * @throws IOException IO异常103 */104 public static CloseableHttpResponse patch(CloseableHttpClient httpClient, HttpUriRequest request) throws IOException {105 assert HttpPatch.METHOD_NAME.equals(request.getMethod());106 return HttpClientUtils.httpResponse(httpClient, request);107 }108 109 /**110 * httpClient的HttpEntity111 *112 * @param response 响应实体113 * @return HttpEntity114 */115 public static HttpEntity httpEntity(final CloseableHttpResponse response) {116 return response.getEntity();117 }118 119 120 /**121 * 返回状态行代码122 *123 * @param response 响应实体124 * @return 状态行代码125 */126 public static int getStatusCode(final CloseableHttpResponse response) {127 return response.getStatusLine().getStatusCode();128 }129 130 /**131 * 获取ContentType132 *133 * @param httpEntity HttpEntity134 * @return ContentType135 */136 public static String getContentType(final HttpEntity httpEntity) {137 return httpEntity.getContentType().getValue();138 }139 140 /**141 * 获取ContentEncoding142 *143 * @param httpEntity HttpEntity144 * @return ContentEncoding145 */146 public static String getContentEncoding(final HttpEntity httpEntity) {147 return httpEntity.getContentEncoding().getValue();148 }149 150 /**151 * 获取响应体对象InputStream152 *153 * @param httpEntity HttpEntity154 * @return 获取响应体对象InputStream155 * @throws IOException IO异常156 */157 public static InputStream getContent(final HttpEntity httpEntity) throws IOException {158 return httpEntity.getContent();159 }160 161 /**162 * 获取响应体对象的字符串163 *164 * @param inputStream InputStream165 * @return 获取响应体对象InputStream166 * @throws IOException IO异常167 */168 public static String getContentString(final InputStream inputStream) throws IOException {169 return new BufferedReader(new InputStreamReader(inputStream))170 .lines().parallel().collect(Collectors.joining(System.lineSeparator()));171 }172 173 /**174 * 获取响应体对象的字符串175 *176 * @param httpEntity HttpEntity177 * @return 获取响应体对象InputStream178 * @throws IOException IO异常179 */180 public static String getContentString(final HttpEntity httpEntity) throws IOException {181 return new BufferedReader(new InputStreamReader(HttpClientUtils.getContent(httpEntity)))182 .lines().parallel().collect(Collectors.joining(System.lineSeparator()));183 }184 185 /**186 * 字符串转InputStream187 *188 * @param str 字符串189 * @return inputStream190 */191 public static InputStream stringTransferToInputStream(String str) {192 return new ByteArrayInputStream(str.getBytes());193 }194 195 /**196 * 设置默认的Content-Type197 *198 * @param request http请求对象199 */200 public static void setDefaultContentType(HttpUriRequest request) {201 request.setHeader(HttpClientUtils.HEADER_CONTENT_TYPE, HttpClientUtils.R3C_DEFAULT_CONTENT_TYPE);202 }203 204 /**205 * 设置Authorization206 *207 * @param request http请求对象208 * @param token token209 */210 public static void setAuthorization(HttpUriRequest request, String token) {211 request.setHeader(HttpClientUtils.HEADER_AUTHORIZATION, token);212 }213 214 /**215 * 设置默认请求头216 *217 * @param request http请求对象218 * @param token token219 */220 public static void setDefaultHeader(HttpUriRequest request, String token) {221 HttpClientUtils.setDefaultContentType(request);222 HttpClientUtils.setAuthorization(request, token);223 }224 225 /**226 * 关闭连接227 *228 * @param response response响应对象229 * @param inputStream inputStream230 */231 public static void close(CloseableHttpResponse response, InputStream inputStream) throws IOException {232 if (inputStream != null) {233 inputStream.close();234 }235 if (response != null) {236 response.close();237 }238 }239 240 241 }
1 package com.example.httpclientdemo.biz.service; 2 3 /** 4 * HttpClient业务接口类 5 * 6 * @author 复姓江山 7 * @date 2021/02/08 8 */ 9 public interface HttpClientService {10 /**11 * get请求12 *13 * @param url 请求地址14 */15 void requestGet(String url);16 17 /**18 * post请求19 *20 * @param url 请求地址21 * @param obj 请求对象22 */23 void requestPost(String url, Object obj);24 }
1 package com.example.httpclientdemo.biz.service.impl; 2 3 import com.example.httpclientdemo.biz.provider.HttpClientProvider; 4 import com.example.httpclientdemo.biz.service.HttpClientService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 8 /** 9 * HttpClient业务接口实现类10 *11 * @author 复姓江山12 * @date 2021/02/0813 */14 @Service15 public class HttpClientServiceImpl implements HttpClientService {16 17 @Autowired18 private HttpClientProvider httpClientProvider;19 20 21 @Override22 public void requestGet(String url) {23 httpClientProvider.requestGet(url);24 }25 26 @Override27 public void requestPost(String url, Object obj) {28 httpClientProvider.requestPost(url, obj);29 }30 }
为什么会加个业务支撑接口呢?常见的分层结构或分层架构(控制层、业务层和数据访问层——分层结构,或表示层(UI)、业务逻辑层(BLL)和数据访问层——分层架构),在此基础上增加provder是因为多层封装与隔离,HttpClient是作为客户端请求其它系统服务,请求参数与相应参数及异常处理应与本项目进行一定隔离,如果把HttpClient客户端深度耦合到业务层中,到对方服务器变动时,就会影响本系统的核心业务逻辑。而增加了封装与隔离后,影响的只是provider层,系统本身的核心业务逻辑不会受到深层次的影响。在这我就稍微发散一下思维,比如定义一个MQ的接口,在使用的是Kafka,RabitMQ,ActiveMQ或者RocketMQ,不管怎么变更MQ,系统业务代码依赖的是MQ的接口,丝毫不会受到影响。所以编程原则里有一条:面向接口编程,面向抽象编程。
1 package com.example.httpclientdemo.biz.provider; 2 3 /** 4 * HttpClient业务支撑接口类 5 * 6 * @author 复姓江山 7 * @date 2021/02/08 8 */ 9 public interface HttpClientProvider {10 11 /**12 * get请求13 *14 * @param url 请求地址15 */16 void requestGet(String url);17 18 /**19 * post请求20 *21 * @param url 请求地址22 * @param obj 请求参数23 */24 void requestPost(String url, Object obj);25 }
1 package com.example.httpclientdemo.biz.provider.impl; 2 3 import com.alibaba.fastjson.JSON; 4 import com.example.httpclientdemo.biz.provider.HttpClientProvider; 5 import com.example.httpclientdemo.common.utils.http.client.HttpClientUtils; 6 import org.apache.http.HttpEntity; 7 import org.apache.http.client.methods.CloseableHttpResponse; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.client.methods.HttpPost;10 import org.apache.http.entity.StringEntity;11 import org.apache.http.impl.client.CloseableHttpClient;12 import org.slf4j.Logger;13 import org.slf4j.LoggerFactory;14 import org.springframework.stereotype.Service;15 16 import javax.annotation.Resource;17 import java.io.IOException;18 import java.io.InputStream;19 import java.nio.charset.StandardCharsets;20 21 /**22 * HttpClient业务支撑接口实现类23 *24 * @author 复姓江山25 * @date 2021/02/0826 */27 @Service28 public class HttpClientProviderImpl implements HttpClientProvider {29 private final Logger logger = LoggerFactory.getLogger(this.getClass());30 31 @Resource(name = "closeableHttpClient")32 private CloseableHttpClient httpClient;33 34 35 @Override36 public void requestGet(String url) {37 HttpGet httpGet = new HttpGet(url);38 CloseableHttpResponse response = null;39 InputStream inputStream = null;40 41 try {42 response = HttpClientUtils.get(httpClient, httpGet);43 HttpEntity httpEntity = response.getEntity();44 inputStream = HttpClientUtils.getContent(httpEntity);45 String respString = HttpClientUtils.getContentString(inputStream);46 logger.debug("respString: {}", respString);47 } catch (Exception e) {48 e.fillInStackTrace();49 } finally {50 try {51 HttpClientUtils.close(response, inputStream);52 } catch (IOException ioException) {53 ioException.printStackTrace();54 }55 }56 }57 58 @Override59 public void requestPost(String url, Object obj) {60 HttpPost httpPost = new HttpPost(url);61 String jsonString = JSON.toJSONString(obj);62 httpPost.setEntity(new StringEntity(jsonString, StandardCharsets.UTF_8));63 CloseableHttpResponse response = null;64 InputStream inputStream = null;65 try {66 response = HttpClientUtils.post(httpClient, httpPost);67 HttpEntity httpEntity = response.getEntity();68 inputStream = HttpClientUtils.getContent(httpEntity);69 String respString = HttpClientUtils.getContentString(inputStream);70 logger.debug("respString: {}", respString);71 } catch (Exception e) {72 e.fillInStackTrace();73 } finally {74 try {75 HttpClientUtils.close(response, inputStream);76 } catch (IOException ioException) {77 ioException.printStackTrace();78 }79 }80 }81 }
通过运行单元测试类来测试接口,下文有详细的测试数据。
1 package com.example.httpclientdemo.biz.service; 2 3 import org.junit.jupiter.api.Test; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.boot.test.context.SpringBootTest; 8 9 import java.util.concurrent.ExecutorService;10 import java.util.concurrent.Executors;11 import java.util.concurrent.atomic.AtomicInteger;12 13 import static org.junit.jupiter.api.Assertions.*;14 15 @SpringBootTest16 class HttpClientServiceTest {17 private final static Logger logger = LoggerFactory.getLogger(HttpClientServiceTest.class);18 19 @Autowired20 private HttpClientService httpClientService;21 22 private static final String URL_GET_PATH = "https://www.baidu.com";23 24 private final AtomicInteger count = new AtomicInteger();25 26 /**27 * workStealingPool28 */29 public final static ExecutorService workStealingPool = Executors.newWorkStealingPool(1<<6);30 31 @Test32 void requestGet() throws InterruptedException {33 final long startMils = System.currentTimeMillis();34 final long statNanos = System.nanoTime();35 for (int i = 0; i < 1000; i++) {36 workStealingPool.execute(() -> {37 httpClientService.requestGet(URL_GET_PATH);38 logger.warn("requestGet, times: {}, betMils: {},betNanos: {}", count.getAndIncrement(),39 (System.currentTimeMillis() - startMils), (System.nanoTime() - statNanos));40 });41 42 }43 Thread.sleep(20000);44 }45 46 @Test47 void requestPost() {48 }49 }
本文测试方式可能不是那么专业与严谨,可是并不妨碍我们通过测试数据看出些原理,总结出些规律,也许有些片面,但可看出些趋势。
1 main 2021-02-09 14:09:34,963 DEBUG (PoolingHttpClientConnectionManager.java:267)- Connection request: [route: {s}->https://www.baidu.com:443][total available: 0; route allocated: 0 of 50; total allocated: 0 of 200] 2 main 2021-02-09 14:09:34,978 DEBUG (PoolingHttpClientConnectionManager.java:312)- Connection leased: [id: 0][route: {s}->https://www.baidu.com:443][total available: 0; route allocated: 1 of 50; total allocated: 1 of 200] 3 main 2021-02-09 14:09:34,980 DEBUG (MainClientExec.java:234)- Opening connection {s}->https://www.baidu.com:443 4 main 2021-02-09 14:09:34,994 DEBUG (DefaultHttpClientConnectionOperator.java:139)- Connecting to www.baidu.com/14.215.177.38:443 5 main 2021-02-09 14:09:34,994 DEBUG (SSLConnectionSocketFactory.java:366)- Connecting socket to www.baidu.com/14.215.177.38:443 with timeout 0 6 main 2021-02-09 14:09:35,101 DEBUG (SSLConnectionSocketFactory.java:430)- Enabled protocols: [TLSv1, TLSv1.1, TLSv1.2] 7 main 2021-02-09 14:09:35,101 DEBUG (SSLConnectionSocketFactory.java:431)- Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] 8 main 2021-02-09 14:09:35,102 DEBUG (SSLConnectionSocketFactory.java:435)- Starting handshake 9 main 2021-02-09 14:09:35,250 DEBUG (SSLConnectionSocketFactory.java:465)- Secure session established10 main 2021-02-09 14:09:35,250 DEBUG (SSLConnectionSocketFactory.java:466)- negotiated protocol: TLSv1.211 main 2021-02-09 14:09:35,250 DEBUG (SSLConnectionSocketFactory.java:467)- negotiated cipher suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA25612 main 2021-02-09 14:09:35,250 DEBUG (SSLConnectionSocketFactory.java:475)- peer principal: CN=baidu.com, O="Beijing Baidu Netcom Science Technology Co., Ltd", OU=service operation department, L=beijing, ST=beijing, C=CN13 main 2021-02-09 14:09:35,251 DEBUG (SSLConnectionSocketFactory.java:484)- peer alternative names: [baidu.com, baifubao.com, www.baidu.cn, www.baidu.com.cn, mct.y.nuomi.com, apollo.auto, dwz.cn, *.baidu.com, *.baifubao.com, *.baidustatic.com, *.bdstatic.com, *.bdimg.com, *.hao123.com, *.nuomi.com, *.chuanke.com, *.trustgo.com, *.bce.baidu.com, *.eyun.baidu.com, *.map.baidu.com, *.mbd.baidu.com, *.fanyi.baidu.com, *.baidubce.com, *.mipcdn.com, *.news.baidu.com, *.baidupcs.com, *.aipage.com, *.aipage.cn, *.bcehost.com, *.safe.baidu.com, *.im.baidu.com, *.baiducontent.com, *.dlnel.com, *.dlnel.org, *.dueros.baidu.com, *.su.baidu.com, *.91.com, *.hao123.baidu.com, *.apollo.auto, *.xueshu.baidu.com, *.bj.baidubce.com, *.gz.baidubce.com, *.smartapps.cn, *.bdtjrcv.com, *.hao222.com, *.haokan.com, *.pae.baidu.com, *.vd.bdstatic.com, click.hm.baidu.com, log.hm.baidu.com, cm.pos.baidu.com, wn.pos.baidu.com, update.pan.baidu.com]14 main 2021-02-09 14:09:35,251 DEBUG (SSLConnectionSocketFactory.java:488)- issuer principal: CN=GlobalSign Organization Validation CA - SHA256 - G2, O=GlobalSign nv-sa, C=BE15 main 2021-02-09 14:09:35,254 DEBUG (DefaultHttpClientConnectionOperator.java:146)- Connection established 192.168.100.24:63820<->14.215.177.38:44316 main 2021-02-09 14:09:35,255 DEBUG (MainClientExec.java:255)- Executing request GET / HTTP/1.117 main 2021-02-09 14:09:35,255 DEBUG (MainClientExec.java:260)- Target auth state: UNCHALLENGED18 main 2021-02-09 14:09:35,255 DEBUG (MainClientExec.java:266)- Proxy auth state: UNCHALLENGED19 main 2021-02-09 14:09:35,257 DEBUG (LoggingManagedHttpClientConnection.java:133)- http-outgoing-0 >> GET / HTTP/1.120 main 2021-02-09 14:09:35,257 DEBUG (LoggingManagedHttpClientConnection.java:136)- http-outgoing-0 >> Host: www.baidu.com21 main 2021-02-09 14:09:35,257 DEBUG (LoggingManagedHttpClientConnection.java:136)- http-outgoing-0 >> Connection: Keep-Alive22 main 2021-02-09 14:09:35,257 DEBUG (LoggingManagedHttpClientConnection.java:136)- http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.12 (Java/1.8.0_161)23 main 2021-02-09 14:09:35,257 DEBUG (LoggingManagedHttpClientConnection.java:136)- http-outgoing-0 >> Accept-Encoding: gzip,deflate24 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "GET / HTTP/1.1[\r][\n]"25 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "Host: www.baidu.com[\r][\n]"26 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"27 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.12 (Java/1.8.0_161)[\r][\n]"28 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"29 main 2021-02-09 14:09:35,258 DEBUG (Wire.java:73)- http-outgoing-0 >> "[\r][\n]"30 main 2021-02-09 14:09:35,300 DEBUG (Wire.java:73)- http-outgoing-0 << "HTTP/1.1 200 OK[\r][\n]"31 main 2021-02-09 14:09:35,301 DEBUG (Wire.java:73)- http-outgoing-0 << "Content-Encoding: gzip[\r][\n]"32 main 2021-02-09 14:09:35,301 DEBUG (Wire.java:73)- http-outgoing-0 << "Content-Length: 1145[\r][\n]"33 main 2021-02-09 14:09:35,301 DEBUG (Wire.java:73)- http-outgoing-0 << "Content-Type: text/html[\r][\n]"34 main 2021-02-09 14:09:35,301 DEBUG (Wire.java:73)- http-outgoing-0 << "Server: bfe[\r][\n]"35 main 2021-02-09 14:09:35,301 DEBUG (Wire.java:73)- http-outgoing-0 << "Date: Tue, 09 Feb 2021 06:09:34 GMT[\r][\n]"36 main 2021-02-09 14:09:35,302 DEBUG (Wire.java:73)- http-outgoing-0 << "[\r][\n]"37 main 2021-02-09 14:09:35,302 DEBUG (Wire.java:73)- http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0][0x0][0xff][0x94]V[[0x8f][0xd4][0xb6][0x17]G[0xe2];[0x98][0xfc][0xb5][0xbb] 4[0xe3][0xb9][0x8][0xc1]7[0x9][0xda]nABH[0x5][0x15]V*O#[0xc7]v[0x12][0xb3][0x89]mlg[0xc2][0xf0][0xd4][0x95]J[0xd5][0xaa][0xa5][0xb4][0xa2][0x17]Q*[0xb5][0xaa]Z[0xb6][0xf][0x95][0xa0]*R[0xd1]R[0xe0][0xcb]L[0xd8][0xdd][0xa7]~[0x85][0xca]If[0xe7][\n]"38 main 2021-02-09 14:09:35,302 DEBUG (Wire.java:73)- http-outgoing-0 << "[0xa8][0xf3][0x12][0xfb][0xf8][0x9c][0xdf][0xf9][0x9d][0x9b]=[0xee][0xb1]w/m\[0xbd]v[0xf9][0x1c][0x88]M[0x9a][0xf8]G[0x8f][0xb8][0xc7][0x1a][0x8d]+W[0xd7][0xaf]n^[0x1][0x97].6[0x1a][0xbe][[0xca][0x81][0x1b]SD|7[0xa5][0x6][0x81][0xd8][0x18][0xd9][0xa0]72[0xd6][0xf7][0xb0][0xe0][0x86]r[0xd3]0[0x3]IA[0xbd][0xf1][0xc][0xbd]i[0xa0]5[[0xc3]1R[0x9a][0x1a]/3a[0xe3][0xcc][0xbc][0xf5][0x7][0x8d][0xcd][0xf5][0xc6][0x86]H%2,H[0xc6][0x0][0x17][0xce]y[0xe7]HDk[0x83][0x91][0x14]%9[0x1a]h[0xc0]QJ=EC[0xaa][0x14]U[0xbe][0x9b]0[0xbe][0x5][0x14]M[0xfd][0xec][0x9f][0xbf]?[0x1f]>[0xff][0xa9]x[0xfc][0xc7][0xde][0x8f][0xbf][0x1e]l[0xdf]sa[0xa5][0xe2][0xc2]2q[0xc0][\r][0x4][0x19][0x0]K[0xd6][0xfb]_[0xab][0xd5]ja[0xec][0x3][0x97][0xb0]>`[0xc4][0xcb][0x15][0x92][0x92][0xaa][0xb1][0xa0]6[0xb1];[0x9c] [0xad]KAoZ[0xad]:[0xd0][0xbd]P[0xa8]t[0x81][0xa8]7[0x7][0x9a]D>pY[0x1a][0x81][0x98][0x11][0x1a][\n]"41 main 2021-02-09 14:09:35,303 DEBUG (Wire.java:73)- http-outgoing-0 << "[0x9c]i[0xcf][0xa8][0x8c][0x2][0xad][0xb0][0x7]m[0xbc][0xcd]*F[0x9b][0x14][0x96]F0 [0xbd]DD[0xa2][0xdd][0x94]<[0x2]9#&[0xf6]:[0xa7][ [0xa6],[0x8a][0x8d][0xd7][0xee][0xfc][0xdf][0x7].$[0xac][0xef][0x3][0xd7]:[0xb4].[0xca]oY[0x9f][0x10] l[0x98][0xe0]s[0xc8][0xba]f[0x19]Z[0xd2][0x8c][0xcb][0xac][0xae]V[0xcc][0x8][0xa1][0xbc]2.3[0xde][0xc3]"[0xa5][0xa0][0x8f][0x92][0x8c]z[0xed][0xd7][0xeb][0xb2][0x91]N[0xd5]b[0xaf][0xd5][0xb]k[0xb5]7[0xa8]([0xdd][0xef][0x5][0xf2][0xed].[0xad][0x1e]#7[0xdf][0xae]hx[0xad]S[0x86][0xef][0xbb]Z"^[0x87][0xef][0x4][0x11][0xd0]=&M/W[0x8e]_[0x3]0[0xe2]m[0xe5][0x95]iN[0xe][0xab][0xc9][0xa4][0xa9]`@[0x8a]n&[0x94]G[0xb6][0x10][0xa7]N[0x1][0x94][0x19][0x81]E*[0x13]j[0xa8]'[0xc2][0xb0][0x14]Tu=\[0xf9].[0xb4]N[0x17][0xb8][0xe][0xc][0x9f]t]r[0xd7]Y[0x90][0xb2][0x92][0x86][0xce]j[0xe6][0x93][0x1d]>k[0xef][0x80]9?[0xc0][0x85]u?[0xd6][0x9d]Q[0xea][0x16][0xcc]l[0xb2][0xd0]x W![0xe4]4[0xd7][0xe3][0xf6][0xa8][0xf3]v[0xbd]g[0x94]=[0xa8]=[0xa6][0x1c][0xf5][0xfd]W[0xdf]>>[0xf8][0xee][0x99][0xb][0xd1]4[0x84][0x9d]i[0xdb]a1[0x12][0xed]Nw[0x6][0xa3][0x12]N[0xa2]T[0x92]9[0x94]U[0x8]S$[0x17][0xf2]H[0x91][0x9c][0x4](~x\ [0xb9][0xf7][0xf5]o[0x15][0x91][0xe2][0xd3];[0xc5][0xcb][0xdb][0xfb][0x8f][0xca][0xeb][0xa1]B[0x98][0xe5]u[0x9d]!>`[0x13](ub[0xb1]l[0x84][0x94][0x92][0x0][0xe1]-[0xff][0xd5]Gw[0xf7]w[0xb6][0x8b][0xbb]w[0xe][0x1e]~2F[0x1a][0xee][0xfe]~a[0xe3][0xf2][0xfe][0xa3][0xed]V[0xb7][0xd5]>[0xdd]-[0xee][0xfe]5[0xf2]`[0x1f][0xd3][0xd7][0xbd][0x9e][0x91][0xb6][0xbd]:[\n]"44 main 2021-02-09 14:09:35,303 DEBUG (Wire.java:87)- http-outgoing-0 << "yaA[0xa0][0xfd]K`[0xbf][0xf5]?[0xaf][0x3][0x0][0x0][0xff][0xff]0[0xc][0x81][0x9a][0x8b][0x9][0x0][0x0]"45 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:122)- http-outgoing-0 << HTTP/1.1 200 OK46 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:125)- http-outgoing-0 << Content-Encoding: gzip47 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:125)- http-outgoing-0 << Content-Length: 114548 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:125)- http-outgoing-0 << Content-Type: text/html49 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:125)- http-outgoing-0 << Server: bfe50 main 2021-02-09 14:09:35,306 DEBUG (LoggingManagedHttpClientConnection.java:125)- http-outgoing-0 << Date: Tue, 09 Feb 2021 06:09:34 GMT51 main 2021-02-09 14:09:35,310 DEBUG (MainClientExec.java:285)- Connection can be kept alive indefinitely52 main 2021-02-09 14:09:35,316 DEBUG (HttpClientProviderImpl.java:46)- respString: 53 百度一下,你就知道 55 main 2021-02-09 14:09:35,318 DEBUG (PoolingHttpClientConnectionManager.java:344)- Connection [id: 0][route: {s}->https://www.baidu.com:443] can be kept alive indefinitely56 main 2021-02-09 14:09:35,318 DEBUG (LoggingManagedHttpClientConnection.java:88)- http-outgoing-0: set socket timeout to 057 main 2021-02-09 14:09:35,318 DEBUG (PoolingHttpClientConnectionManager.java:351)- Connection released: [id: 0][route: {s}->https://www.baidu.com:443][total available: 1; route allocated: 1 of 50; total allocated: 1 of 200]58 main 2021-02-09 14:09:35,319 WARN (HttpClientServiceTest.java:80)- requestGet, times: 0, betMils: 403,betNanos: 402522300
maxTotal:200
Threads | defaultMaxPerRoute | validateAfterInactivity | betMils | betNanos |
256
| 20 | 1500
| 5270 | 5269994599 |
50 |
4310 |
4310753100 | ||
100 |
4513 |
4512503700 | ||
150 |
4963 |
4962632601 | ||
200 |
5245 |
5244764201 | ||
1024
|
20 | 1500
|
5087 |
5087564601 |
50 |
4323 |
4323088301 | ||
64
|
50
|
15000 |
3900 |
3900514800 |
2000(default) |
3531 |
3537111900 | ||
0 | 3358 | 3355394500 | ||
32
| 50
| 0 | 3471 |
3456438200 |
2000(default) |
2918 |
2912550800 | ||
50
| 50
|
15000 |
2847 |
2846675400 |
0 | 3190 | 3189325400 | ||
2000(default) | 3263 | 3264044800 | ||
1000 | 2741 | 2740233600 | ||
500 | 2802 | 2801841600 | ||
800 |
2800 |
2800641400 |
根据以上数据我们能看出什么呢?
在分析数据以前我先罗列一下电脑配置:
Windows 10 专业版
Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz 3.00 GHz 4核
16.0 GB
64 位
SSD 232.89 GB
首先根据本机硬件及系统配置,多线程可以提高系统的并发效率,但是也不是绝对的,大致在50个线程的时候效果较好。然后defaultMaxPerRoute参数也是50,即每个路由默认并行接受的请求数。因为本文的测试目标服务器是百度,百度服务器设置连接时间可无限期的保持“Connection can be kept alive indefinitely”,调节validateAfterInactivity参数,总的来说效果不是很大,当然需要排除网络请求的一些影响因素,比如拥塞、路由等情况,可根据下表的参数比较知道,即使相同的配置参数,不同时间点请求服务器,响应的时长也有差异,也就是说每一次的网络请求都是必然中的一次偶然情况。
Threads: 50
maxTotal: 200
defaultMaxPerRoute:50
validateAfterInactivity:1000
idleTimeOut:3
betMils | betNanos |
2813 | 2813779600 |
3228 | 3229129100 |
3030 | 3030484200 |
2806 | 2805046900 |
2981 | 2981383600 |
2629 | 2629455200 |
2814 | 2813863100 |
2747 | 2747754700 |
2748 | 2747132800 |
3365 | 3365044500 |
通过上表可以看出,每一次网络请求在同一网络情况下或相似网络情况下,响应的时长差距不大。但因为影响网络的因素众多,导致每次响应的时长都不一样。
暂时先分享到此,后期会有补充的,比如源码、架构等。
转载地址:http://kywyz.baihongyu.com/