欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: kingszelda

一、配景

由于事情上的业务本人常常与第三方系统交互,所以常常会利用HttpClient与第三方举办通信。对付生意业务类的接口,订单状态是至关重要的。

这就牵扯到一系列问题:

HttpClient是否有默认的重试计策?重试计策道理?如何克制重试?

接下来,本文将从源码中探讨这些问题。源码下载地点:http://hc.apache.org/downloads.cgi,版本是4.5.5。

二、一般利用要领

一般而言,得到HttpClient实例的要领有两种:

1.HttpClients.custom().setXXX().build()
2.HttpClients.build()

第一种要领用来定制一些HttpClient的属性,好比https证书,署理处事器,http过滤器,毗连池打点器等自界说的用法。

第二种要领用来得到一个默认的HttpClient实例。

这两种要领得到都是CloseableHttpClient实例,昆山软件公司,且都是通过HttpClientBuilder的build()构建的。

三、有没有重试计策

可以看到,上面的两种用法最终都获得了一个InternalHttpClient,是抽象类CloseableHttpClient的一种实现。

public CloseableHttpClient build() {
        //省略若干行
        return new InternalHttpClient(
                execChain,
                connManagerCopy,
                routePlannerCopy,
                cookieSpecRegistryCopy,
                authSchemeRegistryCopy,
                defaultCookieStore,
                defaultCredentialsProvider,
                defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
                closeablesCopy);
    }

}

这里有许多设置化参数,这里我们重点存眷一下execChain这个执行链。

而execChain是通过Inte  <a href=昆山软件定制开拓 rnalHttpClient结构器传进来的" class="aligncenter size-full wp-image-28675" title="QQ20180425-154830@2x" src="http://incdn1.b0.upaiyun.com/2018/04/a1ed94f046fb8d532d3fd49fe18644db.png" />

可以看到执行链有多种实现,好比

  1. RedirectExec执行器的默认计策是,在吸收到重定向错误码301与307时会继承会见重定向的地点
  2. 以及我们存眷的RetryExec可以重试的执行器。

这么多执行器,是怎么用到了重试执行器呢?

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);
        }  
}

可以看到在build() httpclient实例的时候,判定了是否封锁了自动重试,这个automaticRetriesDisabled范例是boolean,昆山软件开发,默认值是false,所以if这里是满意的。

即假如没有指定执行链,就是用RetryExec执行器,默认的重试计策是DefaultHttpRequestRetryHandler。

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

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

最后一行可以看到,最终的执行execute方法利用的是exeChain的执行要领,而execChain是通过InternalHttpClient结构器传进来的,就是上面看到的RetryExec。

所以,HttpClient有默认的执行器RetryExec,其默认的重试计策是DefaultHttpRequestRetryHandler。

四、重试计策阐明

4.1 是否需要重试的判定在那边?

http请求是执行器执行的,所以先看RetryExec发送请求的部门。

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轮回记录了当前http请求的执行次数
        for (int execCount = 1;; execCount++) {
            try {
          //挪用基本executor执行http请求
                return this.requestExecutor.execute(route, request, context, execAware);
            } catch (final IOException ex) {
          //产生IO异常的时候,判定上下文是否已经间断,假如间断则抛异常退出
                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;
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }