小編給大家分享一下Spring Cloud中Zuul重試機制的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
站在用戶的角度思考問題,與客戶深入溝通,找到始興網(wǎng)站設(shè)計與始興網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、申請域名、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務(wù)覆蓋始興地區(qū)。
具體內(nèi)容如下:
開啟Zuul功能
通過源碼了解Zuul的一次轉(zhuǎn)發(fā)
怎么開啟zuul的重試機制
Edgware.RC1版本的優(yōu)化
開啟Zuul的功能
首先如何使用spring cloud zuul完成路由轉(zhuǎn)發(fā)的功能,這個問題很簡單,只需要進行如下準備工作即可:
注冊中心(Eureka Server)
zuul(同時也是Eureka Client)
應(yīng)用服務(wù)(同時也是Eureka Client)
我們希望zuul和后端的應(yīng)用服務(wù)同時都注冊到Eureka Server上,當我們訪問Zuul的某一個地址時,對應(yīng)其實訪問的是后端應(yīng)用的某個地址,從而從這個地址返回一段內(nèi)容,并展現(xiàn)到瀏覽器上。
注冊中心(Eureka Server)
創(chuàng)建一個Eureka Server只需要在主函數(shù)上添加@EnableEurekaServer,并在properties文件進行簡單配置即可,具體內(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注解(因為集成Eureka,需要另外添加@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
三個工程全部啟動,這時當我們訪問localhost:8081/api-a/hello時,你會看到瀏覽器輸出的內(nèi)容是hello spring...
通過源碼了解Zuul的一次轉(zhuǎn)發(fā)
接下來我們通過源碼層面來了解下,一次轉(zhuǎn)發(fā)內(nèi)部都做了哪些事情。
首先我們查看Zuul的配置類ZuulProxyAutoConfiguration在這個類中有一項工作是初始化Zuul默認自帶的Filter,其中有一個Filter很重要,它就是RibbonRoutingFilter。它主要是完成請求的路由轉(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); } }
可以看到進行轉(zhuǎn)發(fā)的方法是forward,我們進一步查看這個方法,具體內(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這個類是在RibbonCommandFactoryConfiguration完成初始化的(觸發(fā)RibbonCommandFactoryConfiguration的加載動作是利用ZuulProxyAutoConfiguration類上面的@Import標簽),具體代碼如下:
@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); } }
知道了這個ribbonCommandFactory具體的實現(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)); }
這個方法按照我的理解主要做了以下幾件事情:
@Override public HttpClientRibbonCommand create(final RibbonCommandContext context) { /** *獲取所有ZuulFallbackProvider,即當Zuul *調(diào)用失敗后的降級方法 */ ZuulFallbackProvider = xxxxx /** *創(chuàng)建處理請求轉(zhuǎn)發(fā)類,該類會利用 *Apache的Http client進行請求的轉(zhuǎn)發(fā) */ RibbonLoadBalancingHttpClient = xxxxx /** *將降級方法、處理請求轉(zhuǎn)發(fā)類、以及其他一些內(nèi)容 *包裝成HttpClientRibbonCommand(這個類繼承了HystrixCommand) */ return new HttpClientRibbonCommand(xxxxx); }
到這里我們很清楚的知道了RibbonRoutingFilter類的forward方法中RibbonCommand command = this.ribbonCommandFactory.create(context);這一行代碼都做了哪些內(nèi)容.
接下來調(diào)用的是command.execute();方法,通過剛剛的分析我們知道了command其實指的是HttpClientRibbonCommand,同時我們也知道HttpClientRibbonCommand繼承了HystrixCommand所以當執(zhí)行command.execute();時其實執(zhí)行的是HttpClientRibbonCommand的run方法。查看源碼我們并沒有發(fā)現(xiàn)run方法,但是我們發(fā)現(xiàn)HttpClientRibbonCommand直接繼承了AbstractRibbonCommand。所以其實執(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方法中會調(diào)用client的executeWithLoadBalancer方法,通過上面介紹我們知道client指的是RibbonLoadBalancingHttpClient,而RibbonLoadBalancingHttpClient里面并沒有executeWithLoadBalancer方法。(這里面會最終調(diào)用它的父類AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法。)
具體代碼如下:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { /** * 創(chuàng)建一個RetryHandler,這個很重要它是用來 * 決定利用RxJava的Observable是否進行重試的標準。 */ RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig); /** * 創(chuàng)建一個LoadBalancerCommand,這個類用來創(chuàng)建Observable * 以及根據(jù)RetryHandler來判斷是否進行重試操作。 */ LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder() .withLoadBalancerContext(this) .withRetryHandler(handler) .withLoadBalancerURI(request.getUri()) .build(); try { /** *command.submit()方法主要是創(chuàng)建了一個Observable(RxJava) *并且為這個Observable設(shè)置了重試次數(shù),這個Observable最終 *會回調(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); } } }
下面針對于每一塊內(nèi)容做詳細說明:
首先getRequestSpecificRetryHandler(request, requestConfig);這個方法其實調(diào)用的是RibbonLoadBalancingHttpClient的getRequestSpecificRetryHandler方法,這個方法主要是返回一個RequestSpecificRetryHandler
@Override public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { /** *這個很關(guān)鍵,請注意該類構(gòu)造器中的前兩個參數(shù)的值 *正因為一開始我也忽略了這兩個值,所以后續(xù)給我造 *成一定的干擾。 */ return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig); }
接下來創(chuàng)建LoadBalancerCommand并將上一步獲得的RequestSpecificRetryHandler作為參數(shù)內(nèi)容。
最后調(diào)用LoadBalancerCommand的submit方法。該方法內(nèi)容太長具體代碼細節(jié)就不在這里貼出了,按照我個人的理解,只貼出相應(yīng)的偽代碼:
public Observable<T> submit(final ServerOperation<T> operation) { //相同server的重試次數(shù)(去除首次請求) final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); //集群內(nèi)其他Server的重試個數(shù) final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); /** *創(chuàng)建一個Observable(RxJava),selectServer()方法是 *利用Ribbon選擇一個Server,并將其包裝成Observable */ Observable<T> o = selectServer().concatMap(new Func1<Server, Observable<T>>() { @Override public Observable<T> call(final Server server) { /** *這里會回調(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ā)請求失敗時,會進入此方法。通過此方法進行判斷 *是否超過重試次數(shù)maxRetrysSame、maxRetrysNext。 */ } }); }
operation.call()方法最終會調(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ā)請求 */ final HttpResponse httpResponse = this.delegate.execute(httpUriRequest); /** * 返回結(jié)果 */ return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); }
可以看到上面方法主要做的就是組裝請求參數(shù)(包括各種超時時間),然后發(fā)起轉(zhuǎn)發(fā)請求,最終獲取相應(yīng)結(jié)果。
說到這里,zuul轉(zhuǎn)發(fā)一次請求的基本原理就說完了。讓我們再回顧下整個流程。
zuul的轉(zhuǎn)發(fā)是通過RibbonRoutingFilter這個Filter進行操作的。
在轉(zhuǎn)發(fā)之前,zuul利用Hystrix將此次轉(zhuǎn)發(fā)請求包裝成一個HystrixCommand,正應(yīng)為這樣才使得zuul具有了降級(Fallback)的功能,同時HystrixCommand是具備超時時間的(默認是1s)。而且Zuul默認采用的隔離級別是信號量模式。
在HystrixCommand內(nèi)部zuul再次將請求包裝成一個Observable,(有關(guān)RxJava的知識請參照其官方文檔)。并且為Observable設(shè)置了重試次數(shù)。
事實真的是這樣嗎?當我看到源碼中為Observable設(shè)置重試次數(shù)的時候,我以為這就是zuul的重試邏輯。遺憾的是我的想法是錯誤的。還記得上面我說的getRequestSpecificRetryHandler(request, requestConfig);這個方法嗎?(不記得的同學(xué)可以回過頭來再看下),這個方法返回的是RequestSpecificRetryHandler這個類,而且在創(chuàng)建該類時,構(gòu)造器的前兩個參數(shù)都為false。(這一點非常重要)。這兩個參數(shù)分別是okToRetryOnConnectErrors和okToRetryOnAllErrors。
我原本的想法是這個請求被包裝成Observable,如果這次請求因為超時出現(xiàn)異?;蛘咂渌惓?,這樣就會觸發(fā)Observable的重試機制(RxJava),但是事實并非如此,為什么呢?原因就是上面的那兩個參數(shù),當出現(xiàn)了超時異常的時候,在觸發(fā)重試機制之前會調(diào)用RequestSpecificRetryHandler的isRetriableException()方法,該方法的作用是用來判斷是否執(zhí)行重試動作,具體代碼如下:
@Override public boolean isRetriableException(Throwable e, boolean sameServer) { //此時該值為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 { //此時該值為false return okToRetryOnConnectErrors && isConnectionException(e); } }
說道這里zuul轉(zhuǎn)發(fā)一次請求的基本原理大概了解了,同時也驗證了一個事實就是實現(xiàn)zuul進行重試的邏輯并不是Observable的重試機制。那么問題來了?是什么使zuul具有重試功能的呢?
怎么開啟zuul的重試機制
開啟Zuul重試的功能在原有的配置基礎(chǔ)上需要額外進行以下設(shè)置:
在pom中添加spring-retry的依賴(maven工程)
設(shè)置zuul.retryable=true(該參數(shù)默認為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 #同一個Server重試的次數(shù)(除去首次) ribbon.MaxAutoRetries=3 #切換相同Server的次數(shù) ribbon.MaxAutoRetriesNextServer=0 eureka.client.service-url.defaultZone=http://localhost:8761/eureka
為了模擬出Zuul重試的功能,需要對后端應(yīng)用服務(wù)進行改造,改造后的內(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)達到Zuul轉(zhuǎn)發(fā)超時情況(Zuul默認連接超時未2s、read超時時間為5s),從而觸發(fā)Zuul的重試功能。這時候在此訪問localhost:8081/api-a/hello時,查看應(yīng)用服務(wù)后臺,會發(fā)現(xiàn)最終打印三次"request is coming..."
通過現(xiàn)象看本質(zhì),接下來簡單介紹下Zuul重試的原理。首先如果你工程classpath中存在spring-retry,那么zuul在初始化的時候就不會創(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; } }
所以請求到來需要轉(zhuǎn)發(fā)的時候(AbstractLoadBalancerAwareClient類中executeWithLoadBalancer方法會調(diào)用AbstractLoadBalancerAwareClient.this.execute())其實調(diào)用的是RetryableRibbonLoadBalancingHttpClient的execute方法(而不是沒有重試時候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(請求信息) */ final RequestConfig requestConfig = builder.build(); final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); /** * 創(chuàng)建RetryCallbck的實現(xiàn)類,用來完成重試邏輯 */ RetryCallback retryCallback = new RetryCallback() {}; //創(chuàng)建Spring-retry的模板類,RetryTemplate。 RetryTemplate retryTemplate = new RetryTemplate(); /** *設(shè)置重試規(guī)則,即在什么情況下進行重試 *什么情況下停止重試。源碼中這部分存在 *一個判斷,判斷的根據(jù)就是在zuul工程 *的propertris中配置的zuul.retryable *該參數(shù)內(nèi)容為true才可以具有重試功能。 */ retryTemplate.setRetryPolicy(xxx); /** *發(fā)起請求 */ return retryTemplate.execute(callback); }
以上是“Spring Cloud中Zuul重試機制的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
文章題目:SpringCloud中Zuul重試機制的示例分析
文章分享:http://muchs.cn/article46/jpihhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、營銷型網(wǎng)站建設(shè)、自適應(yīng)網(wǎng)站、定制網(wǎng)站、全網(wǎng)營銷推廣
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)