为了防止网络抖动问题,需要进行重试处理,重试达到阈值后进行告警通知,做到问题及时响应
类型 | 同步、异步 | 是否支持声明式调用(注解) | 是否支持监控 |
---|---|---|---|
resilience4j-retry | 同步 | 是 | 是 |
Guava Retry | 同步 | 否 | 否,可通过监听器自行实现监控统计 |
Spring Retry | 同步 | 是 | 否,可通过监听器自行实现监控统计 |
基于以上方案的对比,选择了使用resilience4j-retry,主要基于以下两点:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
// 对应@Retry注解的name属性
resilience4j.retry.instances.sendConfirmEmail.max-attempts=3
@Retry(name= "sendConfirmEmail",fallbackMethod = "sendConfirmEmailFallback")
public void sendConfirmEmail(SsoSendConfirmEmailDTO ssoSendConfirmEmail) {
//省略方法内容
throw new ServiceException("send confirm email error");
}
public void sendConfirmEmailFallback(SsoSendConfirmEmailDTO ssoSendConfirmEmail,ServiceException e){
//发送邮件通知
}
public class SendEmailIntervalBiFunction implements IntervalBiFunction<Integer> {
private final Duration waitDuration = Duration.ofSeconds(1);
@Override
public Long apply(Integer numOfAttempts, Either<Throwable, Integer> either) {
return numOfAttempts * waitDuration.toMillis();
}
}
resilience4j.retry.instances.sendConfirmEmail.interval-bi-function=com.xxx.xxx.retry.SendEmailIntervalBiFunction
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RetryTest {
@Resource
private UserApiService userApiService;
@Test
public void testRetryThreeTimes() throws InterruptedException {
SsoSendConfirmEmailDTO ssoSendConfirmEmailDTO = null;
userApiService.sendConfirmEmail(ssoSendConfirmEmailDTO);
}
}
@Around(value = "matchAnnotatedClassOrMethod(retryAnnotation)", argNames = "proceedingJoinPoint, retryAnnotation")
public Object retryAroundAdvice(ProceedingJoinPoint proceedingJoinPoint,
@Nullable Retry retryAnnotation) throws Throwable {
//根据name创建Retry实现类:RetryImpl ---> Retry retry = retryRegistry.retry(backend)
io.github.resilience4j.retry.Retry retry = getOrCreateRetry(methodName, backend);
// 根据@Retry注解的fallbackMethod创建FallbackMethod -->FallbackMethod#create
FallbackMethod fallbackMethod = FallbackMethod
.create(fallbackMethodValue, method, proceedingJoinPoint.getArgs(),
proceedingJoinPoint.getTarget());
//重试处理:RetryAspect#proceed -->最终触发RetryImpl#executeCheckedSupplier
return fallbackDecorators.decorate(fallbackMethod,
() -> proceed(proceedingJoinPoint, methodName, retry, returnType)).apply();
}
static <T> CheckedFunction0<T> decorateCheckedSupplier(Retry retry,
CheckedFunction0<T> supplier) {
return () -> {
//获取重试上下文:RetryImpl$ContextImpl
Retry.Context<T> context = retry.context();
do {
try {
// 调被@Retry修饰的业务方法
T result = supplier.apply();
final boolean validationOfResult = context.onResult(result);
if (!validationOfResult) {
context.onComplete();
return result;
}
} catch (Exception exception) {
context.onError(exception);
}
} while (true);
};
}
private void throwOrSleepAfterException() throws Exception {
int currentNumOfAttempts = numOfAttempts.incrementAndGet();
Exception throwable = lastException.get();
// 如果重试次数超过阈值,则抛出异常
if (currentNumOfAttempts >= maxAttempts) {
failedAfterRetryCounter.increment();
publishRetryEvent(
() -> new RetryOnErrorEvent(getName(), currentNumOfAttempts, throwable));
throw throwable;
} else {
// 在重试范围内,则sleep间隔时间
waitIntervalAfterFailure(currentNumOfAttempts, Either.left(throwable));
}
}
//重试后成功次数
private final LongAdder succeededAfterRetryCounter;
// 重试后失败次数(超过阈值后还是失败)
private final LongAdder failedAfterRetryCounter;
// 没有重试就成功的次数
private final LongAdder succeededWithoutRetryCounter;
// 没有重试就失败的次数(不是可重试的异常)
private final LongAdder failedWithoutRetryCounter;
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.7.1</version>
</dependency>
3.2 配置actutor开放premethus采集接口management.server.port=9099
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=health,prometheus