提升網(wǎng)絡(luò)請求穩(wěn)定性HttpClient的重試機制深入理解
序
本文主要研究一下HttpClient的重試機制
HttpRequestRetryHandler
org/apache/http/client/HttpRequestRetryHandler.java
public interface HttpRequestRetryHandler {
/**
* Determines if a method should be retried after an IOException
* occurs during execution.
*
* @param exception the exception that occurred
* @param executionCount the number of times this method has been
* unsuccessfully executed
* @param context the context for the request execution
*
* @return {@code true} if the method should be retried, {@code false}
* otherwise
*/
boolean retryRequest(IOException exception, int executionCount, HttpContext context);
}HttpRequestRetryHandler接口定義了retryRequest方法,它接收IOException、executionCount及context,然后判斷是否可以重試
DefaultHttpRequestRetryHandler
org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
/** the number of times a method will be retried */
private final int retryCount;
/** Whether or not methods that have successfully sent their request will be retried */
private final boolean requestSentRetryEnabled;
private final Set<Class<? extends IOException>> nonRetriableClasses;
/**
* Create the request retry handler using the specified IOException classes
*
* @param retryCount how many times to retry; 0 means no retries
* @param requestSentRetryEnabled true if it's OK to retry requests that have been sent
* @param clazzes the IOException types that should not be retried
* @since 4.3
*/
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);
}
}
/**
* Create the request retry handler using the following list of
* non-retriable IOException classes:
* <ul>
* <li>InterruptedIOException</li>
* <li>UnknownHostException</li>
* <li>ConnectException</li>
* <li>SSLException</li>
* </ul>
* @param retryCount how many times to retry; 0 means no retries
* @param requestSentRetryEnabled true if it's OK to retry non-idempotent requests that have been sent
*/
@SuppressWarnings("unchecked")
public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
this(retryCount, requestSentRetryEnabled, Arrays.asList(
InterruptedIOException.class,
UnknownHostException.class,
ConnectException.class,
SSLException.class));
}
/**
* Create the request retry handler with a retry count of 3, requestSentRetryEnabled false
* and using the following list of non-retriable IOException classes:
* <ul>
* <li>InterruptedIOException</li>
* <li>UnknownHostException</li>
* <li>ConnectException</li>
* <li>SSLException</li>
* </ul>
*/
public DefaultHttpRequestRetryHandler() {
this(3, false);
}
/**
* Used {@code retryCount} and {@code requestSentRetryEnabled} to determine
* if the given method should be retried.
*/
@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;
}
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;
}
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;
}
/**
* @return {@code true} if this handler will retry methods that have
* successfully sent their request, {@code false} otherwise
*/
public boolean isRequestSentRetryEnabled() {
return requestSentRetryEnabled;
}
/**
* @return the maximum number of times a method will be retried
*/
public int getRetryCount() {
return retryCount;
}
/**
* @since 4.2
*/
protected boolean handleAsIdempotent(final HttpRequest request) {
return !(request instanceof HttpEntityEnclosingRequest);
}
/**
* @since 4.2
*
* @deprecated (4.3)
*/
@Deprecated
protected boolean requestIsAborted(final HttpRequest request) {
HttpRequest req = request;
if (request instanceof RequestWrapper) { // does not forward request to original
req = ((RequestWrapper) request).getOriginal();
}
return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted());
}
}DefaultHttpRequestRetryHandler實現(xiàn)了HttpRequestRetryHandler接口,其無參構(gòu)造器默認(rèn)將InterruptedIOException、UnknownHostException、ConnectException、SSLException設(shè)定為不重試的異常,默認(rèn)retryCount為3,requestSentRetryEnabled為false;其retryRequest方法先判斷executionCount是否超出retryCount,接著判斷異常類型是否是不重試的異常類型,若request為aborted則返回false,若request非HttpEntityEnclosingRequest則表示冪等請求,返回true,若請求未完全發(fā)送則返回true,其余的默認(rèn)返回false。
RetryExec
org/apache/http/impl/execchain/RetryExec.java
@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
public class RetryExec implements ClientExecChain {
private final Log log = LogFactory.getLog(getClass());
private final ClientExecChain requestExecutor;
private final HttpRequestRetryHandler retryHandler;
public RetryExec(
final ClientExecChain requestExecutor,
final HttpRequestRetryHandler retryHandler) {
Args.notNull(requestExecutor, "HTTP request executor");
Args.notNull(retryHandler, "HTTP request retry handler");
this.requestExecutor = requestExecutor;
this.retryHandler = retryHandler;
}
@Override
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
Args.notNull(route, "HTTP route");
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
final Header[] origheaders = request.getAllHeaders();
for (int execCount = 1;; execCount++) {
try {
return this.requestExecutor.execute(route, request, context, execAware);
} catch (final IOException ex) {
if (execAware != null && execAware.isAborted()) {
this.log.debug("Request has been aborted");
throw ex;
}
if (retryHandler.retryRequest(ex, execCount, context)) {
if (this.log.isInfoEnabled()) {
this.log.info("I/O exception ("+ ex.getClass().getName() +
") caught when processing request to "
+ route +
": "
+ ex.getMessage());
}
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
if (!RequestEntityProxy.isRepeatable(request)) {
this.log.debug("Cannot retry non-repeatable request");
throw new NonRepeatableRequestException("Cannot retry request " +
"with a non-repeatable request entity", ex);
}
request.setHeaders(origheaders);
if (this.log.isInfoEnabled()) {
this.log.info("Retrying request to " + route);
}
} else {
if (ex instanceof NoHttpResponseException) {
final NoHttpResponseException updatedex = new NoHttpResponseException(
route.getTargetHost().toHostString() + " failed to respond");
updatedex.setStackTrace(ex.getStackTrace());
throw updatedex;
}
throw ex;
}
}
}
}
}RetryExec實現(xiàn)了ClientExecChain接口,其execute方法會循環(huán)執(zhí)行requestExecutor.execute,它catch了IOException,對于retryHandler.retryRequest(ex, execCount, context)返回true的,會在通過RequestEntityProxy.isRepeatable(request)判斷一下是否是可重復(fù)讀取的request,不是則拋出NonRepeatableRequestException;對于retryHandler.retryRequest返回false的則針對NoHttpResponseException重新包裝一下,將targetHost體現(xiàn)在message里頭然后重新拋出
HttpEntityEnclosingRequest
org/apache/http/HttpEntityEnclosingRequest.java
public interface HttpEntityEnclosingRequest extends HttpRequest {
/**
* Tells if this request should use the expect-continue handshake.
* The expect continue handshake gives the server a chance to decide
* whether to accept the entity enclosing request before the possibly
* lengthy entity is sent across the wire.
* @return true if the expect continue handshake should be used, false if
* not.
*/
boolean expectContinue();
/**
* Associates the entity with this request.
*
* @param entity the entity to send.
*/
void setEntity(HttpEntity entity);
/**
* Returns the entity associated with this request.
*
* @return entity
*/
HttpEntity getEntity();
}HttpEntityEnclosingRequest定義了getEntity、setEntity、expectContinue方法,它的子類有HttpPut、HttpPost、HttpPatch、HttpDelete等
RequestEntityProxy.isRepeatable
org/apache/http/impl/execchain/RequestEntityProxy.java
class RequestEntityProxy implements HttpEntity {
private final HttpEntity original;
private boolean consumed = false;
public boolean isConsumed() {
return consumed;
}
static boolean isRepeatable(final HttpRequest request) {
if (request instanceof HttpEntityEnclosingRequest) {
final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
if (entity != null) {
if (isEnhanced(entity)) {
final RequestEntityProxy proxy = (RequestEntityProxy) entity;
if (!proxy.isConsumed()) {
return true;
}
}
return entity.isRepeatable();
}
}
return true;
}
}RequestEntityProxy提供了靜態(tài)方法isRepeatable用于判斷該request的entity是否可以重復(fù)讀取,對于非HttpEntityEnclosingRequest的返回true,是HttpEntityEnclosingRequest類型的話則判斷entity.isRepeatable(),若entity是RequestEntityProxy類型的,則通過RequestEntityProxy.isConsumed來判斷
entity.isRepeatable()
public interface HttpEntity {
/**
* Tells if the entity is capable of producing its data more than once.
* A repeatable entity's getContent() and writeTo(OutputStream) methods
* can be called more than once whereas a non-repeatable entity's can not.
* @return true if the entity is repeatable, false otherwise.
*/
boolean isRepeatable();
//......
}HttpEntity接口定義了isRepeatable方法,用于表示entity的content及OutputStream是否可以讀寫多次。其實現(xiàn)類里頭,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity為true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity為false
小結(jié)
HttpRequestRetryHandler接口定義了retryRequest方法,它接收IOException、executionCount及context,然后判斷是否可以重試
DefaultHttpRequestRetryHandler實現(xiàn)了HttpRequestRetryHandler接口,其無參構(gòu)造器默認(rèn)將InterruptedIOException、UnknownHostException、ConnectException、SSLException設(shè)定為不重試的異常,默認(rèn)retryCount為3,requestSentRetryEnabled為false;其retryRequest方法先判斷executionCount是否超出retryCount,接著判斷異常類型是否是不重試的異常類型,若request為aborted則返回false,若request非HttpEntityEnclosingRequest則表示冪等請求,返回true,若請求未完全發(fā)送則返回true,其余的默認(rèn)返回false。
RetryExec實現(xiàn)了ClientExecChain接口,其execute方法會循環(huán)執(zhí)行requestExecutor.execute,它catch了IOException,對于retryHandler.retryRequest(ex, execCount, context)返回true的,會在通過RequestEntityProxy.isRepeatable(request)判斷一下是否是可重復(fù)讀取的request,不是則拋出NonRepeatableRequestException
DefaultHttpRequestRetryHandler針對不是冪等請求的HttpEntityEnclosingRequest類型(HttpPut、HttpPost、HttpPatch、HttpDelete),不會重試;若retryHandler.retryRequest返回可以重試,RetryExec還有一個repeatable的判斷,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity為true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity為false
以上就是提升網(wǎng)絡(luò)請求穩(wěn)定性HttpClient的重試機制深入理解的詳細(xì)內(nèi)容,更多關(guān)于HttpClient重試機制的資料請關(guān)注腳本之家其它相關(guān)文章!
- 解讀httpclient的validateAfterInactivity連接池狀態(tài)檢測
- httpclient的disableConnectionState方法工作流程
- 探索HttpClient中的close方法及其對連接的影響
- HttpClient的RedirectStrategy重定向處理核心機制
- HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究
- HttpClient HttpRoutePlanner接口確定請求目標(biāo)路由
- 使用Backoff策略提高HttpClient連接管理的效率
- httpclient getPoolEntryBlocking連接池方法源碼解讀
相關(guān)文章
詳解SpringBoot開發(fā)案例之整合定時任務(wù)(Scheduled)
本篇文章主要介紹了詳解SpringBoot開發(fā)案例之整合定時任務(wù)(Scheduled),具有一定的參考價值,有興趣的可以了解一下2017-07-07
使用Java代碼進行因數(shù)分解和求最小公倍數(shù)的示例
這篇文章主要介紹了使用Java代碼進行因數(shù)分解和求最小公倍數(shù)的示例,都是基于最基礎(chǔ)的算法原理實現(xiàn),需要的朋友可以參考下2015-11-11
Java編程實現(xiàn)遍歷兩個MAC地址之間所有MAC的方法
這篇文章主要介紹了Java編程實現(xiàn)遍歷兩個MAC地址之間所有MAC的方法,涉及Java針對MAC的遍歷獲取與字符串轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Springboot以Repository方式整合Redis的方法
這篇文章主要介紹了Springboot以Repository方式整合Redis的方法,本文通過圖文并茂實例詳解給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04

