Ведущие реализаторы верны
Мы продолжаем использоватьresilience4j
Реализовать повтор, согласно предыдущей статьеSpring Cloud Upgrade Road — Hoxton — 4. Используйте Resilience4j для обеспечения изоляции и прерывания цепи на уровне экземпляра, мы загрузилиRetryReqistry
Эта основная конфигурацияBean
.
Retry
Связанная конфигурация:create-and-configure-retry
Наша конфигурация здесь:
resilience4j.retry:
configs:
default:
maxRetryAttempts: 2
waitDuration: 1
retryExceptions:
- java.lang.Exception
service-provider2:
maxRetryAttempts: 4
Обратитесь к нашему предыдущемуLoadBalancerConfig
//对于非返回200的接口,抛出异常
if (execute.status() != HttpStatus.OK.value()) {
throw new ResponseWrapperException(execute.toString(), execute);
}
Таким образом, пока все исключения настроены на повторную попытку, она также будет повторяться для возвратов, отличных от 200.
Чтобы реализовать повторную попытку, она должна быть до того, как сработает балансировщик нагрузки, потому чтоSpring-Cloud
Там может быть много связующего кода вFeignBlockingLoadBalancerClient
Метод может быть не очень масштабируемым, здесь мы используем фасетный метод для реализации повторной попытки.
CustomizedCircuitBreakerAspect
//配置哪些包下的FeignClient进行重试,必须含有@FeignClient注解
@Around("execution(* com.github.hashjang.hoxton..*(..)) && @within(org.springframework.cloud.openfeign.FeignClient)")
public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable {
boolean isGet = false;
MethodSignature signature = (MethodSignature) pjp.getSignature();
FeignClient annotation = signature.getMethod().getDeclaringClass().getAnnotation(FeignClient.class);
String serviceName = annotation.value();
if (StringUtils.isBlank(serviceName)) {
serviceName = annotation.name();
}
//查看是否是GET请求
RequestMapping requestMapping = signature.getMethod().getAnnotation(RequestMapping.class);
if (requestMapping != null &&
(requestMapping.method().length == 0 ||
Arrays.asList(requestMapping.method()).contains(RequestMethod.GET))
) {
isGet = true;
}
GetMapping getMapping = signature.getMethod().getAnnotation(GetMapping.class);
if (getMapping != null) {
isGet = true;
}
Retry retry;
try {
retry = retryRegistry.retry(serviceName, serviceName);
} catch (ConfigurationNotFoundException e) {
retry = retryRegistry.retry(serviceName);
}
if (!isGet) {
//非GET请求,只有在断路器打开的情况下,才会重试
retry = Retry.of(serviceName, RetryConfig.from(retry.getRetryConfig()).retryExceptions().retryOnException(throwable -> {
Throwable cause = throwable.getCause();
if (cause instanceof CallNotPermittedException) {
//对于断路器,不区分方法,都重试,因为没有实际调用
log.info("retry on circuit breaker is on: {}", cause.getMessage());
return true;
}
return false;
}).build());
}
//对于GET请求,启用重试机制
Supplier<Object> objectSupplier = Retry.decorateSupplier(retry, () -> {
try {
return pjp.proceed();
} catch (Throwable throwable) {
ReflectionUtils.rethrowRuntimeException(throwable);
return null;
}
});
return Try.ofSupplier(objectSupplier).get();
}
Spring Cloud Gateway реализует повторную попытку
Spring Cloud Gateway по умолчанию имеет свои собственные повторные попытки, иresilience4j
изRetry
Он несовместим с механизмом Reactor Spring Cloud Gateway, поэтому вам нужно написать дополнительный связующий код.Для простоты Spring Cloud Gateway по умолчанию имеет собственную повторную попытку. Используйте эту повторную попытку для достижения повторной попыткиFilter
Подключен к Spring Cloud Gateway.
Повторная попытка Spring Cloud GatewayFilter
пройти черезRetryGatewayFilterFactory
Реализация, мы хотим вступить в силу для каждого вызова микросервиса и сделать егоGlobalFilter
.И эта повторная попытка должна быть до того, как балансировщик нагрузки выберет экземпляр, поэтому эта повторная попытка должна быть вRouteToRequestUrlFilter
иLoadBalancerClientFilter
Перед (эти двое отвечают заlb:
Балансировщик нагрузки запроса маршрутизации для получения URL-адреса перезаписи экземпляра).
Мы хотим реализовать разные конфигурации для разных микросервисов, поэтому создаем классы конфигурацииApiGatewayRetryConfig
:
@Data
@ConfigurationProperties(prefix = "spring.cloud.gateway")
public class ApiGatewayRetryConfig {
private Map<String, RetryGatewayFilterFactory.RetryConfig> retry;
public RetryGatewayFilterFactory.RetryConfig getDefault() {
return retry.computeIfAbsent("default", key -> new RetryGatewayFilterFactory.RetryConfig());
}
}
один из нихRetryConfig
Содержит следующие свойства, которые мы используем:
Значение по умолчанию элемента конфигурации указывает максимальное количество повторных попыток3, за исключением того, какие коды ответов повторять при вызове seriesSERVER_ERROR, по умолчанию все коды ответов 5XX пусты, для каких кодов состояния повторять попытку, это конкретный HttpStatus и серия Only можно указать один methodGET, для которого HttpMethods нужно повторить. exceptionsList.of(IOException.class, TimeoutException.class) Для исключений, которые нужно повторить, по умолчанию используется исключение IO и исключение времени ожидания. Конфигурация backoffbackOff, которая определяет, как повторить попытку позже — firstBackoff5[мс] Интервал первой попытки — maxBackoff Максимальный интервал повтора — factor2 каждый интервал повтора = firstBackoff * (коэффициент ^ (times - 1)), максимальное значение maxBackoff-basedOnPreviousValuetrue основано на отсрочке последнего запроса, если да, сохраните последнее время отсрочки, следующий раз с этого времени отсрочки начните как интервал первого повтора
Наша конфигурация:
spring:
cloud:
gateway:
# 这是我们自定义的重试配置:ApiGatewayRetryConfig,
retry:
default:
retries: 1
series: SERVER_ERROR,CLIENT_ERROR
methods: GET
# 对于断路器打开也会重试
exceptions: io.github.resilience4j.circuitbreaker.CallNotPermittedException, org.springframework.cloud.gateway.support.TimeoutException, java.io.IOException
backoff:
basedOnPreviousValue: true
factor: 2
firstBackoff: 100ms
maxBackoff: 500ms