這篇文章給大家介紹Spring Cloud 超時和重試機制是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
創(chuàng)新互聯(lián)主營同安網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都app軟件開發(fā),同安h5微信小程序搭建,同安網(wǎng)站營銷推廣歡迎同安等地區(qū)企業(yè)咨詢
本文基于Spring Cloud Greenwich.SR2、Spring Boot 2.1.6.RELEASE
feign: client: config: default: connect-timeout: 2000 read-timeout: 2000
Spring Cloud默認(rèn)關(guān)閉了Feign的重試機制
//org.springframework.cloud.openfeign.FeignClientsConfiguration @Bean @ConditionalOnMissingBean public Retryer feignRetryer() { return Retryer.NEVER_RETRY; } //feign.Retryer /** * 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; } };
如果想要開啟的話,就自己聲明一個bean
@Bean public Retryer feignRetryer() { return new Retryer.Default(); }
#ribbon的超時時間 #如果ribbon和feign的超時時間都配置了,ribbon的配置會被覆蓋 ribbon: ReadTimeout: 3000 ConnectTimeout: 3000
ribbon: MaxAutoRetries: 1 #同一臺實例最大重試次數(shù),不包括首次調(diào)用 MaxAutoRetriesNextServer: 1 #重試負(fù)載均衡其他的實例最大重試次數(shù),不包括首次調(diào)用 OkToRetryOnAllOperations: false #是否所有操作都重試
hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 1000
注意Hystrix的超時時間要超過ribbon的重試時間,否則ribbon重試過程中,就會先觸發(fā)Hystrix的熔斷
超時時間計算可以參考zuul中的AbstractRibbonCommand類的getRibbonTimeout()方法,
protected static int getRibbonTimeout(IClientConfig config, String commandKey) { int ribbonTimeout; // 這是比較異常的情況,不說 if (config == null) { ribbonTimeout = RibbonClientConfiguration.DEFAULT_READ_TIMEOUT + RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT; } else { // 這里獲取了四個參數(shù),ReadTimeout,ConnectTimeout,MaxAutoRetries, MaxAutoRetriesNextServer int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout", IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT); int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout", IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT); int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries", IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES); int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer", IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER); // ribbonTimeout的計算方法在這里,以上文的設(shè)置為例 // ribbonTimeout = (3000 + 3000) * (1 + 1) * (1 + 1) = 24000(毫秒) ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1); } return ribbonTimeout; }
RestTemplate默認(rèn)超時時間是-1,即不會超時,如果想要設(shè)置的話,可以這么做
@Bean @Primary @LoadBalanced public RestTemplate lbRestTemplate() { SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); simpleClientHttpRequestFactory.setConnectTimeout(1000); simpleClientHttpRequestFactory.setReadTimeout(1000); return new RestTemplate(simpleClientHttpRequestFactory); }
//AbstractLoadBalancerAwareClient.java public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommandcommand = buildLoadBalancerCommand(request, requestConfig); //省略... } protected LoadBalancerCommand buildLoadBalancerCommand(final S request, final IClientConfig config) { RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config); //省略... } //FeignLoadBalancer.java //FeignLoadBalancer是AbstractLoadBalancerAwareClient的子類 @Override public RequestSpecificRetryHandler getRequestSpecificRetryHandler( RibbonRequest request, IClientConfig requestConfig) { //如果isOkToRetryOnAllOperations參數(shù)為true if (this.ribbon.isOkToRetryOnAllOperations()) { return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); } if (!request.toRequest().httpMethod().name().equals("GET")) { return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig); } else { return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig); } } //RequestSpecificRetryHandler.java /** * okToRetryOnConnectErrors:只對連接錯誤發(fā)起重試 * okToRetryOnAllErrors:對于所有錯誤都會發(fā)起重試 */ public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) { Preconditions.checkNotNull(baseRetryHandler); this.okToRetryOnConnectErrors = okToRetryOnConnectErrors; this.okToRetryOnAllErrors = okToRetryOnAllErrors; this.fallback = baseRetryHandler; if (requestConfig != null) { if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) { retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries); } if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) { retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer); } } }
可以看到如果設(shè)置了isOkToRetryOnAllOperations為true,就會對所有錯誤發(fā)起重試,否則的話就只對連接異常發(fā)起重試,判斷是否重試的代碼如下:
//RequestSpecificRetryHandler.java @Override public boolean isRetriableException(Throwable e, boolean sameServer) { 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 { return okToRetryOnConnectErrors && isConnectionException(e); } }
這里會有一個問題,如果你是新增/修改操作,系統(tǒng)處理時間過長導(dǎo)致超時,也會觸發(fā)Feign的自動重試,如果你的冪等性做的不好,就會導(dǎo)致很嚴(yán)重的后果。
而如果是連接異常,此時請求還沒有發(fā)送過去,所以是不會重復(fù)執(zhí)行的。
當(dāng)然了,在分布式系統(tǒng)中,還是建議做好每個接口的冪等性。
//SynchronousMethodHandler.java @Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); //這里我們假設(shè)你開啟了Feign的重試,并且使用的是Retryer.Default這個類 Retryer retryer = this.retryer.clone(); while (true) { try { //這里會調(diào)用到5.3節(jié)executeWithLoadBalancer()方法 return executeAndDecode(template); } catch (RetryableException e) { try { //在重試次數(shù)之內(nèi),會等待一段時間返回,繼續(xù)while循環(huán),否則會拋出異常跳出循環(huán) retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
//AbstractLoadBalancerAwareClient.java public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommandcommand = buildLoadBalancerCommand(request, requestConfig); try { return command.submit( new ServerOperation () { @Override public Observable 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); } } }
上邊是調(diào)用的入口,下邊是重試執(zhí)行的邏輯,由于的RxJava寫的,暫時看不懂,先貼出來日后再說......
//LoadBalancerCommand.java public Observablesubmit(final ServerOperation operation) { final ExecutionInfoContext context = new ExecutionInfoContext(); if (listenerInvoker != null) { try { listenerInvoker.onExecutionStart(); } catch (AbortExecutionException e) { return Observable.error(e); } } final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); // Use the load balancer Observable o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1 >() { @Override // Called for each server being selected public Observable call(Server server) { context.setServer(server); final ServerStats stats = loadBalancerContext.getServerStats(server); // Called for each attempt and retry Observable o = Observable .just(server) .concatMap(new Func1 >() { @Override public Observable call(final Server server) { context.incAttemptCount(); loadBalancerContext.noteOpenConnection(stats); if (listenerInvoker != null) { try { listenerInvoker.onStartWithServer(context.toExecutionInfo()); } catch (AbortExecutionException e) { return Observable.error(e); } } final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start(); return operation.call(server).doOnEach(new Observer () { private T entity; @Override public void onCompleted() { recordStats(tracer, stats, entity, null); // TODO: What to do if onNext or onError are never called? } @Override public void onError(Throwable e) { recordStats(tracer, stats, null, e); logger.debug("Got error {} when executed on server {}", e, server); if (listenerInvoker != null) { listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo()); } } @Override public void onNext(T entity) { this.entity = entity; if (listenerInvoker != null) { listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo()); } } private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) { tracer.stop(); loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler); } }); } }); if (maxRetrysSame > 0) o = o.retry(retryPolicy(maxRetrysSame, true)); return o; } }); if (maxRetrysNext > 0 && server == null) o = o.retry(retryPolicy(maxRetrysNext, false)); return o.onErrorResumeNext(new Func1 >() { @Override public Observable call(Throwable e) { if (context.getAttemptCount() > 0) { if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) { e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED, "Number of retries on next server exceeded max " + maxRetrysNext + " retries, while making a call for: " + context.getServer(), e); } else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) { e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED, "Number of retries exceeded max " + maxRetrysSame + " retries, while making a call for: " + context.getServer(), e); } } if (listenerInvoker != null) { listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo()); } return Observable.error(e); } }); }
關(guān)于Spring Cloud 超時和重試機制是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。