Ribbon、Feign、Hystrix和Zuul超时重试设置(一)

刚学Spring Cloud时,被里面的各种组件的超时搞得晕头转向的,所以趁着这段不忙的时间,查找了不少的资料,测试了一些代码,好好总结一下,避免以后忘记掉。

这里Spring Cloud的版本为Greenwich.SR1,代码都是最简单的写法,主要是为了验证各种超时配置。

Ribbon

刚学Spring Cloud,我是通过网上的各种博客教程学习,跟着示例敲代码,下面就是一个ribbon的示例代码:
eureka-client服务:

@RequestMapping("/test")
public String test() {
    int sleepTime = new Random().nextInt(4000);
    System.out.println("sleepTime: " + sleepTime);
    try {
        Thread.sleep(sleepTime); //模拟网络延迟
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "test";
}

eureka-client就是一个简单的服务,提供一个test的接口供ribbon远程调用。
service-ribbon服务:
启动类:

@SpringBootApplication
public class RibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

controller:

@RestController
public class TestController {
	
	private final TestService testService;

	@Autowired
	public TestController(TestService testService) {
		this.testService = testService;
	}

	@GetMapping("/test")
	public String test() {
	    return testService.test();
	}
}

service:

@Service
public class TestService {

	public final RestTemplate restTemplate;

	@Autowired
	public TestService(RestTemplate restTemplate) {
		this.restTemplate = restTemplate;
	}

	public String test() {
		return restTemplate.getForObject("http://eureka-client/test", String.class);
	}
}

代码很简单,也没其他的配置,当然一些基本的配置比如注册到eureka server等配置还有依赖我这里就不列出来了。
跑一下:

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第1张图片
可以看到正常返回信息了,但我发现模拟的网络延迟3秒多了还是能正常返回,好奇之下我试了下10秒延迟:

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第2张图片
什么情况,10秒都能正常返回,难道默认没有超时吗?带着这个疑虑,我在service-ribbon服务的yml配置文件中给ribbon设置了一个超时:

ribbon:
    ReadTimeout: 2000
    ConnectionTimeout: 2000

重试了一下发现10秒还是能正常返回,不会超时,嗯?怎么回事?上网搜ribbon超时发现都是这样配置的,难道是新版本改了?我看了下文档好像也不是这么一回事(虽然英语不好)。我又重新上网查找资料,最后发现RestTemplate使用默认构造函数时是使用SimpleClientHttpRequestFactory来创建http请求的,通过debug也可知:

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第3张图片
而查看SimpleClientHttpRequestFactory源码可知SimpleClientHttpRequestFactory默认是没有超时的:

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {

	private static final int DEFAULT_CHUNK_SIZE = 4096;


	@Nullable
	private Proxy proxy;

	private boolean bufferRequestBody = true;

	private int chunkSize = DEFAULT_CHUNK_SIZE;

	private int connectTimeout = -1;

	private int readTimeout = -1;

	private boolean outputStreaming = true;

	@Nullable
	private AsyncListenableTaskExecutor taskExecutor;
	...
}

这也解释了为什么上面延迟10秒都不会超时。而要想设置超时,就需要对SimpleClientHttpRequestFactory实例设置超时时间并注入到RestTemplate中:

@SpringBootApplication
public class RibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(2000);
        requestFactory.setReadTimeout(2000);
        return new RestTemplate(requestFactory);
    }
}

此时,如果超时,就会报错:

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第4张图片
如果想使ribbon.ConnectTimeout ,ribbon.ReadTimeout这两个参数配置生效,则需要多一个配置:

ribbon:
  ReadTimeout: 2000
  ConnectionTimeout: 2000
  http:
    client:
      enabled: true

把ribbon.http.client.enabled设置为true,就会使用RibbonClientHttpRequestFactory,此时ribbon.ConnectTimeout ,ribbon.ReadTimeout 这两个参数配置才会生效

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第5张图片
而如果不配超时时间,默认超时时间是1秒,超时后默认会重试一次。

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第6张图片
可以通过以下配置来设置重试次数:

ribbon:
  ReadTimeout: 2000
  ConnectionTimeout: 2000
  OkToRetryOnAllOperations: true
  MaxAutoRetriesNextServer: 1 # 当前实例全部失败后可以换1个实例再重试
  MaxAutoRetries: 2 # 在当前实例只重试2次
  http:
    client:
      enabled: true

以上配置最多会调用6次((MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)),也就是最多会重试5次。

Feign

eureka-client还是上面的例子没变化,主要看service-feign服务:
controller:

@RestController
public class TestController {
	
	private final TestFeign testFeign;

	@Autowired
	public TestController(TestFeign testFeign) {
		this.testFeign = testFeign;
	}

	@GetMapping("/test")
	public String test() {
	    return testFeign.test();
	}
}

feign接口:

@FeignClient("eureka-client")
public interface TestFeign {

	@RequestMapping("/test")
	String test();
}

配置文件还是基本的配置。
运行如下:

Ribbon、Feign、Hystrix和Zuul超时重试设置(一)_第7张图片
feign默认的超时时间是1秒,重试1次。在我查找资料的过程当中,发现一些博客是写feign默认重试5次(包括首次),但在我的测试中都不会出现重试5次的情况,几经搜查,在一篇大佬博客中找到答案:由于feign本身也具备重试能力,在早期的Spring Cloud中,Feign使用的是 feign.Retryer.Default#Default() ,而它默认重试5次。

public interface Retryer extends Cloneable {

  /**
   * if retry is permitted, return (possibly after sleeping). Otherwise propagate the exception.
   */
  void continueOrPropagate(RetryableException e);

  Retryer clone();

  public static class Default implements Retryer {

    private final int maxAttempts;
    private final long period;
    private final long maxPeriod;
    int attempt;
    long sleptForMillis;

    public Default() {
      this(100, SECONDS.toMillis(1), 5);
    }

    public Default(long period, long maxPeriod, int maxAttempts) {
      this.period = period;
      this.maxPeriod = maxPeriod;
      this.maxAttempts = maxAttempts;
      this.attempt = 1;
    }
    ...
}

但Feign整合了Ribbon,Ribbon也有重试的能力,此时,就可能会导致行为的混乱。所以C版本及以后的版本重试机制改为feign.Retryer#NEVER_RETRY,即关闭了feign自身的重试机制。

/**
   * Implementation that never retries request. It propagates the RetryableException.
   */
  Retryer NEVER_RETRY = new Retryer() {

    @Override
    public void continueOrPropagate(RetryableException e) {
      throw e;
    }

    @Override
    public Retryer clone() {
      return this;
    }
  };

如需使用重试,只需使用Ribbon的重试配置,超时设置也是配ribbon的,即

ribbon:
  ReadTimeout: 2000
  ConnectionTimeout: 2000
  OkToRetryOnAllOperations: true
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 2

feign的超时设置其实还有一层Hystrix的,这个留下一篇再讲。

demo地址

参考链接:
Spring Cloud各组件重试总结
feign 的重试机制

你可能感兴趣的:(Java,Spring,Cloud)