最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Spring Cloud Zuul重試機(jī)制探秘

 更新時(shí)間:2017年11月18日 11:02:00   作者:翟永超  
本篇文章主要介紹了詳解Spring Cloud Zuul重試機(jī)制探秘,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

簡介

本文章對(duì)應(yīng)spring cloud的版本為(Dalston.SR4),具體內(nèi)容如下:

  1. 開啟Zuul功能
  2. 通過源碼了解Zuul的一次轉(zhuǎn)發(fā)
  3. 怎么開啟zuul的重試機(jī)制
  4. Edgware.RC1版本的優(yōu)化

開啟Zuul的功能

首先如何使用spring cloud zuul完成路由轉(zhuǎn)發(fā)的功能,這個(gè)問題很簡單,只需要進(jìn)行如下準(zhǔn)備工作即可:

  1. 注冊(cè)中心(Eureka Server)
  2. zuul(同時(shí)也是Eureka Client)
  3. 應(yīng)用服務(wù)(同時(shí)也是Eureka Client)

我們希望zuul和后端的應(yīng)用服務(wù)同時(shí)都注冊(cè)到Eureka Server上,當(dāng)我們?cè)L問Zuul的某一個(gè)地址時(shí),對(duì)應(yīng)其實(shí)訪問的是后端應(yīng)用的某個(gè)地址,從而從這個(gè)地址返回一段內(nèi)容,并展現(xiàn)到瀏覽器上。

注冊(cè)中心(Eureka Server)

創(chuàng)建一個(gè)Eureka Server只需要在主函數(shù)上添加@EnableEurekaServer,并在properties文件進(jìn)行簡單配置即可,具體內(nèi)容如下:

@EnableEurekaServer
@RestController
@SpringBootApplication
public class EurekaServerApplication {
  public static void main(String[] args) {
   SpringApplication.run(EurekaServerApplication.class, args);
  }
}
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Zuul

主函數(shù)添加@EnableZuulProxy注解(因?yàn)榧蒃ureka,需要另外添加@EnableDiscoveryClient注解)。并配置properties文件,具體內(nèi)容如下所示:

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulDemoApplication {
  /**
  * 省略代碼...
  */
}
server.port=8081
spring.application.name=ZUUL-CLIENT
zuul.routes.api-a.serviceId=EUREKA-CLIENT
zuul.routes.api-a.path=/api-a/**
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

應(yīng)用服務(wù)

@RestController
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientApplication {
  public static void main(String[] args) {
   SpringApplication.run(EurekaClientApplication.class, args);
  }
  @RequestMapping(value = "/hello")
  public String index() {
   return "hello spring...";
  }
}
 
spring.application.name=EUREKA-CLIENT
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

三個(gè)工程全部啟動(dòng),這時(shí)當(dāng)我們?cè)L問localhost:8081/api-a/hello時(shí),你會(huì)看到瀏覽器輸出的內(nèi)容是hello spring...

通過源碼了解Zuul的一次轉(zhuǎn)發(fā)

接下來我們通過源碼層面來了解下,一次轉(zhuǎn)發(fā)內(nèi)部都做了哪些事情。

首先我們查看Zuul的配置類ZuulProxyAutoConfiguration在這個(gè)類中有一項(xiàng)工作是初始化Zuul默認(rèn)自帶的Filter,其中有一個(gè)Filter很重要,它就是RibbonRoutingFilter。它主要是完成請(qǐng)求的路由轉(zhuǎn)發(fā)。接下來我們看下他的run方法

@Override
public Object run() {
  RequestContext context = RequestContext.getCurrentContext();
  try {
   RibbonCommandContext commandContext = buildCommandContext(context);
   ClientHttpResponse response = forward(commandContext);
   setResponse(response);
   return response;
  }
  catch (ZuulException ex) {
   throw new ZuulRuntimeException(ex);
  }
  catch (Exception ex) {
   throw new ZuulRuntimeException(ex);
  }
}

可以看到進(jìn)行轉(zhuǎn)發(fā)的方法是forward,我們進(jìn)一步查看這個(gè)方法,具體內(nèi)容如下:

省略部分代碼

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
  RibbonCommand command = this.ribbonCommandFactory.create(context);
  try {
   ClientHttpResponse response = command.execute();
   return response;
  }
  catch (HystrixRuntimeException ex) {
   return handleException(info, ex);
  }
}

ribbonCommandFactory指的是HttpClientRibbonCommandFactory這個(gè)類是在RibbonCommandFactoryConfiguration完成初始化的(觸發(fā)RibbonCommandFactoryConfiguration的加載動(dòng)作是利用ZuulProxyAutoConfiguration類上面的@Import標(biāo)簽),具體代碼如下:

@Configuration
@ConditionalOnRibbonHttpClient
protected static class HttpClientRibbonConfiguration {
  @Autowired(required = false)
  private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
  @Bean
  @ConditionalOnMissingBean
  public RibbonCommandFactory<?> ribbonCommandFactory(
     SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
   return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties, zuulFallbackProviders);
  }
}

知道了這個(gè)ribbonCommandFactory具體的實(shí)現(xiàn)類(HttpClientRibbonCommandFactory),接下來我們看看它的create方法具體做了那些事情

@Override
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
  ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
  final String serviceId = context.getServiceId();
  final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
     serviceId, RibbonLoadBalancingHttpClient.class);
  client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
  return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider,
     clientFactory.getClientConfig(serviceId));
}

這個(gè)方法按照我的理解主要做了以下幾件事情:

@Override
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
  /**
  *獲取所有ZuulFallbackProvider,即當(dāng)Zuul
  *調(diào)用失敗后的降級(jí)方法
  */
  ZuulFallbackProvider = xxxxx
  /**
  *創(chuàng)建處理請(qǐng)求轉(zhuǎn)發(fā)類,該類會(huì)利用
  *Apache的Http client進(jìn)行請(qǐng)求的轉(zhuǎn)發(fā)
  */
  RibbonLoadBalancingHttpClient = xxxxx
  
  /**
  *將降級(jí)方法、處理請(qǐng)求轉(zhuǎn)發(fā)類、以及其他一些內(nèi)容
  *包裝成HttpClientRibbonCommand(這個(gè)類繼承了HystrixCommand)
  */
  return new HttpClientRibbonCommand(xxxxx);
}

到這里我們很清楚的知道了RibbonRoutingFilter類的forward方法中RibbonCommand command = this.ribbonCommandFactory.create(context);這一行代碼都做了哪些內(nèi)容.

接下來調(diào)用的是command.execute();方法,通過剛剛的分析我們知道了command其實(shí)指的是HttpClientRibbonCommand,同時(shí)我們也知道HttpClientRibbonCommand繼承了HystrixCommand所以當(dāng)執(zhí)行command.execute();時(shí)其實(shí)執(zhí)行的是HttpClientRibbonCommand的run方法。查看源碼我們并沒有發(fā)現(xiàn)run方法,但是我們發(fā)現(xiàn)HttpClientRibbonCommand直接繼承了AbstractRibbonCommand。所以其實(shí)執(zhí)行的是AbstractRibbonCommand的run方法,接下來我們看看run方法里面都做了哪些事情:

@Override
protected ClientHttpResponse run() throws Exception {
  final RequestContext context = RequestContext.getCurrentContext();
  RQ request = createRequest();
  RS response = this.client.executeWithLoadBalancer(request, config);
  context.set("ribbonResponse", response);
  if (this.isResponseTimedOut()) {
   if (response != null) {
     response.close();
   }
  }
  return new RibbonHttpResponse(response);
}

可以看到在run方法中會(huì)調(diào)用client的executeWithLoadBalancer方法,通過上面介紹我們知道client指的是RibbonLoadBalancingHttpClient,而RibbonLoadBalancingHttpClient里面并沒有executeWithLoadBalancer方法。(這里面會(huì)最終調(diào)用它的父類AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法。)

具體代碼如下:

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
 /**
 * 創(chuàng)建一個(gè)RetryHandler,這個(gè)很重要它是用來
 * 決定利用RxJava的Observable是否進(jìn)行重試的標(biāo)準(zhǔn)。
 */
  RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
  /**
   * 創(chuàng)建一個(gè)LoadBalancerCommand,這個(gè)類用來創(chuàng)建Observable
   * 以及根據(jù)RetryHandler來判斷是否進(jìn)行重試操作。
   */
  LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
      .withLoadBalancerContext(this)
      .withRetryHandler(handler)
      .withLoadBalancerURI(request.getUri())
      .build();
  try {
   /**
   *command.submit()方法主要是創(chuàng)建了一個(gè)Observable(RxJava)
   *并且為這個(gè)Observable設(shè)置了重試次數(shù),這個(gè)Observable最終
   *會(huì)回調(diào)AbstractLoadBalancerAwareClient.this.execute()
   *方法。
   */
    return command.submit(
      new ServerOperation<T>() {
        @Override
        public Observable<T> call(Server server) {
          URI finalUri = reconstructURIWithServer(server, request.getUri());
          S requestForServer = (S) request.replaceUri(finalUri);
          try {
            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
          } 
          catch (Exception e) {
            return Observable.error(e);
          }
        }
      })
      .toBlocking()
      .single();
  } catch (Exception e) {
    Throwable t = e.getCause();
    if (t instanceof ClientException) {
      throw (ClientException) t;
    } else {
      throw new ClientException(e);
    }
  }
  
}

下面針對(duì)于每一塊內(nèi)容做詳細(xì)說明:

首先getRequestSpecificRetryHandler(request, requestConfig);這個(gè)方法其實(shí)調(diào)用的是RibbonLoadBalancingHttpClient的getRequestSpecificRetryHandler方法,這個(gè)方法主要是返回一個(gè)RequestSpecificRetryHandler

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
 /**
 *這個(gè)很關(guān)鍵,請(qǐng)注意該類構(gòu)造器中的前兩個(gè)參數(shù)的值
 *正因?yàn)橐婚_始我也忽略了這兩個(gè)值,所以后續(xù)給我造
 *成一定的干擾。
 */
  return new RequestSpecificRetryHandler(false, false,
     RetryHandler.DEFAULT, requestConfig);
}

接下來創(chuàng)建LoadBalancerCommand并將上一步獲得的RequestSpecificRetryHandler作為參數(shù)內(nèi)容。

最后調(diào)用LoadBalancerCommand的submit方法。該方法內(nèi)容太長具體代碼細(xì)節(jié)就不在這里貼出了,按照我個(gè)人的理解,只貼出相應(yīng)的偽代碼:

public Observable<T> submit(final ServerOperation<T> operation) {
 //相同server的重試次數(shù)(去除首次請(qǐng)求)
  final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
  //集群內(nèi)其他Server的重試個(gè)數(shù)
  final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
  /**
   *創(chuàng)建一個(gè)Observable(RxJava),selectServer()方法是
   *利用Ribbon選擇一個(gè)Server,并將其包裝成Observable
   */
  Observable<T> o = selectServer().concatMap(new Func1<Server, Observable<T>>() { 
    @Override
    public Observable<T> call(final Server server) {
     /**
     *這里會(huì)回調(diào)submit方法入?yún)erverOperation類的call方法,
     */
   return operation.call(server).doOnEach(new Observer<T>() {}
 }
  }
  if (maxRetrysSame > 0) 
   o = o.retry(retryPolicy(maxRetrysSame, true));
    
  if (maxRetrysNext > 0 && server == null) 
    o = o.retry(retryPolicy(maxRetrysNext, false));
  
  return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
    @Override
    public Observable<T> call(Throwable e) {
    /**
    *轉(zhuǎn)發(fā)請(qǐng)求失敗時(shí),會(huì)進(jìn)入此方法。通過此方法進(jìn)行判斷
    *是否超過重試次數(shù)maxRetrysSame、maxRetrysNext。
    */
   }
  });
}

operation.call()方法最終會(huì)調(diào)用RibbonLoadBalancingHttpClient的execute方法,該方法內(nèi)容如下:

@Override
public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request,
   final IClientConfig configOverride) throws Exception {
  /**
  * 組裝參數(shù)(RequestConfig)
  */
  final RequestConfig.Builder builder = RequestConfig.custom();
  IClientConfig config = configOverride != null ? configOverride : this.config;
  builder.setConnectTimeout(config.get(
     CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
  builder.setSocketTimeout(config.get(
     CommonClientConfigKey.ReadTimeout, this.readTimeout));
  builder.setRedirectsEnabled(config.get(
     CommonClientConfigKey.FollowRedirects, this.followRedirects));
  final RequestConfig requestConfig = builder.build();
  if (isSecure(configOverride)) {
   final URI secureUri = UriComponentsBuilder.fromUri(request.getUri())
      .scheme("https").build().toUri();
   request = request.withNewUri(secureUri);
  }
  final HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
  /**
  * 發(fā)送轉(zhuǎn)發(fā)請(qǐng)求
  */
  final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);
  /**
  * 返回結(jié)果
  */
  return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
}

可以看到上面方法主要做的就是組裝請(qǐng)求參數(shù)(包括各種超時(shí)時(shí)間),然后發(fā)起轉(zhuǎn)發(fā)請(qǐng)求,最終獲取相應(yīng)結(jié)果。

說到這里,zuul轉(zhuǎn)發(fā)一次請(qǐng)求的基本原理就說完了。讓我們?cè)倩仡櫹抡麄€(gè)流程。

  1. zuul的轉(zhuǎn)發(fā)是通過RibbonRoutingFilter這個(gè)Filter進(jìn)行操作的。
  2. 在轉(zhuǎn)發(fā)之前,zuul利用Hystrix將此次轉(zhuǎn)發(fā)請(qǐng)求包裝成一個(gè)HystrixCommand,正應(yīng)為這樣才使得zuul具有了降級(jí)(Fallback)的功能,同時(shí)HystrixCommand是具備超時(shí)時(shí)間的(默認(rèn)是1s)。而且Zuul默認(rèn)采用的隔離級(jí)別是信號(hào)量模式。
  3. 在HystrixCommand內(nèi)部zuul再次將請(qǐng)求包裝成一個(gè)Observable,(有關(guān)RxJava的知識(shí)請(qǐng)參照其官方文檔)。并且為Observable設(shè)置了重試次數(shù)。

事實(shí)真的是這樣嗎?當(dāng)我看到源碼中為Observable設(shè)置重試次數(shù)的時(shí)候,我以為這就是zuul的重試邏輯。遺憾的是我的想法是錯(cuò)誤的。還記得上面我說的getRequestSpecificRetryHandler(request, requestConfig);這個(gè)方法嗎?(不記得的同學(xué)可以回過頭來再看下),這個(gè)方法返回的是RequestSpecificRetryHandler這個(gè)類,而且在創(chuàng)建該類時(shí),構(gòu)造器的前兩個(gè)參數(shù)都為false。(這一點(diǎn)非常重要)。這兩個(gè)參數(shù)分別是okToRetryOnConnectErrors和okToRetryOnAllErrors。

我原本的想法是這個(gè)請(qǐng)求被包裝成Observable,如果這次請(qǐng)求因?yàn)槌瑫r(shí)出現(xiàn)異?;蛘咂渌惓?,這樣就會(huì)觸發(fā)Observable的重試機(jī)制(RxJava),但是事實(shí)并非如此,為什么呢?原因就是上面的那兩個(gè)參數(shù),當(dāng)出現(xiàn)了超時(shí)異常的時(shí)候,在觸發(fā)重試機(jī)制之前會(huì)調(diào)用RequestSpecificRetryHandler的isRetriableException()方法,該方法的作用是用來判斷是否執(zhí)行重試動(dòng)作,具體代碼如下:

@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
  //此時(shí)該值為false
  if (okToRetryOnAllErrors) {
    return true;
  } 
  else if (e instanceof ClientException) {
    ClientException ce = (ClientException) e;
    if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
      return !sameServer;
    } else {
      return false;
    }
  } 
  else {
    //此時(shí)該值為false
    return okToRetryOnConnectErrors && isConnectionException(e);
  }
}

說道這里zuul轉(zhuǎn)發(fā)一次請(qǐng)求的基本原理大概了解了,同時(shí)也驗(yàn)證了一個(gè)事實(shí)就是實(shí)現(xiàn)zuul進(jìn)行重試的邏輯并不是Observable的重試機(jī)制。那么問題來了?是什么使zuul具有重試功能的呢?

怎么開啟zuul的重試機(jī)制

開啟Zuul重試的功能在原有的配置基礎(chǔ)上需要額外進(jìn)行以下設(shè)置:

  1. 在pom中添加spring-retry的依賴(maven工程)
  2. 設(shè)置zuul.retryable=true(該參數(shù)默認(rèn)為false)

具體properties文件內(nèi)容如下:

server.port=8081
spring.application.name=ZUUL-CLIENT
#路由信息
zuul.routes.api-a.serviceId=EUREKA-CLIENT
zuul.routes.api-a.path=/api-a/**
#是否開啟重試功能
zuul.retryable=true
#同一個(gè)Server重試的次數(shù)(除去首次)
ribbon.MaxAutoRetries=3
#切換相同Server的次數(shù)
ribbon.MaxAutoRetriesNextServer=0
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

為了模擬出Zuul重試的功能,需要對(duì)后端應(yīng)用服務(wù)進(jìn)行改造,改造后的內(nèi)容如下:

@RequestMapping(value = "/hello")
public String index() {
  System.out.println("request is coming...");
  try {
   Thread.sleep(100000);
  } catch (InterruptedException e) {
   System.out.println("線程被打斷... " + e.getMessage());
  }
  return "hello spring ...";
}

通過使用Thread.sleep(100000)達(dá)到Zuul轉(zhuǎn)發(fā)超時(shí)情況(Zuul默認(rèn)連接超時(shí)未2s、read超時(shí)時(shí)間為5s),從而觸發(fā)Zuul的重試功能。這時(shí)候在此訪問localhost:8081/api-a/hello時(shí),查看應(yīng)用服務(wù)后臺(tái),會(huì)發(fā)現(xiàn)最終打印三次"request is coming..."

通過現(xiàn)象看本質(zhì),接下來簡單介紹下Zuul重試的原理。首先如果你工程classpath中存在spring-retry,那么zuul在初始化的時(shí)候就不會(huì)創(chuàng)建RibbonLoadBalancingHttpClient而是創(chuàng)建RetryableRibbonLoadBalancingHttpClient具體源代碼如下:

@ConditionalOnClass(name = "org.apache.http.client.HttpClient")
@ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
public class HttpClientRibbonConfiguration {
  @Value("${ribbon.client.name}")
  private String name = "client";
  @Bean
  @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
  public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
     IClientConfig config, ServerIntrospector serverIntrospector,
     ILoadBalancer loadBalancer, RetryHandler retryHandler) {
   RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(
      config, serverIntrospector);
   client.setLoadBalancer(loadBalancer);
   client.setRetryHandler(retryHandler);
   Monitors.registerObject("Client_" + this.name, client);
   return client;
  }
  @Bean
  @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
  @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
  public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
     IClientConfig config, ServerIntrospector serverIntrospector,
     ILoadBalancer loadBalancer, RetryHandler retryHandler,
     LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
   RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
      config, serverIntrospector, loadBalancedRetryPolicyFactory);
   client.setLoadBalancer(loadBalancer);
   client.setRetryHandler(retryHandler);
   Monitors.registerObject("Client_" + this.name, client);
   return client;
  }
}

所以請(qǐng)求到來需要轉(zhuǎn)發(fā)的時(shí)候(AbstractLoadBalancerAwareClient類中executeWithLoadBalancer方法會(huì)調(diào)用AbstractLoadBalancerAwareClient.this.execute())其實(shí)調(diào)用的是RetryableRibbonLoadBalancingHttpClient的execute方法(而不是沒有重試時(shí)候RibbonLoadBalancingHttpClient的execute方法),源碼內(nèi)容如下:

@Override
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
  final RequestConfig.Builder builder = RequestConfig.custom();
  IClientConfig config = configOverride != null ? configOverride : this.config;
  builder.setConnectTimeout(config.get(
     CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
  builder.setSocketTimeout(config.get(
     CommonClientConfigKey.ReadTimeout, this.readTimeout));
  builder.setRedirectsEnabled(config.get(
     CommonClientConfigKey.FollowRedirects, this.followRedirects));
  final RequestConfig requestConfig = builder.build();
  final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
  RetryCallback retryCallback = new RetryCallback() {
   @Override
   public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception {
     //on retries the policy will choose the server and set it in the context
     //extract the server and update the request being made
     RibbonApacheHttpRequest newRequest = request;
     if(context instanceof LoadBalancedRetryContext) {
      ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance();
      if(service != null) {
        //Reconstruct the request URI using the host and port set in the retry context
        newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(),
           newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(),
           newRequest.getURI().getPath(), newRequest.getURI().getQuery(),
           newRequest.getURI().getFragment()));
      }
     }
     if (isSecure(configOverride)) {
      final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri())
         .scheme("https").build().toUri();
      newRequest = newRequest.withNewUri(secureUri);
     }
     HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig);
     final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
     if(retryPolicy.retryableStatusCode(httpResponse.getStatusLine().getStatusCode())) {
      if(CloseableHttpResponse.class.isInstance(httpResponse)) {
        ((CloseableHttpResponse)httpResponse).close();
      }
      throw new RetryableStatusCodeException(RetryableRibbonLoadBalancingHttpClient.this.clientName,
         httpResponse.getStatusLine().getStatusCode());
     }
     return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
   }
  };
  return this.executeWithRetry(request, retryPolicy, retryCallback);
}

executeWithRetry方法內(nèi)容如下:

private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy, RetryCallback<RibbonApacheHttpResponse, IOException> callback) throws Exception {
  RetryTemplate retryTemplate = new RetryTemplate();
  boolean retryable = request.getContext() == null ? true :
     BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
  retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
     : new RetryPolicy(request, retryPolicy, this, this.getClientName()));
  return retryTemplate.execute(callback);
}

按照我的理解,主要邏輯如下:

@Override
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
  /**
  *創(chuàng)建RequestConfig(請(qǐng)求信息)
  */
  final RequestConfig requestConfig = builder.build();
  final LoadBalancedRetryPolicy retryPolicy =       loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
  /**
  * 創(chuàng)建RetryCallbck的實(shí)現(xiàn)類,用來完成重試邏輯
  */
  RetryCallback retryCallback = new RetryCallback() {};
  
  //創(chuàng)建Spring-retry的模板類,RetryTemplate。
  RetryTemplate retryTemplate = new RetryTemplate();
 /**
 *設(shè)置重試規(guī)則,即在什么情況下進(jìn)行重試
 *什么情況下停止重試。源碼中這部分存在
 *一個(gè)判斷,判斷的根據(jù)就是在zuul工程
 *的propertris中配置的zuul.retryable
 *該參數(shù)內(nèi)容為true才可以具有重試功能。
 */
 retryTemplate.setRetryPolicy(xxx);
 /**
 *發(fā)起請(qǐng)求
 */
 return retryTemplate.execute(callback);
}

到此為止我們不僅知道了zuul路由一次請(qǐng)求的整體過程,也明確了zuul因后端超時(shí)而觸發(fā)重試的原理??墒撬坪踹€存在著一個(gè)問題,就是超時(shí)問題。前面說過zuul把路由請(qǐng)求這個(gè)過程包裝成一個(gè)HystrixCommnd,而在我的propertries文件中并沒有設(shè)置Hystrix的超時(shí)時(shí)間(默認(rèn)時(shí)間為1s),而read的超時(shí)時(shí)間是5s(前面源碼部分介紹過)。這里就會(huì)有人問,因?yàn)樽钔鈱邮遣捎肏ystrix,而Hystrix此時(shí)已經(jīng)超時(shí)了,為什么還允許它內(nèi)部繼續(xù)使用spring-retry進(jìn)行重試呢?帶著這個(gè)問題我查看了官方GitHub上的issues,發(fā)現(xiàn)有人對(duì)此問題提出過疑問。作者給出的回復(fù)是Hystrix超時(shí)的時(shí)候并不會(huì)打斷內(nèi)部重試的操作。

其實(shí)說實(shí)話這塊內(nèi)容我并不是很理解(可能是因?yàn)镠ystrix源碼了解較少),帶著這個(gè)問題我給作者發(fā)了一封郵件,郵件對(duì)話內(nèi)容如下:

我的(英語水平不好,大家見諒):

I want to confirm two issues with you, First of all zuul retry only spring-retry exists and zuul.retry this parameter is true to take effect? The second problem is that if my classpath spring-retry at the same time I let zuul.retry this parameter is true, which means that at this time zuul have a retry mechanism, then why when Hystrix time-out can not interrupt the spring- retry it. thank you very much

作者的回復(fù)(重點(diǎn)):

Zuul will retry failed requests IF Spring Retry is on the classpath and the property zuul.retryable is set to true. The retry is happening within the hystrix command, so if hystrix times out than a response is returned. Right now there is no mechanism to stop further retries from happening if hystrix times out before all the retries are exhauted.
On Thu, Nov 16, 2017 at 8:40 AM 李剛 spring_holy@163.com wrote:

雖然得到了作者的確認(rèn),但是這部分內(nèi)容始終還是沒有完全理解,后續(xù)還要看看Hystrix的源碼。

Edgware.RC1版本的優(yōu)化

在Edgware.RC1版本中,作者修改了代碼并不使用Ribbon的默認(rèn)值而是將ConnectTimeout以及ReadTimeout都賦值為1S),,同時(shí)調(diào)整Hystrix的超時(shí)時(shí)間,時(shí)間為(2S).具體信息內(nèi)容如下:
https://github.com/spring-cloud/spring-cloud-netflix/pull/2261

同時(shí)作者也闡明了利用Hystrix包裝使用Ribbon時(shí)關(guān)于超時(shí)時(shí)間的設(shè)置規(guī)則(以下內(nèi)容來自GitHub):

When using Hystrix commands that wrap Ribbon clients you want to make sure your Hystrix timeout is configured to be longer than the configured Ribbon timeout, including any potential
retries that might be made. For example, if your Ribbon connection timeout is one second and
the Ribbon client might retry the request three times, than your Hystrix timeout should
be slightly more than three seconds.

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家

相關(guān)文章

  • Java創(chuàng)建和啟動(dòng)線程的兩種方式實(shí)例分析

    Java創(chuàng)建和啟動(dòng)線程的兩種方式實(shí)例分析

    這篇文章主要介紹了Java創(chuàng)建和啟動(dòng)線程的兩種方式,結(jié)合實(shí)例形式分析了java多線程創(chuàng)建、使用相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下
    2019-09-09
  • Apache Calcite進(jìn)行SQL解析(java代碼實(shí)例)

    Apache Calcite進(jìn)行SQL解析(java代碼實(shí)例)

    Calcite是一款開源SQL解析工具, 可以將各種SQL語句解析成抽象語法樹AST(Abstract Syntax Tree), 之后通過操作AST就可以把SQL中所要表達(dá)的算法與關(guān)系體現(xiàn)在具體代碼之中,今天通過代碼實(shí)例給大家介紹Apache Calcite進(jìn)行SQL解析問題,感興趣的朋友一起看看吧
    2022-01-01
  • Java并發(fā)編程之詳解ConcurrentHashMap類

    Java并發(fā)編程之詳解ConcurrentHashMap類

    在之前的文章中已經(jīng)為大家介紹了java并發(fā)編程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue、BlockingDeque接口,本文為系列文章第八篇.需要的朋友可以參考下
    2021-06-06
  • java 線程方法join簡單用法實(shí)例總結(jié)

    java 線程方法join簡單用法實(shí)例總結(jié)

    這篇文章主要介紹了java 線程方法join簡單用法,結(jié)合實(shí)例形式總結(jié)分析了Java線程join方法的功能、原理及使用技巧,需要的朋友可以參考下
    2019-11-11
  • 詳細(xì)分析JAVA8新特性 Base64

    詳細(xì)分析JAVA8新特性 Base64

    這篇文章主要介紹了JAVA8新特性 Base64的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-08-08
  • 使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例

    使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例

    這篇文章主要介紹了使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • 如何用Stream解決兩層List屬性求和問題

    如何用Stream解決兩層List屬性求和問題

    這篇文章主要介紹了如何用Stream解決兩層List屬性求和問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Java使用mapstruct實(shí)現(xiàn)對(duì)象拷貝

    Java使用mapstruct實(shí)現(xiàn)對(duì)象拷貝

    MapStruct可以簡化對(duì)象之間的映射,本文就來介紹一下Java使用mapstruct實(shí)現(xiàn)對(duì)象拷貝,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • java抓取網(wǎng)頁數(shù)據(jù)獲取網(wǎng)頁中所有的鏈接實(shí)例分享

    java抓取網(wǎng)頁數(shù)據(jù)獲取網(wǎng)頁中所有的鏈接實(shí)例分享

    java抓取網(wǎng)頁數(shù)據(jù)獲取網(wǎng)頁中所有的鏈接實(shí)例分享,使用方法,只要實(shí)例化HtmlParser時(shí)傳入網(wǎng)頁地址就可以了
    2013-12-12
  • mybatis-plus更新字段為null的處理方式

    mybatis-plus更新字段為null的處理方式

    這篇文章主要介紹了mybatis-plus更新字段為null的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評(píng)論

镇坪县| 法库县| 西乡县| 林州市| 天台县| 建瓯市| 永康市| 闽清县| 浦城县| 石台县| 东乌珠穆沁旗| 宣汉县| 陆河县| 陕西省| 锦屏县| 遂宁市| 九江县| 青河县| 民丰县| 夏津县| 宿松县| 莎车县| 班戈县| 铜鼓县| 昌宁县| 奉贤区| 台湾省| 若尔盖县| 如东县| 玉溪市| 无极县| 延津县| 榕江县| 大洼县| 平邑县| 伊吾县| 平山县| 长乐市| 三穗县| 迭部县| 恩平市|