httpclient架构原理介绍连接池详解
本篇重点介绍httpclient连接池的相关原理以及介绍,顺带的介绍httpclient发送请求时的简单介绍,并会带上⼀些源码分析。本篇博⽂是基于httpclient的4.5.2版本进⾏介绍的。
⼀、发送请求的流程原理
⼏个关键的类和接⼝介绍
在介绍架构原理前,先介绍⼏个类和接⼝,⽅便读者对httpclient的整体设计有个⼤概的概念。
HttpClient:⼀个接⼝,即http客户端的抽象,主要就是⽤它发送请求http请求。它的主要实现有CloseableHttpClient,相信读者们⽐较熟悉。
HttpRequestBase:⼀个抽象类,是请求内容的抽象。包括了请求协议、uri、还有⼀些配置。我们常⽤的HttpGet和HttpPost都是它的⼦类。
HttpClientConnectionManager:⼀个接⼝,连接管理的抽象。⼀般要发送http请求前,需要和⽬标服务建⽴连接,然后再发送数据包。这个连接管理器可以对连接以池的⽅式进⾏管理。
HttpRoute:⼀个final类,⽤来表⽰⽬标服务器(ip+端⼝)。
发送流程图
⼀个HttpRequestBase在被httpclient执⾏后,会经过⼀个链路被⼀个个组件处理。这⾥使⽤了职责链的设计模式,⼀个组件处理完后,就会交给下⼀个组件处理。这样做的好处就是如果要移除⼀个组件或者添加⼀个新的组件来实现对请求的⼀些处理⾮常⽅便。这⾥要说⼀下,上图列的组件中的⼀些是根据配置决定是否加⼊到该执⾏链中。
我们⼀般通过CloseableHttpClient httpClient = HttpClients.custom().build();获取到⼀个httpClient。这⾥返回的实际对象其实
是InternalHttpClient类,所以执⾏ute(request)时候最终会调⽤InternalHttpClient#doExecute()。我们看下对应的源码
protected CloseableHttpResponse doExecute(
final HttpHost target,
final HttpRequest request,
final HttpContext context)throws IOException, ClientProtocolException {
HttpExecutionAware execAware = null;
if(request instanceof HttpExecutionAware){
execAware =(HttpExecutionAware) request;
}
try{
final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request, target);
final HttpClientContext localcontext = HttpClientContext.adapt(
pendingcontext != null ? context :new BasicHttpContext());
RequestConfig config = null;
if(request instanceof Configurable){
config =((Configurable) request).getConfig();
}
if(config == null){
final HttpParams params = Params();
if(params instanceof HttpParamsNames){
if(!((HttpParamsNames) params).getNames().isEmpty()){
config = RequestConfig(params);
}
}else{
config = RequestConfig(params);
}
}
if(config != null){
localcontext.setRequestConfig(config);
}
setupContext(localcontext);
final HttpRoute route =determineRoute(target, wrapper, localcontext);
ute(route, wrapper, localcontext, execAware);
}catch(final HttpException httpException){
throw new ClientProtocolException(httpException);
}
}
通过代码可以看到,这⾥主要是做两件事
1. 获取requestConfig,然后设置到请求上下⽂中
2. 通过请求获取对应的⽬标服务器HttpRoute
之后就交给处理链处理请求。
处理链的构造是在InternalHttpClient的构造中完成的,也就是HttpClients.custom().build()⽅法中构造起来的。我们看下处理链的构造代码
public CloseableHttpClient build(){
...
ClientExecChain execChain =createMainExec(
requestExecCopy,
connManagerCopy,
reuseStrategyCopy,
keepAliveStrategyCopy,
new ImmutableHttpProcessor(new RequestTargetHost(),new RequestUserAgent(userAgentCopy)),
targetAuthStrategyCopy,
proxyAuthStrategyCopy,
userTokenHandlerCopy);
execChain =decorateMainExec(execChain);
...
execChain =new ProtocolExec(execChain, httpprocessorCopy);
execChain =decorateProtocolExec(execChain);
// Add request retry executor, if not disabled
if(!automaticRetriesDisabled){
HttpRequestRetryHandler retryHandlerCopy =Handler;
if(retryHandlerCopy == null){
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain =new RetryExec(execChain, retryHandlerCopy);
}
HttpRoutePlanner routePlannerCopy =utePlanner;
if(routePlannerCopy == null){
SchemePortResolver schemePortResolverCopy =this.schemePortResolver;
if(schemePortResolverCopy == null){
schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
}
if(proxy != null){
routePlannerCopy =new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
}else if(systemProperties){
routePlannerCopy =new SystemDefaultRoutePlanner(
schemePortResolverCopy, Default());
}else{
routePlannerCopy =new DefaultRoutePlanner(schemePortResolverCopy);
}
}
// Add redirect executor, if not disabled
if(!redirectHandlingDisabled){
RedirectStrategy redirectStrategyCopy =directStrategy;
if(redirectStrategyCopy == null){
redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
}
execChain =new RedirectExec(execChain, routePlannerCopy, redirectStrategyCopy);
}
// Optionally, add service unavailable retry executor
final ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy =this.serviceUnavailStrategy;
if(serviceUnavailStrategyCopy != null){
execChain =new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategyCopy);
}
/
/ Optionally, add connection back-off executor
if(this.backoffManager != null &&tionBackoffStrategy != null){
execChain =new BackoffStrategyExec(tionBackoffStrategy,this.backoffManager);
}
...
return new InternalHttpClient(
execChain,
connManagerCopy,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
defaultCookieStore,
defaultCredentialsProvider,
defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
closeablesCopy);
}
由于这个⽅法太长,所以只保留了处理链的那部分代码。
下⾯介绍处理链中各个组件的⼀个⼤概功能:
1. MainClientExec:主要执⾏客户端请求的,通过连接管理器,来把请求绑定到具体的连接上⾯,接着发送请求。同时也是在这个组件
⾥⾯做连接的池化处理等。
2. ProtocolExec:通过⼀系列的HttpProcessor处理链对Http消息按格式编码以及解码。每⼀个processor处理⼀个范畴的事情,⽐
如处理header,content以及cookie等等。我们可以往HttpRequestInterceptor和HttpResponseInterceptor中添加我们⾃⼰定义的。这样,HttpProcessor在处理请求和响应前,就会经过我们设定的进⾏相应的操作。
3. RetryExec:进⾏重连操作。是否要重连的判断的根据配置的HttpRequestRetryHandler。
4. RedirectExec:处理重定向的情况
5. ServiceUnavailableRetryExec:返回503进⾏重试
6. BackoffStrategyExec:对出现连接或者响应超时异常的route进⾏降级,缩⼩该route上连接数,能使得服务质量更好的route能得
到更多的连接。降级的速度可以通过因⼦设置,默认是每次降级减少⼀半的连接数,即降级因⼦是0.5。
上图中的HttpRequestExecutor只是MainClientExec中的组件,⽤于真正的发送http请求给⽬标服务器。
⼆、连接池的管理
通过下⾯这段代码,我们可以给httpclient设置连接管理器
private static CloseableHttpClient createHttpClient(){
PoolingHttpClientConnectionManager cm =new PoolingHttpClientConnectionManager();
// 将最⼤连接数添加
cm.setMaxTotal(200);
// 将每⼀个路由基础的连接添加
cm.setDefaultMaxPerRoute(40);
HttpHost httpHost =new HttpHost("www.baidu",80);
HttpRoute httpRoute =new HttpRoute(httpHost);
cm.setMaxPerRoute(httpRoute,80);
HttpRequestRetryHandler httpRetryHandler =new DefaultHttpRequestRetryHandler(5,false);
/
/默认连接配置
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000).setSocketTimeout(10000).build();
return HttpClients.custom()
.setDefaultRequestConfig(defaultRequestConfig)
.evictExpiredConnections()
.evictIdleConnections(10, TimeUnit.SECONDS)
.setConnectionManager(cm)
.setRetryHandler(httpRetryHandler).build();
}
在上⾯的例⼦中,我们初始化了⼀个基于连接池的连接管理器。这个连接池中最多持有200个连接,每个⽬标服务器最多持有40个连接。其中,我们专门设定了www.baidu:80的⽬标服务器的可以持有的最⼤连接数是80个。
如果我们不给httpclient配置指定的连接管理器,在默认情况下,httpclient也会⾃动使⽤PoolingHttpClientConnectionManager作为连接管理器。但是PoolingHttpClientConnectionManager默认的maxConnPerRoute和maxConnTotal分别是是2和20。也就是对于每个服务器最多只会维护2个连接,看起来有点少。所以,在⽇常使⽤时我们尽量使⽤⾃⼰配置的连接管理器⽐较好。
a. 连接池结构如下

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。