restTemplate请求重发的相关设置-通过配置

restTemplate请求重发的相关设置-通过配置

通过配置的方式

相关的pom文件需要引入:httpclient

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

根据响应的HTTP状态码重试

在RestTemplate中有一个服务不可用重试配置,可以通过httpClientBuilder.setServiceUnavailableRetryStrategy来进行相关配置。通过查看源码可以发现,此处需要一个ServiceUnavailableRetryStrategy的实现,该接口已有一个默认的DefaultServiceUnavailableRetryStrategy配置。

默认的配置中,构造器代码如下:

public DefaultServiceUnavailableRetryStrategy(int maxRetries, int retryInterval) {
    Args.positive(maxRetries, "Max retries");
    Args.positive(retryInterval, "Retry interval");
    this.maxRetries = maxRetries;
    this.retryInterval = (long)retryInterval;
}

public DefaultServiceUnavailableRetryStrategy() {
    this(1, 1000);
}

从默认的构造器源码中可以得到以下信息:

  • 可以设置两个参数,一个是重试次数,一个是重试时间间隔。
  • 默认的最大重试次数为1次,重试时间间隔为1s。

判断的相关代码如下:

public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
        return executionCount <= this.maxRetries && response.getStatusLine().getStatusCode() == 503;
    }
  • 可以看出就是一个简单的判断,判断执行次数是否小于最大重试次数且返回的状态码是否为503.
如何定制自己需要的配置
  • 次数和时间默认的配置已经给了构造方法。
  • 如果想根据不同的状态则可以通过自己写一个类实现ServiceUnavailableRetryStrategy
package com.doordiey.game.config;

import org.apache.http.HttpResponse;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;

/**
 * 根据返回的HTTP状态码决定是否要重试
 */
public class MyDefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {

    private final int maxRetries;
    private final long retryInterval;

    public MyDefaultServiceUnavailableRetryStrategy(int maxRetries, int retryInterval) {
        Args.positive(maxRetries, "Max retries");
        Args.positive(retryInterval, "Retry interval");
        this.maxRetries = maxRetries;
        this.retryInterval = (long)retryInterval;
    }

    public MyDefaultServiceUnavailableRetryStrategy() {
        this(1, 1000);
    }

    /**
     * 修改此处的判断
     * @param response
     * @param executionCount
     * @param context
     * @return
     */
    public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
        return executionCount <= this.maxRetries && (response.getStatusLine().getStatusCode() == 503||
                response.getStatusLine().getStatusCode() == 502)
                ;
    }

    public long getRetryInterval() {
        return this.retryInterval;
    }
}

配置类

package com.doordiey.game.config;

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;



@Configuration
public class RestTemplateConfig {
    private static Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(clientHttpRequestFactory());
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            httpClientBuilder.setSSLContext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
                    hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();// 注册http和https请求
            // 开始设置连接池
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
                    socketFactoryRegistry);
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            httpClientBuilder.setServiceUnavailableRetryStrategy(new MyDefaultServiceUnavailableRetryStrategy());
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
            HttpClient httpClient = httpClientBuilder.build();
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                    httpClient); // httpClient连接配置
            return clientHttpRequestFactory;
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            logger.error("初始化HTTP连接池出错", e);
        }
        return null;
    }

}

测试的接口

package com.doordiey.game.controller;


import com.doordiey.game.GameApplication;
import com.sun.corba.se.spi.ior.ObjectKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
public class GameController {
    public static Logger logger = LoggerFactory.getLogger(GameApplication.class);

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/play")
    @ResponseStatus(code=HttpStatus.SERVICE_UNAVAILABLE,reason="server error")
    public String play(){
        logger.info("play");
        return "ok";
    }

    @GetMapping("/test")
    public void test(){
        logger.info("test");
        RestTemplate restTemplate = new RestTemplate();
        String gg = restTemplate.getForObject("http://127.0.0.1:8086/play",String.class);
        logger.info(gg);
    }


}


请求错误重试

在RestTemplate中请求错误重试可以通过httpClientBuilder.setRetryHandler 来进行相关配置。通过查看源码可以发现,此处需要set一个HttpRequestRetryHandler的实现,已经提供了一个默认的DefaultHttpRequestRetryHandler

默认的配置中,构造器代码如下:

protected DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled, Collection<Class<? extends IOException>> clazzes) {
        this.retryCount = retryCount;
        this.requestSentRetryEnabled = requestSentRetryEnabled;
        this.nonRetriableClasses = new HashSet();
        Iterator i$ = clazzes.iterator();

        while(i$.hasNext()) {
            Class<? extends IOException> clazz = (Class)i$.next();
            this.nonRetriableClasses.add(clazz);
        }

    }

    public DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
        this(retryCount, requestSentRetryEnabled, Arrays.asList(InterruptedIOException.class, UnknownHostException.class, ConnectException.class, SSLException.class));
    }

    public DefaultHttpRequestRetryHandler() {
        this(3, false);
    }

从默认的构造器源码中可以得到以下几个信息:

  • 默认的重试次数为3次,且默认是不进行请求重发的。
  • 对请求报错中的InterruptedIOException.class, UnknownHostException.class, ConnectException.class, SSLException.class这四个错误类会特殊处理。

源码中主要进行是否重发的判断的是下面的代码:

public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
        Args.notNull(exception, "Exception parameter");
        Args.notNull(context, "HTTP context");
        if (executionCount > this.retryCount) {
            return false;
        } else if (this.nonRetriableClasses.contains(exception.getClass())) {
            return false;
        } else {
            Iterator i$ = this.nonRetriableClasses.iterator();

            Class rejectException;
            do {
                if (!i$.hasNext()) {
                    HttpClientContext clientContext = HttpClientContext.adapt(context);
                    HttpRequest request = clientContext.getRequest();
                    if (this.requestIsAborted(request)) {
                        return false;
                    }

                    if (this.handleAsIdempotent(request)) {
                        return true;
                    }

                    if (clientContext.isRequestSent() && !this.requestSentRetryEnabled) {
                        return false;
                    }

                    return true;
                }

                rejectException = (Class)i$.next();
            } while(!rejectException.isInstance(exception));

            return false;
        }
    }

从判断部分的源码可以得到以下信息:

  • 先校验重试次数,再校验是否是那几个特殊的报错类,再进行其他校验。
综合以上得到的信息
  • 可以通过设置重试次数,再非特殊报错的情况下,请求进行多次重试。

版权声明:本文为doordiev原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。