SpringCloud | FeignClient和Ribbon重试机制区别与联系
本文内容纲要:
-1)FeignClient重试机制分析:
-2)Ribbon重试机制分析:
-3)FeignClient和Ribbon重试区别与联系:
在springcloud体系项目中,引入的重试机制保证了高可用的同时,也会带来一些其它的问题,如幂等操作或一些没必要的重试。
今天就来分别分析一下FeignClient和Ribbon重试机制的实现原理和区别,主要分为三点:
1)FeignClient重试机制分析
2)Ribbon重试机制分析
3)FeignClient和Ribbon重试机制的区别于联系
1)FeignClient重试机制分析:
FeignClient重试机制的实现原理相对简单。首先看一下feignClient处理请求的拦截类:SynchronousMethodHandler,看一下该类中的代理方法invoke
:
@Override
publicObjectinvoke(Object[]argv)throwsThrowable{
//生成处理请求模板
RequestTemplatetemplate=buildTemplateFromArgs.create(argv);
//获取重试配置类
Retryerretryer=this.retryer.clone();
while(true){
try{
returnexecuteAndDecode(template);
}catch(RetryableExceptione){
//在异常里执行是否重试方法
retryer.continueOrPropagate(e);
if(logLevel!=Logger.Level.NONE){
logger.logRetry(metadata.configKey(),logLevel);
}
continue;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
上面的默认重试配置Retryer
,在其构造方法中,默认的请求次数为5次,如下:
publicDefault(){
this(100,SECONDS.toMillis(1),5);
}
- 1
- 2
- 3
- 4
判断是否重试的算法如下:
publicvoidcontinueOrPropagate(RetryableExceptione){
//重试次数大于最大请求次数,抛出异常
if(attempt++>=maxAttempts){
throwe;
}
longinterval;
if(e.retryAfter()!=null){
interval=e.retryAfter().getTime()-currentTimeMillis();
if(interval>maxPeriod){
interval=maxPeriod;
}
if(interval<0){
return;
}
}else{
interval=nextMaxInterval();
}
try{
Thread.sleep(interval);
}catch(InterruptedExceptionignored){
Thread.currentThread().interrupt();
}
sleptForMillis+=interval;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
如果要关闭或者要重写feignClient重试机制的话,可以自定义feignRetryer
,在方法中不做重试,直接抛出异常。配置如下:
/**
*@authorzhangshukang
*/
@Configuration
publicclassFeignConfig{
@Bean
RetryerfeignRetryer(){
returnnewRetryer(){
@Override
//在这里重写continueOrPropagate算法,可自定义处理方式。这里直接抛出异常,相当于不重试。
publicvoidcontinueOrPropagate(RetryableExceptione){
throwe;
}
@Override
publicRetryerclone(){
returnthis;
}
};
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
2)Ribbon重试机制分析:
首先看一下我们ribbon常用的配置,已经配置用到的地方:
ribbon:
ReadTimeout:0
ConnectTimeout:10
MaxAutoRetries:1
MaxAutoRetriesNextServer:2
OkToRetryOnAllOperations:false
- 1
- 2
- 3
- 4
- 5
- 6
这里从字面意思可以看出:
retrySameServer:重试相同实例,对应MaxAutoRetries
retryNextServer:重试下一实例,对应MaxAutoRetriesNextServer
retryEnabled:重试所有操作,对应OkToRetryOnAllOperations
这里声明一点,关于feignClient如何整合ribbon负载均衡的,之前的博客已经有完整的分析:
《SpringCloud|Feign如何整合Ribbon进行负载均衡的?》,所以下面就跳过整合部分,直接分析负载均衡模块。
publicTexecuteWithLoadBalancer(finalSrequest,finalIClientConfigrequestConfig)throwsClientException{
//获取重试机制配置:RequestSpecificRetryHandler,继续跟进该方法...
RequestSpecificRetryHandlerhandler=getRequestSpecificRetryHandler(request,requestConfig);
//这里很关键,很明显采用了命令模式,ribbon负载均衡的配置在这里传给LoadBalancerCommand类
LoadBalancerCommand<T>command=LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build();
try{
returncommand.submit(
newServerOperation<T>(){
@Override
publicObservable<T>call(Serverserver){
URIfinalUri=reconstructURIWithServer(server,request.getUri());
SrequestForServer=(S)request.replaceUri(finalUri);
try{
returnObservable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer,requestConfig));
}
catch(Exceptione){
returnObservable.error(e);
}
}
})
.toBlocking()
.single();
}catch(Exceptione){
Throwablet=e.getCause();
if(tinstanceofClientException){
throw(ClientException)t;
}else{
thrownewClientException(e);
}
}
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
@Override publicRequestSpecificRetryHandlergetRequestSpecificRetryHandler( RibbonRequestrequest,IClientConfigrequestConfig){ //这里如果配置了OkToRetryOnAllOperations为true,则所有的请求都进行重试。默认为false if(this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)){ returnnewRequestSpecificRetryHandler(true,true,this.getRetryHandler(), requestConfig); } //如果没配置的话,如果不是get请求,就关闭重试 if(!request.toRequest().method().equals("GET")){ returnnewRequestSpecificRetryHandler(true,false,this.getRetryHandler(), requestConfig); } else{ //如果是get请求,则开启重试。 returnnewRequestSpecificRetryHandler(true,true,this.getRetryHandler(), requestConfig); } }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
上述代码是对请求类型进行区分,哪些重试,哪些不重试。
区别就在于第二个参数,来看一下第二个参数具体哪里用到了,继续跟进代码如下:
publicbooleanisRetriableException(Throwablee,booleansameServer){
//如果手动配置了所有请求都重试,或者get请求时,这里开启重试。
if(this.okToRetryOnAllErrors){
returntrue;
}elseif(einstanceofClientException){
ClientExceptionce=(ClientException)e;
returnce.getErrorType()==ErrorType.SERVER_THROTTLED?!sameServer:false;
}else{
returnthis.okToRetryOnConnectErrors&&this.isConnectionException(e);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
刚刚上面提到了命令模式,属于RxJava的内容,事件驱动机制,有兴趣的可以自行研读。这里看一下上面命令模式执行类具体怎么用的:
publicObservable<T>submit(finalServerOperation<T>operation){
finalExecutionInfoContextcontext=newExecutionInfoContext();
if(listenerInvoker!=null){
try{
listenerInvoker.onExecutionStart();
}catch(AbortExecutionExceptione){
returnObservable.error(e);
}
}
//这两个变量,上面已经提到了,重试机制的关键
finalintmaxRetrysSame=retryHandler.getMaxRetriesOnSameServer();
finalintmaxRetrysNext=retryHandler.getMaxRetriesOnNextServer();
//利用RxJava生成一个Observable用于后面的回调
Observable<T>o=
//选择具体的server进行调用
(server==null?selectServer():Observable.just(server))
.concatMap(newFunc1<Server,Observable<T>>(){
@Override
//Calledforeachserverbeingselected
publicObservable<T>call(Serverserver){
context.setServer(server);
//获取这个server调用监控记录,用于各种统计和LoadBalanceRule的筛选server处理
finalServerStatsstats=loadBalancerContext.getServerStats(server);
//获取本次server调用的回调入口,用于重试同一实例的重试回调
Observable<T>o=Observable
.just(server)
.concatMap(newFunc1<Server,Observable<T>>(){
@Override
publicObservable<T>call(finalServerserver){
context.incAttemptCount();
loadBalancerContext.noteOpenConnection(stats);
if(listenerInvoker!=null){
try{
listenerInvoker.onStartWithServer(context.toExecutionInfo());
}catch(AbortExecutionExceptione){
returnObservable.error(e);
}
}
finalStopwatchtracer=loadBalancerContext.getExecuteTracer().start();
......省略部分代码
}
});
//设置针对同一实例的重试回调
if(maxRetrysSame>0)
o=o.retry(retryPolicy(maxRetrysSame,true));
returno;
}
});
//设置重试下一个实例的回调
if(maxRetrysNext>0&&server==null)
o=o.retry(retryPolicy(maxRetrysNext,false));
//异常回调
returno.onErrorResumeNext(newFunc1<Throwable,Observable<T>>(){
@Override
publicObservable<T>call(Throwablee){
if(context.getAttemptCount()>0){
if(maxRetrysNext>0&&context.getServerAttemptCount()==(maxRetrysNext+1)){
e=newClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED,
"Numberofretriesonnextserverexceededmax"+maxRetrysNext
+"retries,whilemakingacallfor:"+context.getServer(),e);
}
elseif(maxRetrysSame>0&&context.getAttemptCount()==(maxRetrysSame+1)){
e=newClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED,
"Numberofretriesexceededmax"+maxRetrysSame
+"retries,whilemakingacallfor:"+context.getServer(),e);
}
}
if(listenerInvoker!=null){
listenerInvoker.onExecutionFailed(e,context.toFinalExecutionInfo());
}
returnObservable.error(e);
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
上述代码典型的RxJava风格。
接下来是关键。o为Observable实例,类似于生产者,上面代码为Observable回调逻辑。上面有两行关键的代码:
o=o.retry(retryPolicy(maxRetrysSame,true));
o=o.retry(retryPolicy(maxRetrysNext,false));
首先看一下retryPolicy方法,这个就是ribbon重试算法的逻辑了,来看一下的实现:
privateFunc2<Integer,Throwable,Boolean>retryPolicy(finalintmaxRetrys,finalbooleansame){
returnnewFunc2<Integer,Throwable,Boolean>(){
@Override
publicBooleancall(IntegertryCount,Throwablee){
if(einstanceofAbortExecutionException){
returnfalse;
}
//判断是否继续重试
if(tryCount>maxRetrys){
returnfalse;
}
if(e.getCause()!=null&&einstanceofRuntimeException){
e=e.getCause();
}
//进入异常处理
returnretryHandler.isRetriableException(e,same);
}
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
上述代码是Ribbon判断是否重试的实现,根据我们配置的变量次数,进行判断,有异常则进入异常处理。
整体的重试机制就是将LoadBalancerCommand类中retryPolicy的重试实现逻辑,传入RxJavaObservable对象的o.retry()方法,该方法接收的参数的就是一个Function:
publicfinalObservable<T>retry(Func2<Integer,Throwable,Boolean>predicate){
returnnest().lift(newOperatorRetryWithPredicate<T>(predicate));
}
- 1
- 2
- 3
最后回过头看这两行代码,逻辑大致清晰许多,来看一下执行顺序:
o=o.retry(retryPolicy(maxRetrysSame,true));
o=o.retry(retryPolicy(maxRetrysNext,false));
- 1
- 2
执行顺序:
1)首先会先执行下面一行代码,获取负载均衡的重试配置,然后进行负载均衡,选取实例。
2)再执行上面一行代码,获取执行单个服务的重试配置,最后再执行具体的业务逻辑。
3)FeignClient和Ribbon重试区别与联系:
疑问:一个http请求,如果feign和ribbon都配置了重试机制,异常情况下一共会请求多少次?
经过上面的分析,请求总次数n为feignClient和ribbon配置参数的笛卡尔积:
n(请求总次数)=feign(默认5次)*(MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)
注意:+1是代表ribbon本身默认的请求。
其实二者的重试机制相互独立,并无联系。但是因为用了feign肯定会用到ribbon,所以feign的重试机制相对来说比较鸡肋,自己feignClient的时候一般会关闭该功能。ribbon的重试机制默认配置为0,也就是默认是去除重试机制的,建议不要修改。如果配置不当,会因为幂等请求带来数据问题。所以建议关闭二者的重试功能。
如果开启的话,建议合理配置Hystrix的超时时间,在一些没必要的重试请求执行时,根据Hystrix的超时时间,快速失败,结束重试。
友链:探果网
本文内容总结:1)FeignClient重试机制分析:,2)Ribbon重试机制分析:,3)FeignClient和Ribbon重试区别与联系:,
原文链接:https://www.cnblogs.com/tiancai/p/9621800.html