提升網(wǎng)絡(luò)請(qǐng)求穩(wěn)定性HttpClient的重試機(jī)制深入理解
序
本文主要研究一下HttpClient的重試機(jī)制
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實(shí)現(xiàn)了HttpRequestRetryHandler接口,其無(wú)參構(gòu)造器默認(rèn)將InterruptedIOException、UnknownHostException、ConnectException、SSLException設(shè)定為不重試的異常,默認(rèn)retryCount為3,requestSentRetryEnabled為false;其retryRequest方法先判斷executionCount是否超出retryCount,接著判斷異常類(lèi)型是否是不重試的異常類(lèi)型,若request為aborted則返回false,若request非HttpEntityEnclosingRequest則表示冪等請(qǐng)求,返回true,若請(qǐng)求未完全發(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實(shí)現(xiàn)了ClientExecChain接口,其execute方法會(huì)循環(huán)執(zhí)行requestExecutor.execute,它c(diǎn)atch了IOException,對(duì)于retryHandler.retryRequest(ex, execCount, context)返回true的,會(huì)在通過(guò)RequestEntityProxy.isRepeatable(request)判斷一下是否是可重復(fù)讀取的request,不是則拋出NonRepeatableRequestException;對(duì)于retryHandler.retryRequest返回false的則針對(duì)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方法,它的子類(lèi)有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ù)讀取,對(duì)于非HttpEntityEnclosingRequest的返回true,是HttpEntityEnclosingRequest類(lèi)型的話則判斷entity.isRepeatable(),若entity是RequestEntityProxy類(lèi)型的,則通過(guò)RequestEntityProxy.isConsumed來(lái)判斷
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ě)多次。其實(shí)現(xiàn)類(lèi)里頭,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity為true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity為false
小結(jié)
HttpRequestRetryHandler接口定義了retryRequest方法,它接收IOException、executionCount及context,然后判斷是否可以重試
DefaultHttpRequestRetryHandler實(shí)現(xiàn)了HttpRequestRetryHandler接口,其無(wú)參構(gòu)造器默認(rèn)將InterruptedIOException、UnknownHostException、ConnectException、SSLException設(shè)定為不重試的異常,默認(rèn)retryCount為3,requestSentRetryEnabled為false;其retryRequest方法先判斷executionCount是否超出retryCount,接著判斷異常類(lèi)型是否是不重試的異常類(lèi)型,若request為aborted則返回false,若request非HttpEntityEnclosingRequest則表示冪等請(qǐng)求,返回true,若請(qǐng)求未完全發(fā)送則返回true,其余的默認(rèn)返回false。
RetryExec實(shí)現(xiàn)了ClientExecChain接口,其execute方法會(huì)循環(huán)執(zhí)行requestExecutor.execute,它c(diǎn)atch了IOException,對(duì)于retryHandler.retryRequest(ex, execCount, context)返回true的,會(huì)在通過(guò)RequestEntityProxy.isRepeatable(request)判斷一下是否是可重復(fù)讀取的request,不是則拋出NonRepeatableRequestException
DefaultHttpRequestRetryHandler針對(duì)不是冪等請(qǐng)求的HttpEntityEnclosingRequest類(lèi)型(HttpPut、HttpPost、HttpPatch、HttpDelete),不會(huì)重試;若retryHandler.retryRequest返回可以重試,RetryExec還有一個(gè)repeatable的判斷,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity為true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity為false
以上就是提升網(wǎng)絡(luò)請(qǐng)求穩(wěn)定性HttpClient的重試機(jī)制深入理解的詳細(xì)內(nèi)容,更多關(guān)于HttpClient重試機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 解讀httpclient的validateAfterInactivity連接池狀態(tài)檢測(cè)
- httpclient的disableConnectionState方法工作流程
- 探索HttpClient中的close方法及其對(duì)連接的影響
- HttpClient的RedirectStrategy重定向處理核心機(jī)制
- HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究
- HttpClient HttpRoutePlanner接口確定請(qǐng)求目標(biāo)路由
- 使用Backoff策略提高HttpClient連接管理的效率
- httpclient getPoolEntryBlocking連接池方法源碼解讀
相關(guān)文章
詳解SpringBoot開(kāi)發(fā)案例之整合定時(shí)任務(wù)(Scheduled)
本篇文章主要介紹了詳解SpringBoot開(kāi)發(fā)案例之整合定時(shí)任務(wù)(Scheduled),具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07使用Java代碼進(jìn)行因數(shù)分解和求最小公倍數(shù)的示例
這篇文章主要介紹了使用Java代碼進(jìn)行因數(shù)分解和求最小公倍數(shù)的示例,都是基于最基礎(chǔ)的算法原理實(shí)現(xiàn),需要的朋友可以參考下2015-11-11Spring Boot與前端配合與Idea配置部署操作過(guò)程
這篇文章主要介紹了Spring Boot與前端配合與Idea配置部署的操作過(guò)程,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-02-02Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法,涉及Java針對(duì)MAC的遍歷獲取與字符串轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Springboot以Repository方式整合Redis的方法
這篇文章主要介紹了Springboot以Repository方式整合Redis的方法,本文通過(guò)圖文并茂實(shí)例詳解給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04簡(jiǎn)單了解JAVA內(nèi)存區(qū)域效果知識(shí)
這篇文章主要介紹了簡(jiǎn)單了解JAVA內(nèi)存區(qū)域效果知識(shí),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10