HttpComponents HttpClient连接池(7)-重试

上一篇文章里我们介绍了 httpclient 连接池中空闲连接的清理,在这里我们主要介绍 http 连接的重试机制。

http连接的重试

httpclient 连接池也支持请求的重试,即在请求失败的情况下进行重试,对于重试设计以下几个关键点。

  1. 如何开启重试

  2. 如何定义重试次数

  3. 如何进行重试

如何开启重试

在 httpclient 连接池中,连接发送请求的重试是由 HttpRequestRetryHandler 类型的对象来处理,在HttpClientBuilder 有方法 disableAutomaticRetries() 来关闭重试,默认情况下重试是开启的,所以如果希望禁止重试那么就调用此方法。然后对于HttpClientBuilder 在构建 httpclient 的时候会根据这设置来确定 HttpRequestRetryHandler ,核心代码如下:

if (!automaticRetriesDisabled) {
    HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
        retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
    }
    execChain = new RetryExec(execChain, retryHandlerCopy);
}
  • httpclient 默认是开启重试机制的,如果希望关闭重试,则在构造中调用 HttpClientBuilder 的 disableAutomaticRetries() 方法。

  • 构造过程中如果开启了重试,那么则设置对象实例 HttpRequestRetryHandler 来负责重试。

  • HttpRequestRetryHandler 可以通过 Builder 对象来在外部设置。

  • 如果没有设置 HttpRequestRetryHandler 实例, 那么默认的为 DefaultHttpRequestRetryHandler 类型。

如何定义重试次数

如果使用默认重试机制,那么重试次数定义在 DefaultHttpRequestRetryHandler 对象实例中,其核心代码如下:

public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();


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);
    }
}
  • 在 DefaultHttpRequestRetryHandler 对象的默认构造函数中重试次数为 3。

  • 同时定义发生什么类型的 Exception 不会进行重试,在默认构造函数中所定义的这些异常类型为InterruptedIOException/UnknownHostException/ConnectException/SSLExcetion 。

如何进行重试

重试的过程定义在 RetryExec 对象实例的 execute() 方法里,其核心代码为:

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;
            }
        }
}
  • 根据设置的重试次数进行循环发 http 请求,如果出现 IOException 则进行重试尝试。

  • 如果请求 abort ,则取消重试。

  • 对于请求 abort 的定义, 是指调了以前文章介绍的 ManagedHttpClientConnection 类型对象的shutdown 方法,绕过 TCP4 次握手关闭 socket 连接,直接设置 linger 通过发送 RST(reset) 包来快速关闭连接,核心代码如下:

public void shutdown() throws IOException {
    final Socket socket = this.socketHolder.getAndSet(null);
    if (socket != null) {
        // force abortive close (RST)
        try {
            socket.setSoLinger(true, 0);
        } catch (final IOException ex) {
        // empty here  
        } 
        finally {
            socket.close();
        }
    }
}
  • 如果 retryHandler 决定重试,那么就进行下一次重试,然后重试次数减 1。

  • 对于 DefaultHttpRequestRetryHandler 对象实例决定是否重试逻辑如下:

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;
}
  • 如何到达重试次数则不重试。

  • 如果请求 abort 则不重试。

  • 如果发生的异常在 retryHanlder 不进行重试的异常名单里或者是名单里的实例,则不重试。在默认构造函数之中所定义的这些异常类型为InterruptedIOException/UnknownHostException/ConnectException/SSLExcetion 。

  • 如果是 idempotent 幂等类型请求则重试, get 为幂等类型请求。

  • 如果是请求没有发出去或者 retryHandler 的 requestSentRetryEnabled 属性为 true 则重试,该值默认为 false 。

  • 除以上情况外的其他情况均不重试。

目前先写到这里,在下一篇文章里我们开始介绍 httpclient 连接池对于 ssl 支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值