(一)HttpClient

  • 简介

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,基于标准的java语言。

  • 功能介绍

  1. 支持HTTP和HTTPS协议
  2. 实现了HTTP的方法,GET,POST,PUT,DELETE等方法。
  3. 连接管理器支持多线程的应用。
  4. 可以设置连接超时
  • 使用方法

使用HttpClient发送请求,接收响应可以分为一下几步:

  1. 创建HttpClient对象
  2. 创建请求方法的实例,并且指定URL
  3. 发送请求参数,GET请求和POST请求发送参数的方式有所不同
  4. 调用HttpClient对象的execute方法,返回HttpResponse对象
  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容
  6. 连接释放。无论成功与否,必须释放连接

(二) HttpClientUtil

2.1 HttpClient版本

笔者用到的版本是4.5.5,由于是maven工程,需要在pom文件引入对应的坐标。

<dependency>  <groupId>org.apache.httpcomponents</groupId>                 <artifactId>httpclient</artifactId>                         <version>4.5.5</version>
</dependency>

2.2 项目中用到的工具类如下

package cn.htjc.customer.util;import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.client.utils.URIBuilder;
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.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Slf4j
public class HttpClientUtil {// utf-8字符编码private static final String CHARSET_UTF_8 = "utf-8";// HTTP内容类型。相当于form表单的形式,提交数据private static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";// 连接管理器private static PoolingHttpClientConnectionManager pool;// 请求配置private static RequestConfig requestConfig;static {try {log.info("初始自定义HttpClient......开始");SSLContextBuilder builder = new SSLContextBuilder();builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());// 配置同时支持 HTTP 和 HTPPSRegistry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslsf).build();// 初始化连接管理器pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);// 设置连接池的最大连接数pool.setMaxTotal(200);// 设置每个路由上的默认连接个数  pool.setDefaultMaxPerRoute(20);// 根据默认超时限制初始化requestConfig// 客户端从服务器读取数据的timeoutint socketTimeout = 1000;// 客户端和服务器建立连接的timeoutint connectTimeout = 10000;// 从连接池获取连接的timeoutint connectionRequestTimeout = 10000;//设置请求超时时间requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectionRequestTimeout).setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();log.info("初始自定义HttpClient......结束");} catch (Exception e) {log.error("初始自定义HttpClient......失败");}}private HttpClientUtil() {}private static CloseableHttpClient getHttpClient() {// 状态码是503的时候,该策略生效ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new ServiceUnavailableRetryStrategy() {@Overridepublic boolean retryRequest(HttpResponse httpResponse, int i, HttpContext httpContext) {if (i < 3) {log.info("ServiceUnavailableRetryStrategy========================"+i);return true;}return false;}@Overridepublic long getRetryInterval() {return 2000L;}};CloseableHttpClient httpClient = HttpClients.custom()// 设置连接池管理.setConnectionManager(pool)// 设置请求配置.setDefaultRequestConfig(requestConfig)// 设置重试次数.setRetryHandler(new DefaultHttpRequestRetryHandler()).setServiceUnavailableRetryStrategy(serviceUnavailableRetryStrategy).build();return httpClient;}public static String doGet(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpClient = getHttpClient();String resultString = "";CloseableHttpResponse response = null;try {// 创建uriURIBuilder builder = new URIBuilder(url);if (param != null) {for (String key : param.keySet()) {builder.addParameter(key, param.get(key));}}URI uri = builder.build();// 创建http GET请求HttpGet httpGet = new HttpGet(uri);// 执行请求response = httpClient.execute(httpGet);// 判断返回状态是否为200if (response.getStatusLine().getStatusCode() == 200) {resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}return resultString;}public static String doGet(String url) {return doGet(url, null);}public static String doPost(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpClient = getHttpClient();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建参数列表if (param != null) {List<NameValuePair> paramList = new ArrayList<>();for (String key : param.keySet()) {paramList.add(new BasicNameValuePair(key, param.get(key)));}// 模拟表单UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, CHARSET_UTF_8);entity.setContentType(CONTENT_TYPE_FORM_URL);httpPost.setEntity(entity);}// 执行http请求mainresponse = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}return resultString;}public static String doPost(String url) {return doPost(url, null);}public static String doPostJson(String url, String json) {// 创建Httpclient对象CloseableHttpClient httpClient = getHttpClient();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建请求内容StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);httpPost.setEntity(entity);// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}return resultString;}}

代码中出现了@Slf4j,作用是引入log,手动打印日志。这个注解是lombok的注解。
解释一下,什么是Route?
Route的概念可以理解为客户端机器到目标机器的一条线路,例如使用HttpClient的实现来分别请求 www.163.com 的资源和 www.sina.com 的资源就会产生两个route。缺省条件下对于每个Route,HttpClient仅维护2个连接,总数不超过20个连接。

2.3 笔者着重说一下http连接池。

  • 1 为什么要使用http连接池?

    • 延迟降低,如果不使用连接池,每次发起的http请求都会重新建立tcp连接(三次握手),用完就会关闭连接(4次握手),采用连接池则会减少这不是分时间的消耗。连接池管理的对象都是长连接。

    • 支持更大的并发,由于连接池只适用于请求经常访问同一主机(或同一端口的情况),连接池避免了反复建立连接,抢占端口资源的情况,如果没用连接池,可能导致连接建立不了。

  • 2 设置超时时间

    首先要明白三个概念:socketTimeout,connectTimeout,connectionRequestTimeout。

    socketTimeout:客户端和服务器读取数据的timeout

    connectTimeout:客户端和服务器建立连接的timeout

    connectionRequestTimeout:从连接池获取连接的timeout

  • 3 解释:一次http请求
    一次http请求,必定会有三个阶段,一:建立连接;二:数据传送;三,断开连接。
    当建立连接在规定的时间内(ConnectionTimeOut )没有完成,那么此次连接就结束了。后续的SocketTimeOutException就一定不会发生。只有当连接建立起来后,
    也就是没有发生ConnectionTimeOutException ,才会开始传输数据,如果数据在规定的时间内(SocketTimeOut)传输完毕,则断开连接。否则,触发SocketTimeOutException。

(三) HttpClient的重试机制

上面说了这么多,就是为了引出下面的重试问题。由于项目中要访问外部接口,访问接口的时候,偶尔会出现SocketTimeOutException:Read timed out,其实就是客户端读取服务器的数据超时了。

3.1. 那么问题来了HttpClient有没有重试策略?

使用PoolingHttpClientConnectionManager得到的InternalHttpClient实例,是抽象类CloseableHttpClient的一个实现。

看一下ClientExecChain接口的实现类

​ 简单看一下build()方法

public CloseableHttpClient build() {// 省略一些代码  // 添加MainClientExecClientExecChain execChain = this.createMainExec(requestExecCopy, (HttpClientConnectionManager)connManagerCopy, (ConnectionReuseStrategy)reuseStrategyCopy, (ConnectionKeepAliveStrategy)keepAliveStrategyCopy, new ImmutableHttpProcessor(new HttpRequestInterceptor[]{new RequestTargetHost(), new RequestUserAgent(userAgentCopy)}), (AuthenticationStrategy)targetAuthStrategyCopy, (AuthenticationStrategy)proxyAuthStrategyCopy, (UserTokenHandler)userTokenHandlerCopy);execChain = this.decorateMainExec(execChain);// 添加ProtocolExecClientExecChain execChain = new ProtocolExec(execChain, httpprocessorCopy);ClientExecChain execChain = this.decorateProtocolExec(execChain);// Add request retry executor, if not disabledif (!automaticRetriesDisabled) {HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;if (retryHandlerCopy == null) {retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;}execChain = new RetryExec(execChain, retryHandlerCopy);} // 省去部分代码// 如果不为空,添加ServiceUnavailableRetryExecServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = this.serviceUnavailStrategy;if (serviceUnavailStrategyCopy != null) {execChain = new ServiceUnavailableRetryExec((ClientExecChain)execChain, serviceUnavailStrategyCopy);}// 添加RedirectExecif (!this.redirectHandlingDisabled) {authSchemeRegistryCopy = this.redirectStrategy;if (authSchemeRegistryCopy == null) {authSchemeRegistryCopy = DefaultRedirectStrategy.INSTANCE;}execChain = new RedirectExec((ClientExecChain)execChain, (HttpRoutePlanner)routePlannerCopy, (RedirectStrategy)authSchemeRegistryCopy);}// 省去部分代码 return new InternalHttpClient((ClientExecChain)execChain, (HttpClientConnectionManager)connManagerCopy, (HttpRoutePlanner)routePlannerCopy, cookieSpecRegistryCopy, (Lookup)authSchemeRegistryCopy, (CookieStore)defaultCookieStore, (CredentialsProvider)defaultCredentialsProvider, this.defaultRequestConfig != null ? this.defaultRequestConfig : RequestConfig.DEFAULT, closeablesCopy);
}

​ 自上而下,创建了不同的ClientExecChain实例。注意:创建对象的顺序就是执行器链的顺序

在构造CloseableHttpClient实例的时候,判断是否关闭了自动重试功能,automaticRetriesDisabled默认是false。如果没有指定执行器链,就用RetryExec。默认的重试策略是DefaultHttpRequestRetryHandler。

如果重写了ServiceUnavailableRetryStrategy接口,或者使用了DefaultServiceUnavailableRetryStrategy,ServiceUnavailableRetryExec也会加入到执行器链里。

同理,redirectHandlingDisabled默认是false,RedirectExec也会加入到执行器链,并且会最先执行。

3.2 执行流程

前面已经看到我们使用的HttiClient本质上是InternalHttpClient,这里看下他的执行发送数据的方法。

 @Overrideprotected CloseableHttpResponse doExecute(final HttpHost target,final HttpRequest request,final HttpContext context) throws IOException, ClientProtocolException {//省略一些代码            return this.execChain.execute(route, wrapper, localcontext, execAware);}}

首先经过RedirectExec,RedirectExec里面调用ServiceUnavailableRetryExec的excute(),进入ServiceUnavailableRetryExec后,调用RetryExec的excute(),进入发到RetryExec后,调用ProtocolExec的execute(),最后调用MainClientExec的excute()。

执行器链结束后,执行HttpRequestExecutor的excute(),excute()方法调用了自己的doSendRequest()。

之后一步一步的返回,遇到异常进行处理。

下面是RetryExec发送请求的部分

public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext context,      HttpExecutionAware execAware) throws IOException, HttpException {// 参数检验Args.notNull(route, "HTTP route");Args.notNull(request, "HTTP request");Args.notNull(context, "HTTP context");// 获取请求头的全部信息Header[] origheaders = request.getAllHeaders();// 初始化请求次数为1int execCount = 1;while(true) {try {// 调用基础executor执行http请求return this.requestExecutor.execute(route, request, context, execAware);} catch (IOException var9) {// 发生IO异常的时候,判断上下文是否已经中断,如果中断则抛异常退出if (execAware != null && execAware.isAborted()) {this.log.debug("Request has been aborted");throw var9;}// 根据重试策略,判断当前执行状况是否要重试,如果是则进入下面逻辑if (!this.retryHandler.retryRequest(var9, execCount, context)) {if (var9 instanceof NoHttpResponseException) {NoHttpResponseException updatedex = new NoHttpResponseException(route.getTargetHost().toHostString() + " failed to respond");updatedex.setStackTrace(var9.getStackTrace());throw updatedex;}throw var9;}// 日志if (this.log.isInfoEnabled()) {this.log.info("I/O exception (" + var9.getClass().getName() + ") caught when processing request to " + route + ": " + var9.getMessage());}// 日志if (this.log.isDebugEnabled()) {this.log.debug(var9.getMessage(), var9);}// 判断当前请求是否可以重复发起if (!RequestEntityProxy.isRepeatable(request)) {this.log.debug("Cannot retry non-repeatable request");throw new NonRepeatableRequestException("Cannot retry request with a non-repeatable request entity", var9);}// 设置请求头 request.setHeaders(origheaders);// 日志if (this.log.isInfoEnabled()) {this.log.info("Retrying request to " + route);}++execCount;}}}

当发生IOException,判断是否要重试。如果重试则记录相应的次数,如果不重试,就抛出异常并且退出。

 //单例模式 final 不可变的对象,线程安全public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();//重试次数private final int retryCount;//如果一个请求发送成功过,是否还会被再次发送private final boolean requestSentRetryEnabled;// 不允许重试的异常类private final Set<Class<? extends IOException>> nonRetriableClasses;// 默认重试3次,请求发送成功,不在发送public DefaultHttpRequestRetryHandler() {this(3, false);}public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {this(retryCount, requestSentRetryEnabled, Arrays.asList(InterruptedIOException.class,UnknownHostException.class,ConnectException.class,SSLException.class));}protected DefaultHttpRequestRetryHandler(final int retryCount,final boolean requestSentRetryEnabled,final Collection<Class<? extends IOException>> clazzes) {super();this.retryCount = retryCount;this.requestSentRetryEnabled = requestSentRetryEnabled;this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();for (final Class<? extends IOException> clazz: clazzes) {this.nonRetriableClasses.add(clazz);}}

通过构造函数,可以看出:

  1. 重试3次
  2. 请求成功,就不再重试
  3. InterruptedIOException、UnknownHostException、ConnectException、SSLException,发生这4种异常不重试
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {  // 参数校验                         Args.notNull(exception, "Exception parameter");    Args.notNull(context, "HTTP context");   // 如果执行次数大于重试次数,则不在进行重试 if (executionCount > this.retryCount) {        return false;  // 如果是上面规定的4种异常,则不进行重试  } else if (this.nonRetriableClasses.contains(exception.getClass())) {        return false;    } else {        Iterator i$ = this.nonRetriableClasses.iterator();        Class rejectException;        do {            if (!i$.hasNext()) {                HttpClientContext clientContext = HttpClientContext.adapt(context);                           HttpRequest  request = clientContext.getRequest();  // 判断当前请求是否被停止              if (this.requestIsAborted(request)) {                    return false;                }        // 判断请求是否是幂等请求,get是幂等请求,post/put都是非幂等请求        if (this.handleAsIdempotent(request)) {                    return true;                }      // 根据上下文判断是否发送成功,或者根据状态是否可以重复发送          if (clientContext.isRequestSent() && !this.requestSentRetryEnabled) {                    return false;                }                return true;            }            rejectException = (Class)i$.next();        } while(!rejectException.isInstance(exception));   /// 如果是上面规定的集中异常的子类,退出循环return false;    }}

​ 关于默认的重试策略

  1. 如果超过三次不进行重试
  2. 以上4中异常及其子类不进行重试
  3. 同一个请求在异步任务中已经停止,不进行重试
  4. 幂等的方法可以进行重试,比如get,含有http body都可以认为是非幂等
  5. 请求没有发送成功,可以进行重试

问题来了,发送成功的请求是怎么样的?
下面的代码在HttpCoreContext里面,HttpCoreContext是HttpContext的实现类

  public static final String HTTP_REQ_SENT    = "http.request_sent";public boolean isRequestSent() {final Boolean b = getAttribute(HTTP_REQ_SENT, Boolean.class);return b != null && b.booleanValue();}

当前httpContext中的http.request_sent设置为true,则认为已经发送成功。

HttpRequestExecutor的excute(),调用了自己的doSendRequest()。

protected HttpResponse doSendRequest(HttpRequest request, HttpClientConnection conn, HttpContext context) throws IOException, HttpException {// 参数检验Args.notNull(request, "HTTP request");Args.notNull(conn, "Client connection");Args.notNull(context, "HTTP context");HttpResponse response = null;// 将连接放入上下文context.setAttribute("http.connection", conn);// 在请求发送之前,将http.request_sent放入上下文context的属性中,值为falsecontext.setAttribute("http.request_sent", Boolean.FALSE);// 将request的header放入连接中conn.sendRequestHeader(request);// 如果是post/put这种有body的请求,要先进行判断if (request instanceof HttpEntityEnclosingRequest) {boolean sendentity = true;// 获取http协议版本号ProtocolVersion ver = request.getRequestLine().getProtocolVersion();// 满足100-continue,并且http协议不是1.0if (((HttpEntityEnclosingRequest)request).expectContinue() && !ver.lessEquals(HttpVersion.HTTP_1_0)) {// 刷新当前连接,发送数据conn.flush();// Checks if response data is available from the connectionif (conn.isResponseAvailable(this.waitForContinue)) {// Receives the request line and headers of the next response available from this connection.response = conn.receiveResponseHeader();// 判断相应是否携带实体(是否有body)if (this.canResponseHaveBody(request, response)) {// Receives the next response entity available from this connection and attaches it to an existing HttpResponse object.conn.receiveResponseEntity(response);}// 获取请求状态码int status = response.getStatusLine().getStatusCode();if (status < 200) {if (status != 100) {throw new ProtocolException("Unexpected response: " + response.getStatusLine());}response = null;} else {sendentity = false;}}}if (sendentity) {// 通过连接发送请求实体conn.sendRequestEntity((HttpEntityEnclosingRequest)request);}}// Writes out all pending buffered data over the open connection.conn.flush();// 将http.request_sent置为truecontext.setAttribute("http.request_sent", Boolean.TRUE);return response;}

判断是否携带实体的方法

protected boolean canResponseHaveBody(HttpRequest request, HttpResponse response) {// 如果是head请求,返回false  HEAD:只请求页面首部if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {return false;} else {int status = response.getStatusLine().getStatusCode();return status >= 200 && status != 204 && status != 304 && status != 205;}}

注:HttpEntityEnclosingRequest是一个接口

public interface HttpEntityEnclosingRequest extends HttpRequest {// 询问Server是否愿意接收数据boolean expectContinue();//  设置httpEntity  void setEntity(HttpEntity entity);// 获取httpEntityHttpEntity getEntity();
}

HttpEntityEnclosingRequestBase是实现HttpEntityEnclosingRequest的抽象类

public abstract class HttpEntityEnclosingRequestBase extends HttpRequestBase implements HttpEntityEnclosingRequest {// HttpEntity其实相当于一个消息实体,内容是http传送的报文,有多个实现类,常用StringEntityprivate HttpEntity entity;public HttpEntityEnclosingRequestBase() {}public HttpEntity getEntity() {return this.entity;}public void setEntity(HttpEntity entity) {this.entity = entity;}// 判断此请求是否应使用expect-continuepublic boolean expectContinue() {// 从请求头获取Except键值对  Header expect = this.getFirstHeader("Expect");// 如果except不为空,并且内容是 100-continue时返回truereturn expect != null && "100-continue".equalsIgnoreCase(expect.getValue());}public Object clone() throws CloneNotSupportedException {HttpEntityEnclosingRequestBase clone = (HttpEntityEnclosingRequestBase)super.clone();if (this.entity != null) {clone.entity = (HttpEntity)CloneUtils.cloneObject(this.entity);}return clone;}
}

下图可以看出,HttpPost和HttpPut是HttpEntityEnclosingRequestBase的子类

简要分析一下,上述的操作过程

  1. 开始将http.request_sent设置为false
  2. 通过流flush数据到客户端
  3. 然后将http.request_sent设置为true

显然conn.flush()是可以发生异常的。注意:conn都是从连接池获取的。

3.3 关闭重试

默认是开启重试的,可以在创建HttpClientBuilder的时候,调用下面的方法关闭。

public final HttpClientBuilder disableAutomaticRetries() {this.automaticRetriesDisabled = true;return this;}

(四)总结

4.1重试发生的条件

  1. 只有发生IOException才会发生重试
  2. InterruptedIOException、UnknownHostException、ConnectException、SSLException,发生这4种异常不重试
  3. get方法可以重试3次,post方法对应的socket流没有被flush成功时可以重试3次

4.2不发生重试的异常

  1. InterruptedIOException,线程中断异常
  2. UnknownHostException,找不到对应host
  3. ConnectException,找到了host但是建立连接失败。
  4. SSLException,https认证异常

4.3 实践中遇到的异常

另外,我们还经常会提到两种超时,连接超时与读超时:
1. java.net.SocketTimeoutException: Read timed out
2. java.net.SocketTimeoutException: connect timed out
这两种超时都是SocketTimeoutException,继承自InterruptedIOException,属于线程中断异常,不会进行重试。

HttpClient连接池及重试机制相关推荐

  1. Http持久连接与HttpClient连接池

    以下文章来源方志朋的博客,回复"666"获面试宝典 一.背景 HTTP协议是无状态的协议,即每一次请求都是互相独立的.因此它的最初实现是,每一个http请求都会打开一个tcp so ...

  2. Http 持久连接与 HttpClient 连接池

    转载自  Http 持久连接与 HttpClient 连接池 一.背景 HTTP协议是无状态的协议,即每一次请求都是互相独立的.因此它的最初实现是,每一个http请求都会打开一个tcp socket连 ...

  3. 问题备忘: httpclient连接池异常引发的惨案

    1. 问题描述 客户端A --> Ngnix --> 服务B Ngnix做服务B的负载,客户端访问服务B时,客户端偶尔会有抛出TimeoutException异常. 举个例子:如A在09: ...

  4. HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查

    HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查 参考文章: (1)HttpCli ...

  5. Apache HttpClient连接池泄露问题排查

    Apache HttpClient连接池泄露问题排查 问题背景 业务系统主要的业务是一个数据聚合管理平台,其中系统有一个功能是同步所有资源(简称 大同步) 业务同步数据请求数据工具是适配 Apache ...

  6. HttpClient连接池的连接保持、超时和失效机制

    HTTP是一种无连接的事务协议,底层使用的还是TCP,连接池复用的就是TCP连接,目的就是在一个TCP连接上进行多次的HTTP请求从而提高性能.每次HTTP请求结束的时候,HttpClient会判断连 ...

  7. HttpClient连接池小记

    **欢迎关注公众号** **微信扫一扫** [仅需9.9订阅专栏合集,作者所有付费文章都能看(持续更新)] 推荐[Kafka教程]https://bigbird.blog.csdn.net/artic ...

  8. HttpClient连接池

    推荐Springboot集成http连接池 https://blog.csdn.net/hellozpc/article/details/106861972 常用的http客户端有JDK原生的URLC ...

  9. HttpClient连接池设置引发的一次雪崩

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://i7q.cn/50G6cx - 1 - 事件背 ...

最新文章

  1. 华为交换机配置基本命令
  2. 负载均衡下ajax第二次请求,会话清除第二个AJAX电话
  3. 项目用的几种机型参数调查
  4. 一个C#写的调用外部进程类
  5. Ansys节点数据批量一键导出脚本生成CSV (ansys数据导出利用matlab脚本)
  6. python打开xml文件报错:UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xb7 in position 58: illegal multi
  7. springmvc接收参数
  8. 叶修手速900什么概念_什么是数学?数学是探索的过程
  9. Vivo叉九Android版本,vivo狂野飙车9正版下载
  10. matlab 下的集成学习工具箱
  11. 国产各数据库厂商数据库梳理
  12. 未检测到ca设备或ca驱动异常_安川伺服驱动器SGDV报警原因及处理措施
  13. Android航海航线地图,航海王热血航线东南之海宝藏一览
  14. 最新个人所得税计算方法
  15. mapgis明码文件转为点线面文件_MapGIS明码文件的获得和在坐标转化中的应用研究(2)...
  16. 策略路由(本地策略和接口策略)
  17. [bzoj3998]弦论
  18. Android使用Downloadmanager进行下载时,鉴别取消下载和下载完成的广播
  19. python爬去新浪微博_Python爬虫爬取新浪微博内容示例【基于代理IP】
  20. PIXIJS学习笔记

热门文章

  1. 视网膜眼底图的数据库介绍
  2. 网络端口被占用的解决方法
  3. python之PIL 小图拼接成大图
  4. MAC一键显示隐藏文件的开关工具- ShowOrHide 1.0
  5. 蓝桥杯嵌入式(G431RBT6):按键
  6. Chrome谷歌浏览器如何永久阻止自动升级更新
  7. 交通大数据应用细分_大数据之路之交通大数据应用总体架构设计
  8. Android APK 加固-支持DEX虚拟化及资源加密
  9. 关于在线问卷调查平台
  10. 基于JAVA的问卷调查平台系统【数据库设计、论文、源码、开题报告】