线上服务依赖第三方服务,访问量较大的情况下接口RT响应时间很长。由于每个超时参数设置为5s。
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); // 连接池最大的连接数 connManager.setMaxTotal(6000); // 单个路由的最大连接数,例如:www.baidu.com,图片最大高峰并发量520 connManager.setDefaultMaxPerRoute(500); RequestConfig.Builder custom = RequestConfig.custom(); // 连接超时时间 建立连接时间 此前设置3000 custom.setConnectTimeout(800); // 从connect Manager获取Connection 超时时间(连接池获取连接超时时间) custom.setConnectionRequestTimeout(500); // (获取response的返回时间)读取返回时间 此前设置40000,官方建议40s custom.setSocketTimeout(1000); RequestConfig config = custom.build(); // DefaultHttpRequestRetryHandler 重试策略(可自定义) httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler(1, true)).setConnectionManager(connManager) .setDefaultRequestConfig(config).build(); 复制代码
通过debug,可以知道build方法执行以后,获取到InternalHttpClient对象,如果没有指定执行链,就是用RetryExec执行器,默认的重试策略是DefaultHttpRequestRetryHandler
public CloseableHttpClient build() { ... 省略部分代码 // Add request retry executor, if not disabled if (!automaticRetriesDisabled) { HttpRequestRetryHandler retryHandlerCopy = this.retryHandler; if (retryHandlerCopy == null) { retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE; } execChain = new RetryExec(execChain, retryHandlerCopy); } ... 省略部分代码 } 复制代码
默认有重试策略,或者可以手动更改或者重新定义重试策略,但是一些情况下,是可以重试,一些情况下重试是失效的。
对于我们的场景应用中的get与post,可以总结为:
只有发生IOExecetion时才会发生重试 InterruptedIOException、UnknownHostException、ConnectException、SSLException,发生这4中异常不重试 get方法可以重试3次,post方法在socket对应的输出流没有被write并flush成功时可以重试3次。
不重试的异常:
DefaultHttpRequestRetryHandler 重试策略
@Override public boolean retryRequest( final IOException exception, final int executionCount, final HttpContext context) { Args.notNull(exception, "Exception parameter"); Args.notNull(context, "HTTP context"); if (executionCount > this.retryCount) { // Do not retry if over max retry count return false; } // 异常类是否在禁止重试中 if (this.nonRetriableClasses.contains(exception.getClass())) { return false; } else { for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) { if (rejectException.isInstance(exception)) { return false; } } } final HttpClientContext clientContext = HttpClientContext.adapt(context); final HttpRequest request = clientContext.getRequest(); if(requestIsAborted(request)){ return false; } if (handleAsIdempotent(request)) { // Retry if the request is considered idempotent return true; } // 根据上下文判断请求是否发送成功了,或者根据状态为是否永远可以重复发送(默认的是否) requestSentRetryEnabled 参数可以在构建httpCLient对象设置重试策略指定此属性 // isRequestSent 是在HttpRequestExecutor中doSendRequest方法中flush成功设置context.setAttribute("http.request_sent", Boolean.TRUE); if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) { // Retry if the request has not been sent fully or // if it's OK to retry methods that have been sent return true; } // otherwise do not retry return false; } // 几种类型的异常不重试,同时判断是否与异常类型的子类,是子类的话,也不重试 复制代码
另外,两种超时,连接超时与读超时: java.net.SocketTimeoutException: Read timed out java.net.SocketTimeoutException: connect timed out 这两种超时都是SocketTimeoutException,继承自InterruptedIOException,属于上面的第1种线程中断异常,不会进行重试。
post请求在输出流进行write与flush的时候,会发生哪些除了InterruptedIOException、UnknownHostException、ConnectException、SSLException以外的IOExecetion。
可能出问题一步在于HttpClientConnection.flush()的一步,跟进去可以得知其操作的对象是一个SocketOutputStream,而这个类的flush是空实现,所以只需要看wirte方法即可。
//根据上下文判断请求是否发送成功了,或者根据状态为是否永远可以重复发送(默认的是否) if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) { return true; } 复制代码
httpclient默认提供了重试策略,对于一些场景下,我们可以手动关闭重试策略。HttpClientBuilder中,其build()方法中之所以选择了RetryExec执行器是有前置条件的,即没有手动禁止。
// Add request retry executor, if not disabled if (!automaticRetriesDisabled) { HttpRequestRetryHandler retryHandlerCopy = this.retryHandler; if (retryHandlerCopy == null) { retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE; } execChain = new RetryExec(execChain, retryHandlerCopy); } 复制代码