詳解Spring Cloud Zuul重試機(jī)制探秘
簡(jiǎn)介
本文章對(duì)應(yīng)spring cloud的版本為(Dalston.SR4),具體內(nèi)容如下:
- 開(kāi)啟Zuul功能
- 通過(guò)源碼了解Zuul的一次轉(zhuǎn)發(fā)
- 怎么開(kāi)啟zuul的重試機(jī)制
- Edgware.RC1版本的優(yōu)化
開(kāi)啟Zuul的功能
首先如何使用spring cloud zuul完成路由轉(zhuǎn)發(fā)的功能,這個(gè)問(wèn)題很簡(jiǎn)單,只需要進(jìn)行如下準(zhǔn)備工作即可:
- 注冊(cè)中心(Eureka Server)
- zuul(同時(shí)也是Eureka Client)
- 應(yīng)用服務(wù)(同時(shí)也是Eureka Client)
我們希望zuul和后端的應(yīng)用服務(wù)同時(shí)都注冊(cè)到Eureka Server上,當(dāng)我們?cè)L問(wèn)Zuul的某一個(gè)地址時(shí),對(duì)應(yīng)其實(shí)訪問(wèn)的是后端應(yīng)用的某個(gè)地址,從而從這個(gè)地址返回一段內(nèi)容,并展現(xiàn)到瀏覽器上。
注冊(cè)中心(Eureka Server)
創(chuàng)建一個(gè)Eureka Server只需要在主函數(shù)上添加@EnableEurekaServer,并在properties文件進(jìn)行簡(jiǎ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問(wèn)localhost:8081/api-a/hello時(shí),你會(huì)看到瀏覽器輸出的內(nèi)容是hello spring...
通過(guò)源碼了解Zuul的一次轉(zhuǎn)發(fā)
接下來(lái)我們通過(guò)源碼層面來(lái)了解下,一次轉(zhuǎn)發(fā)內(nèi)部都做了哪些事情。
首先我們查看Zuul的配置類(lèi)ZuulProxyAutoConfiguration在這個(gè)類(lèi)中有一項(xiàng)工作是初始化Zuul默認(rèn)自帶的Filter,其中有一個(gè)Filter很重要,它就是RibbonRoutingFilter。它主要是完成請(qǐng)求的路由轉(zhuǎn)發(fā)。接下來(lái)我們看下他的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è)類(lèi)是在RibbonCommandFactoryConfiguration完成初始化的(觸發(fā)RibbonCommandFactoryConfiguration的加載動(dòng)作是利用ZuulProxyAutoConfiguration類(lèi)上面的@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)類(lèi)(HttpClientRibbonCommandFactory),接下來(lái)我們看看它的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ā)類(lèi),該類(lèi)會(huì)利用
*Apache的Http client進(jìn)行請(qǐng)求的轉(zhuǎn)發(fā)
*/
RibbonLoadBalancingHttpClient = xxxxx
/**
*將降級(jí)方法、處理請(qǐng)求轉(zhuǎn)發(fā)類(lèi)、以及其他一些內(nèi)容
*包裝成HttpClientRibbonCommand(這個(gè)類(lèi)繼承了HystrixCommand)
*/
return new HttpClientRibbonCommand(xxxxx);
}
到這里我們很清楚的知道了RibbonRoutingFilter類(lèi)的forward方法中RibbonCommand command = this.ribbonCommandFactory.create(context);這一行代碼都做了哪些內(nèi)容.
接下來(lái)調(diào)用的是command.execute();方法,通過(guò)剛剛的分析我們知道了command其實(shí)指的是HttpClientRibbonCommand,同時(shí)我們也知道HttpClientRibbonCommand繼承了HystrixCommand所以當(dāng)執(zhí)行command.execute();時(shí)其實(shí)執(zhí)行的是HttpClientRibbonCommand的run方法。查看源碼我們并沒(méi)有發(fā)現(xiàn)run方法,但是我們發(fā)現(xiàn)HttpClientRibbonCommand直接繼承了AbstractRibbonCommand。所以其實(shí)執(zhí)行的是AbstractRibbonCommand的run方法,接下來(lái)我們看看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方法,通過(guò)上面介紹我們知道client指的是RibbonLoadBalancingHttpClient,而RibbonLoadBalancingHttpClient里面并沒(méi)有executeWithLoadBalancer方法。(這里面會(huì)最終調(diào)用它的父類(lèi)AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法。)
具體代碼如下:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
/**
* 創(chuàng)建一個(gè)RetryHandler,這個(gè)很重要它是用來(lái)
* 決定利用RxJava的Observable是否進(jìn)行重試的標(biāo)準(zhǔn)。
*/
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
/**
* 創(chuàng)建一個(gè)LoadBalancerCommand,這個(gè)類(lèi)用來(lái)創(chuàng)建Observable
* 以及根據(jù)RetryHandler來(lái)判斷是否進(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ì)說(shuō)明:
首先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)注意該類(lèi)構(gòu)造器中的前兩個(gè)參數(shù)的值
*正因?yàn)橐婚_(kāi)始我也忽略了這兩個(gè)值,所以后續(xù)給我造
*成一定的干擾。
*/
return new RequestSpecificRetryHandler(false, false,
RetryHandler.DEFAULT, requestConfig);
}
接下來(lái)創(chuàng)建LoadBalancerCommand并將上一步獲得的RequestSpecificRetryHandler作為參數(shù)內(nèi)容。
最后調(diào)用LoadBalancerCommand的submit方法。該方法內(nèi)容太長(zhǎng)具體代碼細(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類(lèi)的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)入此方法。通過(guò)此方法進(jìn)行判斷
*是否超過(guò)重試次數(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é)果。
說(shuō)到這里,zuul轉(zhuǎn)發(fā)一次請(qǐng)求的基本原理就說(shuō)完了。讓我們?cè)倩仡櫹抡麄€(gè)流程。
- zuul的轉(zhuǎn)發(fā)是通過(guò)RibbonRoutingFilter這個(gè)Filter進(jìn)行操作的。
- 在轉(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)量模式。
- 在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ò)誤的。還記得上面我說(shuō)的getRequestSpecificRetryHandler(request, requestConfig);這個(gè)方法嗎?(不記得的同學(xué)可以回過(guò)頭來(lái)再看下),這個(gè)方法返回的是RequestSpecificRetryHandler這個(gè)類(lèi),而且在創(chuàng)建該類(lèi)時(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()方法,該方法的作用是用來(lái)判斷是否執(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);
}
}
說(shuō)道這里zuul轉(zhuǎn)發(fā)一次請(qǐng)求的基本原理大概了解了,同時(shí)也驗(yàn)證了一個(gè)事實(shí)就是實(shí)現(xiàn)zuul進(jìn)行重試的邏輯并不是Observable的重試機(jī)制。那么問(wèn)題來(lái)了?是什么使zuul具有重試功能的呢?
怎么開(kāi)啟zuul的重試機(jī)制
開(kāi)啟Zuul重試的功能在原有的配置基礎(chǔ)上需要額外進(jìn)行以下設(shè)置:
- 在pom中添加spring-retry的依賴(maven工程)
- 設(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/** #是否開(kāi)啟重試功能 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 ...";
}
通過(guò)使用Thread.sleep(100000)達(dá)到Zuul轉(zhuǎn)發(fā)超時(shí)情況(Zuul默認(rèn)連接超時(shí)未2s、read超時(shí)時(shí)間為5s),從而觸發(fā)Zuul的重試功能。這時(shí)候在此訪問(wèn)localhost:8081/api-a/hello時(shí),查看應(yīng)用服務(wù)后臺(tái),會(huì)發(fā)現(xiàn)最終打印三次"request is coming..."
通過(guò)現(xiàn)象看本質(zhì),接下來(lái)簡(jiǎn)單介紹下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)求到來(lái)需要轉(zhuǎn)發(fā)的時(shí)候(AbstractLoadBalancerAwareClient類(lèi)中executeWithLoadBalancer方法會(huì)調(diào)用AbstractLoadBalancerAwareClient.this.execute())其實(shí)調(diào)用的是RetryableRibbonLoadBalancingHttpClient的execute方法(而不是沒(méi)有重試時(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)類(lèi),用來(lái)完成重試邏輯
*/
RetryCallback retryCallback = new RetryCallback() {};
//創(chuàng)建Spring-retry的模板類(lèi),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)求的整體過(guò)程,也明確了zuul因后端超時(shí)而觸發(fā)重試的原理??墒撬坪踹€存在著一個(gè)問(wèn)題,就是超時(shí)問(wèn)題。前面說(shuō)過(guò)zuul把路由請(qǐng)求這個(gè)過(guò)程包裝成一個(gè)HystrixCommnd,而在我的propertries文件中并沒(méi)有設(shè)置Hystrix的超時(shí)時(shí)間(默認(rèn)時(shí)間為1s),而read的超時(shí)時(shí)間是5s(前面源碼部分介紹過(guò))。這里就會(huì)有人問(wèn),因?yàn)樽钔鈱邮遣捎肏ystrix,而Hystrix此時(shí)已經(jīng)超時(shí)了,為什么還允許它內(nèi)部繼續(xù)使用spring-retry進(jìn)行重試呢?帶著這個(gè)問(wèn)題我查看了官方GitHub上的issues,發(fā)現(xiàn)有人對(duì)此問(wèn)題提出過(guò)疑問(wèn)。作者給出的回復(fù)是Hystrix超時(shí)的時(shí)候并不會(huì)打斷內(nèi)部重試的操作。
其實(shí)說(shuō)實(shí)話這塊內(nèi)容我并不是很理解(可能是因?yàn)镠ystrix源碼了解較少),帶著這個(gè)問(wèn)題我給作者發(fā)了一封郵件,郵件對(duì)話內(nèi)容如下:
我的(英語(yǔ)水平不好,大家見(jiàn)諒):
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)容始終還是沒(mé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)容來(lá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.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家
- 深入理解Spring Cloud Zuul過(guò)濾器
- 詳解Spring Cloud Zuul中路由配置細(xì)節(jié)
- spring cloud zuul修改請(qǐng)求url的方法
- 深入解析Spring Cloud內(nèi)置的Zuul過(guò)濾器
- spring cloud如何修復(fù)zuul跨域配置異常的問(wèn)題
- SpringCloud實(shí)戰(zhàn)小貼士之Zuul的路徑匹配
- 利用Spring Cloud Zuul實(shí)現(xiàn)動(dòng)態(tài)路由示例代碼
- springcloud 中 zuul 修改請(qǐng)求參數(shù)信息的方法
- Spring Cloud zuul自定義統(tǒng)一異常處理實(shí)現(xiàn)方法
- Spring Cloud入門(mén)教程之Zuul實(shí)現(xiàn)API網(wǎng)關(guān)與請(qǐng)求過(guò)濾
相關(guān)文章
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í)例)
Calcite是一款開(kāi)源SQL解析工具, 可以將各種SQL語(yǔ)句解析成抽象語(yǔ)法樹(shù)AST(Abstract Syntax Tree), 之后通過(guò)操作AST就可以把SQL中所要表達(dá)的算法與關(guān)系體現(xiàn)在具體代碼之中,今天通過(guò)代碼實(shí)例給大家介紹Apache Calcite進(jìn)行SQL解析問(wèn)題,感興趣的朋友一起看看吧2022-01-01
Java并發(fā)編程之詳解ConcurrentHashMap類(lèi)
在之前的文章中已經(jīng)為大家介紹了java并發(fā)編程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue、BlockingDeque接口,本文為系列文章第八篇.需要的朋友可以參考下2021-06-06
java 線程方法join簡(jiǎn)單用法實(shí)例總結(jié)
這篇文章主要介紹了java 線程方法join簡(jiǎn)單用法,結(jié)合實(shí)例形式總結(jié)分析了Java線程join方法的功能、原理及使用技巧,需要的朋友可以參考下2019-11-11
使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例
這篇文章主要介紹了使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
Java使用mapstruct實(shí)現(xiàn)對(duì)象拷貝
MapStruct可以簡(jiǎn)化對(duì)象之間的映射,本文就來(lái)介紹一下Java使用mapstruct實(shí)現(xiàn)對(duì)象拷貝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
java抓取網(wǎng)頁(yè)數(shù)據(jù)獲取網(wǎng)頁(yè)中所有的鏈接實(shí)例分享
java抓取網(wǎng)頁(yè)數(shù)據(jù)獲取網(wǎng)頁(yè)中所有的鏈接實(shí)例分享,使用方法,只要實(shí)例化HtmlParser時(shí)傳入網(wǎng)頁(yè)地址就可以了2013-12-12

