亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Spring Cloud 請求重試機制核心代碼分析

2024-07-14 08:41:11
字體:
來源:轉載
供稿:網友

場景

發布微服務的操作一般都是打完新代碼的包,kill掉在跑的應用,替換新的包,啟動。

spring cloud 中使用eureka為注冊中心,它是允許服務列表數據的延遲性的,就是說即使應用已經不在服務列表了,客戶端在一段時間內依然會請求這個地址。那么就會出現請求正在發布的地址,而導致失敗。

我們會優化服務列表的刷新時間,以提高服務列表信息的時效性。但是無論怎樣,都無法避免有那么一段時間是數據不一致的。

所以我們想到一個辦法就是重試機制,當a機子在重啟時,同個集群的b是可以正常提供服務的,如果有重試機制就可以在上面這個場景里進行重試到b而不影響正確響應。

操作

需要進行如下的操作:

ribbon: ReadTimeout: 10000 ConnectTimeout: 10000 MaxAutoRetries: 0 MaxAutoRetriesNextServer: 1 OkToRetryOnAllOperations: false

引入spring-retry包

<dependency>  <groupId>org.springframework.retry</groupId>  <artifactId>spring-retry</artifactId> </dependency>

以zuul為例子還需要配置開啟重試:

zuul.retryable=true

遇到了問題

然而萬事總沒那么一帆風順,通過測試重試機制生效了,但是并沒有我想象的去請求另一臺健康的機子,于是被迫去吧開源碼看一看,最終發現是源碼的bug,不過已經修復,升級版本即可。

代碼分析

使用的版本是

spring-cloud-netflix-core:1.3.6.RELEASE

spring-retry:1.2.1.RELEASE

spring cloud 依賴版本:

<dependencyManagement>    <dependencies>      <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-dependencies</artifactId>        <version>${spring-cloud.version}</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies>  </dependencyManagement>

因為啟用了重試,所以請求應用時會執行RetryableRibbonLoadBalancingHttpClient.execute方法:

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()));          }        }        newRequest = getSecureRequest(request, configOverride);        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);  }

我們發現先new 一個RetryCallback,然后執行this.executeWithRetry(request, retryPolicy, retryCallback);

而這個RetryCallback.doWithRetry的代碼我們清楚看到是實際請求的代碼,也就是說this.executeWithRetry方法最終還是會調用RetryCallback.doWithRetry

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,      RecoveryCallback<T> recoveryCallback, RetryState state)      throws E, ExhaustedRetryException {    RetryPolicy retryPolicy = this.retryPolicy;    BackOffPolicy backOffPolicy = this.backOffPolicy;    // Allow the retry policy to initialise itself...    RetryContext context = open(retryPolicy, state);    if (this.logger.isTraceEnabled()) {      this.logger.trace("RetryContext retrieved: " + context);    }    // Make sure the context is available globally for clients who need    // it...    RetrySynchronizationManager.register(context);    Throwable lastException = null;    boolean exhausted = false;    try {      // Give clients a chance to enhance the context...      boolean running = doOpenInterceptors(retryCallback, context);      if (!running) {        throw new TerminatedRetryException(            "Retry terminated abnormally by interceptor before first attempt");      }      // Get or Start the backoff context...      BackOffContext backOffContext = null;      Object resource = context.getAttribute("backOffContext");      if (resource instanceof BackOffContext) {        backOffContext = (BackOffContext) resource;      }      if (backOffContext == null) {        backOffContext = backOffPolicy.start(context);        if (backOffContext != null) {          context.setAttribute("backOffContext", backOffContext);        }      }      /*       * We allow the whole loop to be skipped if the policy or context already       * forbid the first try. This is used in the case of external retry to allow a       * recovery in handleRetryExhausted without the callback processing (which       * would throw an exception).       */      while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {        try {          if (this.logger.isDebugEnabled()) {            this.logger.debug("Retry: count=" + context.getRetryCount());          }          // Reset the last exception, so if we are successful          // the close interceptors will not think we failed...          lastException = null;          return retryCallback.doWithRetry(context);        }        catch (Throwable e) {          lastException = e;          try {            registerThrowable(retryPolicy, state, context, e);          }          catch (Exception ex) {            throw new TerminatedRetryException("Could not register throwable",                ex);          }          finally {            doOnErrorInterceptors(retryCallback, context, e);          }          if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {            try {              backOffPolicy.backOff(backOffContext);            }            catch (BackOffInterruptedException ex) {              lastException = e;              // back off was prevented by another thread - fail the retry              if (this.logger.isDebugEnabled()) {                this.logger                    .debug("Abort retry because interrupted: count="                        + context.getRetryCount());              }              throw ex;            }          }          if (this.logger.isDebugEnabled()) {            this.logger.debug(                "Checking for rethrow: count=" + context.getRetryCount());          }          if (shouldRethrow(retryPolicy, context, state)) {            if (this.logger.isDebugEnabled()) {              this.logger.debug("Rethrow in retry for policy: count="                  + context.getRetryCount());            }            throw RetryTemplate.<E>wrapIfNecessary(e);          }        }        /*         * A stateful attempt that can retry may rethrow the exception before now,         * but if we get this far in a stateful retry there's a reason for it,         * like a circuit breaker or a rollback classifier.         */        if (state != null && context.hasAttribute(GLOBAL_STATE)) {          break;        }      }      if (state == null && this.logger.isDebugEnabled()) {        this.logger.debug(            "Retry failed last attempt: count=" + context.getRetryCount());      }      exhausted = true;      return handleRetryExhausted(recoveryCallback, context, state);    }    catch (Throwable e) {      throw RetryTemplate.<E>wrapIfNecessary(e);    }    finally {      close(retryPolicy, context, state, lastException == null || exhausted);      doCloseInterceptors(retryCallback, context, lastException);      RetrySynchronizationManager.clear();    }  }

在一個while循環里實現重試機制,當執行retryCallback.doWithRetry(context)出現異常的時候,就會catch異常,然后用 retryPolicy判斷是否進行重試,特別注意registerThrowable(retryPolicy, state, context, e);方法,不但判斷了是否重試,在重試情況下會新選出一個機子放入context,然后再去執行retryCallback.doWithRetry(context)時帶入,如此就實現了換機子重試了。

但是我的配置怎么會沒有換機子呢?調試代碼發現registerThrowable(retryPolicy, state, context, e);選出來的機子沒問題,就是新的健康的機子,但是在執行retryCallback.doWithRetry(context)代碼的時候依然請求的是那臺掛掉的機子。

所以我們再仔細看一下retryCallback.doWithRetry(context)的代碼:

我們發現了這行代碼:

newRequest = getSecureRequest(request, configOverride);protected RibbonApacheHttpRequest getSecureRequest(RibbonApacheHttpRequest request, IClientConfig configOverride) {    if (isSecure(configOverride)) {      final URI secureUri = UriComponentsBuilder.fromUri(request.getUri())          .scheme("https").build(true).toUri();      return request.withNewUri(secureUri);    }    return request;  }

newRequest在前面已經使用context構建完畢,request是上一次請求的數據,只要執行這個代碼就會發現newRequest永遠都會被request覆蓋??吹竭@里我們才發現原來是一個源碼bug。

issue地址:https://github.com/spring-cloud/spring-cloud-netflix/issues/2667

總結

這是一次很普通的查問題過程,在這個過程中當我發現配置沒有達到我的預期時,我先查看了配置的含義,嘗試多次無果,于是進行斷點調試發現異常中斷點后,因為場景需要一臺機子健康一臺機子下線,我模擬了數百次,最終才定位到了這行代碼。開源項目即使是優秀的項目必然也會有bug存在,不迷信,不盲目。另一方面,閱讀源碼能力也是一個解決問題的重要能力,像我在找源碼入口,定位代碼時耗費了很多的時間。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91高潮在线观看| 成人有码在线播放| 国产91露脸中文字幕在线| 成年无码av片在线| 欧美日韩国产精品一区| 成人免费淫片视频软件| 国产成人av网址| 精品中文字幕在线| 日韩精品在线第一页| 国产精品黄色av| 欧美精品亚州精品| 欧美黑人一区二区三区| 久久久噜噜噜久久久| 日韩中文字幕av| 在线亚洲男人天堂| 538国产精品视频一区二区| 亚洲自拍av在线| 精品久久久久久久久久久久久久| 国产aⅴ夜夜欢一区二区三区| 欧美大奶子在线| 亚洲人成在线观看网站高清| 国产精品入口福利| 亚洲视频电影图片偷拍一区| 亚洲国产美女久久久久| 日本一区二区在线免费播放| 亚洲午夜精品久久久久久久久久久久| 亚洲国产第一页| 欧美国产第一页| 亚洲精品国产美女| 国产精品啪视频| 欧美午夜激情在线| 国产在线久久久| 国内精品久久久久影院优| 成人免费观看49www在线观看| 国产精品高清免费在线观看| 性欧美xxxx视频在线观看| 久久99亚洲精品| 88xx成人精品| 一本大道久久加勒比香蕉| 91亚洲精品久久久久久久久久久久| 日韩三级影视基地| 国产日韩欧美视频在线| 国产精品wwwwww| 国产午夜精品久久久| 成人在线免费观看视视频| 91国自产精品中文字幕亚洲| 久久中文字幕一区| 欧美性做爰毛片| 九九九热精品免费视频观看网站| 中文欧美日本在线资源| 亚洲国产精品一区二区久| 亚洲国产成人在线播放| 亚洲国产天堂久久国产91| 国产精品久久久久影院日本| 91在线免费看网站| 国产精品久久久久久久app| 国产精品视频最多的网站| 精品欧美aⅴ在线网站| 日韩高清电影好看的电视剧电影| 亚洲第一天堂无码专区| 日日摸夜夜添一区| 国产精品扒开腿做爽爽爽的视频| 欧美激情第1页| 日韩亚洲一区二区| 亚洲va码欧洲m码| 按摩亚洲人久久| 国产综合香蕉五月婷在线| 九九热精品视频国产| 久久久av亚洲男天堂| 欧美精品精品精品精品免费| 日韩av色综合| xxxx欧美18另类的高清| 国产日韩欧美中文| 久久精品99久久久久久久久| 国产精品黄页免费高清在线观看| 日韩中文在线视频| 国产精品美腿一区在线看| 一本色道久久综合亚洲精品小说| www.欧美精品一二三区| 欧美日韩国产精品一区二区三区四区| 欧美成人自拍视频| 日韩最新在线视频| www日韩中文字幕在线看| 亚洲国产精品久久| 国产精品一区二区三区毛片淫片| 欧美电影免费观看高清完整| 久久精品国产精品| 国产日韩在线一区| 亚洲精品免费一区二区三区| 久久久久这里只有精品| 岛国av一区二区三区| 色偷偷91综合久久噜噜| 国产91网红主播在线观看| 午夜免费日韩视频| 91夜夜揉人人捏人人添红杏| 国产精品免费久久久久影院| 在线视频日本亚洲性| 成人av色在线观看| 亚洲成人网av| 亚洲国产欧美日韩精品| 538国产精品一区二区在线| 精品视频一区在线视频| 国精产品一区一区三区有限在线| 日韩美女中文字幕| 欧美大尺度电影在线观看| 欧美丝袜一区二区三区| 69久久夜色精品国产69| 亚洲福利在线看| 日韩经典中文字幕在线观看| 97**国产露脸精品国产| 38少妇精品导航| 成人精品久久久| 精品夜色国产国偷在线| 国产成人在线播放| 国产精品伦子伦免费视频| 97超碰蝌蚪网人人做人人爽| 国产精品男人的天堂| 亚洲一区二区三区四区视频| 欧美激情喷水视频| 国产成人aa精品一区在线播放| 亚洲精品美女网站| 国产精品嫩草影院一区二区| 欧美电影免费观看高清完整| 久久精品精品电影网| 欧美精品videossex性护士| 91亚洲一区精品| 国产精品永久免费视频| 欧美一区二粉嫩精品国产一线天| 日韩欧中文字幕| 精品福利一区二区| 日韩av在线影院| 欧美视频免费在线| 国产成人综合一区二区三区| 亚洲人成毛片在线播放| 亚洲欧美中文字幕在线一区| 欧美性高潮在线| 久久久久久com| 国产精品男人爽免费视频1| 成人在线播放av| 夜夜躁日日躁狠狠久久88av| 欧美性xxxxx极品娇小| 精品久久久久久中文字幕| 日本不卡高字幕在线2019| 欧美午夜精品久久久久久久| 亚洲精品一区在线观看香蕉| 综合网中文字幕| 日韩av综合网| www.亚洲天堂| 国产成人精品久久| 国产91精品久久久久久| 日韩精品在线免费观看| 中文字幕亚洲激情| 性欧美xxxx视频在线观看| 亚洲精品suv精品一区二区| 亚洲国产天堂久久综合| 亚洲欧美日韩国产中文| 亚洲欧美一区二区激情| 亚洲欧美日韩在线高清直播| 亚洲国产成人久久综合| 日韩精品视频免费在线观看| 最新91在线视频| 久久久噜久噜久久综合| 精品亚洲一区二区三区在线播放| 国产精品美女久久久免费|