前言

在咱们公司里,不同的服务之间经过Feign进行远程调用,可是,咱们在测验使调用可重试时遇到了一个小问题,Feign框架自身能够装备的自己的重试机制,可是它是一刀切的方式,所有的调用都是同样的机制,没有办法像咱们希望的那样在每个办法的基础上装备。不过我在项目中探索除了一种新的写法,经过spring-retry框架集合Feign去完成重试机制,能够为每个调用完成不同的重试机制,那究竟是怎么做到的呢,持续往下看呀。

欢迎重视个人公众号『JAVA旭阳』交流交流

自定义注解@FeignRetry

为了解决上面提到的问题,让Feign调用的每个接口单独装备不同的重试机制。咱们运用了面向切面编程并编写了一个自定义注解:@FeignRetry。此注释的工作方式类似于@Retryable的包装器,并与其同享相同的规范以防止混淆。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignRetry {
    Backoff backoff() default @Backoff();
    int maxAttempt() default 3;
    Class<? extends Throwable>[] include() default {};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Backoff {
    long delay() default 1000L;;
    long maxDelay() default 0L;
    double multiplier() default 0.0D;;
}

FeignRetryAspect切面处理@FeignRetry注解。

Slf4j
@Aspect
@Component
public class FeignRetryAspect {
    @Around("@annotation(FeignRetry)")
    public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getCurrentMethod(joinPoint);
        FeignRetry feignRetry = method.getAnnotation(FeignRetry.class);
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setBackOffPolicy(prepareBackOffPolicy(feignRetry));
        retryTemplate.setRetryPolicy(prepareSimpleRetryPolicy(feignRetry));
        // 重试
        return retryTemplate.execute(arg0 -> {
            int retryCount = arg0.getRetryCount();
            log.info("Sending request method: {}, max attempt: {}, delay: {}, retryCount: {}",
                    method.getName(),
                    feignRetry.maxAttempt(),
                    feignRetry.backoff().delay(),
                    retryCount
            );
            return joinPoint.proceed(joinPoint.getArgs());
        });
    }
    private BackOffPolicy prepareBackOffPolicy(FeignRetry feignRetry) {
        if (feignRetry.backoff().multiplier() != 0) {
            ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
            backOffPolicy.setInitialInterval(feignRetry.backoff().delay());
            backOffPolicy.setMaxInterval(feignRetry.backoff().maxDelay());
            backOffPolicy.setMultiplier(feignRetry.backoff().multiplier());
            return backOffPolicy;
        } else {
            FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
            fixedBackOffPolicy.setBackOffPeriod(feignRetry.backoff().delay());
            return fixedBackOffPolicy;
        }
    }
    private SimpleRetryPolicy prepareSimpleRetryPolicy(FeignRetry feignRetry) {
        Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<>();
        policyMap.put(RetryableException.class, true);  // Connection refused or time out
        policyMap.put(ClientException.class, true);     // Load balance does not available (cause of RunTimeException)
        if (feignRetry.include().length != 0) {
            for (Class<? extends Throwable> t : feignRetry.include()) {
                policyMap.put(t, true);
            }
        }
        return new SimpleRetryPolicy(feignRetry.maxAttempt(), policyMap, true);
    }
    private Method getCurrentMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }
}

捕获FeignRetry注解的办法,将装备传递给Spring RetryTemplate,根据装备调用服务。

@FeignRetry 的运用

用法很简单,只需将注释放在咱们希望重试机制处于活动状态的 Feign Client 办法上即可。自定义切面的用法类似于Spring自带的@Retryable注解。

@GetMapping
@FeignRetry(maxAttempt = 3, backoff = @Backoff(delay = 500L))
ResponseEntity<String> retrieve1();
@GetMapping
@FeignRetry(maxAttempt = 6, backoff = @Backoff(delay = 500L, maxDelay = 20000L, multiplier = 4))
ResponseEntity<String> retrieve2();

另外还需要在应用程序类中运用 @EnableRetry 注释来启动重试,比如能够加载SpringBoot的启动类中。

总结

Feign重试其实是一个很常见的场景,咱们本文经过了自定义了一个@FeignRetry注解来完成可重试的机制,针对不同的Feign接口还能够运用不同的重试策略,是不是很便利,快在你的项目顶用起来吧。

欢迎重视个人公众号『JAVA旭阳』交流交流