前言
首先声明一点,这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。
feign重试
feign的重试机制默认是关闭的,源码如下
//FeignClientsConfiguration.java
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
当没有spring容器中不存在retryer这个实例的时候,会初始化这个bean, NEVER_RETRY
如何开启
@Bean
public Retryer feignRetryer() {
return new Retryer.Default();
}
在你的配置类中,添加如上代码,当然你也可以自定义Retryer。 默认重试5次
源码实现
代码入口: feign.SynchronousMethodHandler
@Override
public Object invoke(Object[] argv) throws Throwable {
// 获取请求模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 克隆当前的重试对象
Retryer retryer = this.retryer.clone();
while (true) { // 一个死循环
try {
// 执行请求
return executeAndDecode(template);
} catch (RetryableException e) {
// 出现异常,retryer.continueOrPropagate进行重试
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
步骤说明:
1.获取请求模板
2.获取重试对象
3.开启一个死循环,执行请求,如果出现异常,则被retryer处理。这个时候就要看retryer的具体实例了,如果是Retryer.NEVER_RETRY 可以看一下他的实现
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
// 直接抛了个异常
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
看一下Retryer.Default的实现
public void continueOrPropagate(RetryableException e) {
// maxAttempts =5 , 如果重试次数大于5了,则直接抛个 异常出去
if (attempt++ >= maxAttempts) {
throw e;
}
long interval;
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 (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
sleptForMillis += interval;
}
综上所述,上面那个while循环什么情况下会直接退出?
1.重试对象里面抛了个异常出来,while循环退出。
2.执行请求成功,直接return出去
3.开启了hystrix,hystrix超时,则直接熔断
ribbon的重试机制
ribbon的重试机制是默认重试一次。
属性 备注
ribbon.MaxAutoRetrues 重试相同的服务,默认次数为1
ribbon.MaxAutoRetruesNextServer 重试下一台服务,默认为1
ribbon.connectTimeout 连接超时时间
ribbon.readTimeout 读取数据超时
ribbon.okToRetryOnAllOperations 无论是超时还是connet异常,统统重试,默认为false,
源码实现
代码入口:AbstractLoadBalancerAwareClient
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 获取请求重试handler
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
// 构建ribbon的负载均衡请求
LoadBalancerCommand
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build();
try {
// 执行请求
return command.submit(
new ServerOperation
@Override
public Observable
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);
}
}
}
上面那么多行,值需要注意其中一行就好了
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
1
由于这个ribbon是结合feign使用的,所以这个获取重试机制的方法是实现在下面这个类里面:
//org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.java
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
RibbonRequest request, IClientConfig requestConfig) {
// 1. 从ribbon的配置里面获取OkToRetryOnAllOperations的配置,默认为false,如果配置为true
if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations,
false)) {
//
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
// 2. 判断请求方式是否是POST请求
if (!request.toRequest().method().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
requestConfig);
}
else {
// 3. 默认的重试handler
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
}
从上面的代码可以看到,根据不同的条件,构建不一样的RequestSpecificRetryHandler,在这之前,有必要了解一下RequestSpecificRetryHandler的四个参数的
含义。
public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, IClientConfig requestConfig);
1
okToRetryOnConnectErrors : 在出现connectErrors的时候,进行重试
okToRetryOnAllErrors : 只要发现实例不可用时,都进行重试
baseRetryHandler :负载均衡重试的handler,使用的是默认的DefaultLoadBalancerRetryHandler
requestConfig : 配置
了解到这些参数的作用是,咱们再来看一下,ribbon的重试
步骤说明:
1.从ribbon的配置里面获取OkToRetryOnAllOperations的配置,默认为false,如果配置为true,那么设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = true , 也就是说 配置了这个,只要发现实例不可达,那么都会进行重试
2.判断请求方式是否是POST请求,如果是POST 请求,则设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = false , 这个设置的意思,只要在
出现connectErrors的时候才会进行重试。
3.第三步,基本上表示该请求为GET请求了,这个时候是设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = true , 跟第一步的效果一样。
总结:
默认OkToRetryOnAllOperations为false, 所以上面的第一步一般是不会执行, 也就是说,POST请求会走上面的第二步,GET请求是走上面的第三步。
默认ribbon重试一次,POST请求默认是在connect异常的时候会重试,GET请求是都会进行重试,包括请求超时之类的。所以尽量不要用GET请求做增加,修改,删除的操作,