持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
Spring Retry:Spring提供的一种重试机制的解决方案,包括:组成部分、重试策略、核心逻辑、源码解析、使用方法
Retry
Spring提供的一种重试机制的解决方案 github.com/spring-proj…
组成部分
RetryOperations:定义基本操作集,以执行具有可配置重试行为的操作 RetryTemplate:RetryOperations的实现,简化操作执行的模板类 RetryCallback:重试回调 RecoveryCallback:重试耗尽时回调 RetryContext:回调上下文,如果需要,它可以作为一个属性包来存储迭代期间的数据 RetryPolicy:重试策略 RetryListener:对整个Retry过程进行监听(open、onError、close) RetryContextCache:为RetryContext提供存储策略 RetryState:有状态重试,跨重试的多个调用标识状态,要标识状态
重试策略
SimpleRetryPolicy:默认将对所有异常进行尝试,最多尝试3次 AlwaysRetryPolicy:一直重试,直到成功为止 NeverRetryPolicy:从不重试 TimeoutRetryPolicy:指定时间范围内进行重试,直到超时为止,默认的超时时间是1000毫秒。 ExceptionClassifierRetryPolicy:基于异常来判断是否需要进行重试 CircuitBreakerRetryPolicy:包含了断路器功能的RetryPolicy CompositeRetryPolicy:组合多个RetryPolicy BackOffPolicy:定义在两次尝试之间需要间隔的时间 FixedBackOffPolicy:将在两次重试之间进行一次固定的时间间隔,默认1秒 ExponentialBackOffPolicy:使每一次尝试的间隔时间指数性增长(参数:初始间隔时间、倍数、最大间隔时间) ExponentialRandomBackOffPolicy:在ExponentialBackOffPolicy的基础上随机增长10% UniformRandomBackOffPolicy:每次都随机的产生一个间隔时间
核心逻辑
- RetryLoadBalancerInterceptor.intercept:HTTP请求的负载均衡重试拦截器
- this.lbRetryFactory.createRetryPolicy:创建重试策略;当前实例的重试次数、切换实例的重试次数、状态码重试等
- createRetryTemplate:创建重试模板;定义重试策略、回退策略、监听器、重试上下文等
- RetryTemplate.doExecute:失败重试核心逻辑,回调业务逻辑
- retryCallback.doWithRetry:选择服务实例,创建HTTP请求,执行请求,指定错误码重试
- registerThrowable:记录重试失败的状态信息
- canRetry(retryPolicy, context):判定重试
- backOffPolicy.backOff:执行最后尝试失败逻辑
源码
org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor.java
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
// 重试策略
final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory.createRetryPolicy(serviceName, this.loadBalancer);
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
// 调用RetryTemplate.doExecute
return template.execute(context -> {
// 业务和重试逻辑
// 选择服务实例
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = this.loadBalancer.choose(serviceName);
}
// 拦截器执行HTTP请求处理
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
.execute(serviceName, serviceInstance,
this.requestFactory.createRequest(request, body, execution));
int statusCode = response.getRawStatusCode();
// 指定HTTP状态码重试
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
response.close();
throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy);
}
return response;
}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
// 最后尝试失败后处理逻辑
@Override
protected ClientHttpResponse createResponse(ClientHttpResponse response,
URI uri) {
return response;
}
});
}
org.springframework.retry.support.RetryTemplate.java
// 如果执行失败,根据重试策略执行retryCallback,否则执行recoveryCallback
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
// 重试策略
RetryPolicy retryPolicy = this.retryPolicy;
// 回退策略
BackOffPolicy backOffPolicy = this.backOffPolicy;
// 重试策略上下文,count:重试次数
RetryContext context = open(retryPolicy, state);
// 确保context对于clients需要的时候全局可用
RetrySynchronizationManager.register(context);
Throwable lastException = null;
// 重试耗尽
boolean exhausted = false;
try {
/*
* We allow the whole loop to be skipped if the policy or context already
* forbid the first try. This is used in the case of external retry to allow a
* recovery in handleRetryExhausted without the callback processing (which
* would throw an exception).
*/
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
lastException = null;
// 回调业务及重试逻辑
return retryCallback.doWithRetry(context);
} catch (Throwable e) {
lastException = e;
try {
registerThrowable(retryPolicy, state, context, e);
} catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable", ex);
} finally {
doOnErrorInterceptors(retryCallback, context, e);
}
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
// 回调最后尝试失败逻辑
backOffPolicy.backOff(backOffContext);
}
}
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
exhausted = true;
// 最后尝试失败后要采取的措施。如果有状态,请清理缓存;如果有恢复回调,请执行该回调并返回其结果;否则抛出异常。
return handleRetryExhausted(recoveryCallback, context, state);
} catch (Throwable e) {
throw RetryTemplate.<E>wrapIfNecessary(e);
} finally {
close(retryPolicy, context, state, lastException == null || exhausted);
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
}
}
使用
- 引入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
- 配置
@Bean
public RetryTemplate simpleRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy());
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(3000);
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(15000);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
- 声明式
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
@Bean public RetryListener retryListener() {
return new RetryListener() {...}
}
}
@Service
class Service {
@Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
public service() {
// ... do something
}
@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service1() {
...
}
}