Spring Cloud升级之路

时间秒杀一切 提交于 2020-08-06 19:48:51

feign 实现重试

我们继续使用resilience4j实现重试,根据上一篇Spring Cloud升级之路 - Hoxton - 4. 使用Resilience4j实现实例级别的隔离与熔断,我们已经加载了RetryReqistry这个核心配置Bean

Retry相关的配置:create-and-configure-retry

配置项 默认值 说明
maxAttempts 3 最大重试次数,包括本身那次调用
waitDuration 500[ms] 重试间隔时间
intervalFunction numOfAttempts -> waitDuration 重试间隔时间函数,和 waitDuration 只能设置一个,默认就是 waitDuration
retryOnResultPredicate result -> false 通过结果判断是否重试,默认是只要获取到结果就不重试
retryOnExceptionPredicate throwable -> true 通过抛出的异常判断是否重试,默认是只要有异常就会重试
retryExceptions empty 配置抛出这些异常以及子类则会重试
ignoreExceptions empty 配置抛出这些异常以及子类则不会重试

我们这里的配置是:

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 默认有自己的重试,并且resilience4jRetry和 Spring Cloud Gateway 的 Reactor 机制是不兼容的,所以需要写一些额外的胶水代码,这里为了简便,就使用 Spring Cloud Gateway 默认有自己的重试。利用这个重试实现重试Filter插入到 Spring Cloud Gateway 中。

Spring Cloud Gateway 的重试Filter通过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包含如下我们使用到的属性:

配置项 默认值 说明
retries 3 最大重试次数,不包括本身那次调用
series SERVER_ERROR 对于哪些响应码重试,默认是所有的5XX响应码
statuses empty 对于哪些状态码重试,这个是具体的 HttpStatus,和 series 之间只能指定一个
methods GET 对于哪些 HttpMethod 重试。
exceptions List.of(IOException.class, TimeoutException.class) 对于哪些异常重试,默认是IO异常和超时异常
backoff backOff配置,决定之后如何重试
- firstBackoff 5[ms] 第一次重试间隔
- maxBackoff 最大重试间隔
- factor 2 每次的重试间隔 = firstBackoff * (factor ^ (次数 - 1)),最大是maxBackoff
- basedOnPreviousValue true 是否基于上次请求的 backoff,如果是,则保留上次 backoff 时间,下次从这个 backoff 时间开始作为第一次重试间隔

我们的配置:

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!