Resilience4j+Feign实现熔断,fallback

FeignClient Hystrix超时重试降级讲了Hystrix和feign各自的超时,重试,降级策略。然后Hystrix其实已经不再维护了,社区推荐Resilience4j,阿里有一个开源的sentinel也可以做到熔断限流等功能。网上有一个表格图片,对比了三者的不同。
Resilience4j+Feign实现熔断,fallback_第1张图片
我自己在实际使用中的情况是,服务间调用使用Feign,没有用其他RPC框架,然后想做熔断策略,又不想用Hystrix,毕竟不维护了,所以在考虑Resilience4j 和sentinel。然后对比了之后发现sentinel可以无缝切换Hystrix+Feign。

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
   <version>0.2.1.RELEASE</version>
</dependency>
@Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.sentinel.enabled", matchIfMissing = true)
    public Feign.Builder sentinelHystrixBuilder() {
        return SentinelFeign.builder();
    }

但是我们进入SentinelFeign里面会发现,这种配置只实现了基础的fallback,如果想做到限流,熔断,还需要配合其dashboard,但是现实情况是,我们的熔断并不怎么变化,只是一种以防万一,不需要动态配置。那么虽然dashboard很轻量,还是有点不合适。

注:这里说的sentinel只能用dashboard做熔断,限流配置是因为使用SentinelFeign,实际上的sentinel是可以使用代码写死规则的

然后考虑Resilience4j,这个网上搜了一下,没啥实际使用的感觉,至少在我这个实用主义来看,那些demo代码都用不上,然后看了github,有个Resilience4j-feign,然后我就试用了一下。

那么果然可以在代码里配置熔断的策略,可以配置

	@Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Resilience4jFeign.Builder feignResilience4jBuilder() {
    CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
        RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");
        FeignDecorators decorators = FeignDecorators.builder()
                                         .withRateLimiter(rateLimiter)
                                         .withCircuitBreaker(circuitBreaker)
                                         .build();
        return Resilience4jFeign.builder(decorators);
    }

那么就想更进一步,能不能利用上次FeignClient Hystrix超时重试降级中的MyHystrixAnno 注解,增加一些自定义的配置呢?至少git上官方代码不行了。然后就对比学习了一下HystrixFeign,SentinelFeign,Resilience4jFeign,发现了一些共同点,然后自己改造了一波,记录如下。

另外:Resilience4jFeign里很多类的访问级别是包内,或者是final class的,所以我改造的时候只能copy出来改造。

使用Resilience4j-feign需要添加如下maven配置

  <dependency>
			<groupId>io.github.resilience4j</groupId>
			<artifactId>resilience4j-feign</artifactId>
			<version>1.1.0</version>
		</dependency>

1、从FeignClientFactoryBean#feign方法来看,feign的一些设置参数可以通过Builder来设置,这一点可以从以上三个框架里看出来

HystrixFeign

public static final class Builder extends Feign.Builder 

SentinelFeign

public static final class Builder extends feign.Feign.Builder implements ApplicationContextAware 

Resilience4jFeign

@Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public ZpzengResilience4jFeign.Builder feignResilience4jBuilder() {
        return ZpzengResilience4jFeign.builder();
    }
 public static final class Builder extends Feign.Builder 

ZpzengResilience4jFeign

public class ZpzengResilience4jFeign{

    public static ZpzengResilience4jFeign.Builder builder() {
        return new ZpzengResilience4jFeign.Builder();
    }

    public static final class Builder extends Feign.Builder implements ApplicationContextAware {

        private ZpzengCircuitBreakerFactory circuitBreakerFactory = new ZpzengCircuitBreakerFactory.Default();
        private ApplicationContext applicationContext;
        private FeignContext feignContext;

        /**
         * Will throw an {@link UnsupportedOperationException} exception.
         */
        @Override
        public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Feign build() {
            super.invocationHandlerFactory(
                    (target, dispatch) -> new ZpzengDecoratorInvocationHandler(target, dispatch, circuitBreakerFactory,feignContext));
            return super.build();
        }

        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
            this.feignContext = (FeignContext)this.applicationContext.getBean(FeignContext.class);
        }
    }
}

2、有一个实现InvocationHandler接口的调用处理器
Hystrix:HystrixInvocationHandler
Resilience4j:DecoratorInvocationHandler
sentinel:
Resilience4j+Feign实现熔断,fallback_第2张图片
ZpzengResilience4jFeign:ZpzengDecoratorInvocationHandler主要是copy了Resilience4j:DecoratorInvocationHandler,修改了其中的构造方法,以及decorateMethodHandlers方法。

decorateMethodHandlers方法和HystrixInvocationHandler中的toSetters方法有异曲同工之妙,是通过类target,方法method来获取不同的配置。而sentinel就没有这个,所以除了下面的ZpzengDecoratorInvocationHandler,还仿照Hystrix的SetterFactory写了ZpzengCircuitBreakerFactory

public class ZpzengDecoratorInvocationHandler implements InvocationHandler {

    private final Target<?> target;
    private final Map<Method, CheckedFunction1<Object[], Object>> decoratedDispatch;

    public ZpzengDecoratorInvocationHandler(Target<?> target,
                                      Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
                                           ZpzengCircuitBreakerFactory circuitBreakerFactory,
                                           FeignContext feignContext) {
        this.target = checkNotNull(target, "target");
        checkNotNull(dispatch, "dispatch");
        this.decoratedDispatch = decorateMethodHandlers(dispatch, circuitBreakerFactory, target,feignContext);
    }

    private Map<Method, CheckedFunction1<Object[], Object>> decorateMethodHandlers(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
                                                                                   ZpzengCircuitBreakerFactory circuitBreakerFactory, Target<?> target,
                                                                                   FeignContext feignContext) {
        final Map<Method, CheckedFunction1<Object[], Object>> map = new HashMap<>();
        for (final Map.Entry<Method, InvocationHandlerFactory.MethodHandler> entry : dispatch.entrySet()) {
            final Method method = entry.getKey();
            final InvocationHandlerFactory.MethodHandler methodHandler = entry.getValue();
            map.put(method, circuitBreakerFactory.create(target,method,feignContext).decorate(methodHandler::invoke, method, methodHandler, target));
        }
        return map;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args)
            throws Throwable {
        switch (method.getName()) {
            case "equals":
                return equals(args.length > 0 ? args[0] : null);

            case "hashCode":
                return hashCode();

            case "toString":
                return toString();

            default:
                break;
        }

        return decoratedDispatch.get(method).apply(args);
    }

    @Override
    public boolean equals(Object obj) {
        Object compareTo = obj;
        if (compareTo == null) {
            return false;
        }
        if (Proxy.isProxyClass(compareTo.getClass())) {
            compareTo = Proxy.getInvocationHandler(compareTo);
        }
        if (compareTo instanceof ZpzengDecoratorInvocationHandler) {
            final ZpzengDecoratorInvocationHandler other = (ZpzengDecoratorInvocationHandler) compareTo;
            return target.equals(other.target);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return target.hashCode();
    }

    @Override
    public String toString() {
        return target.toString();
    }
}

值得注意的是,我这里使用FeignContext是为了获取fallback,如果不需要fallback的话就不用了;那么如果不配置fallback,触发了熔断的时候会报错的,说该方法调用处于OPEN状态,不会和Feign共用一个fallback,这点和Hystrix不一样;同时这里处理可以配置熔断,还可以配置限流策略等等。

public interface ZpzengCircuitBreakerFactory {

    FeignDecorator create(Target<?> target,Method method, FeignContext feignContext);

    final class Default implements ZpzengCircuitBreakerFactory {

        @Override
        public FeignDecorator create(Target<?> target,Method method, FeignContext feignContext) {
            String name = target.type().getName();
            //TODO 从MyHystrixAnno 中获取配置多种参数
            //MyHystrixAnno hystrix = target.type().getAnnotation(SubaoHystrix.class);
            MyHystrixAnno hystrix = method.getAnnotation(SubaoHystrix.class);
            CircuitBreakerConfig circuitBreakerConfig = new CircuitBreakerConfig.Builder()
                    .failureRateThreshold(50)//熔断器关闭状态和半开状态使用的同一个失败率阈值
                    .slidingWindow(5,2, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                    .enableAutomaticTransitionFromOpenToHalfOpen()//如果置为true,当等待时间结束会自动由打开变为半开,若置为false,则需要一个请求进入来触发熔断器状态转换
                    .build();
            //获取fallback
            FeignClient client = target.type().getAnnotation(FeignClient.class);
            return FeignDecorators.builder()
                    .withCircuitBreaker(CircuitBreaker.of(name, circuitBreakerConfig))
                    .withFallback(feignContext.getInstance(name,client.fallback()))
                    .build();
        }
    }
}

3、然后我们来梳理一下整个执行流程。

  • 对于每个服务,比如PromotionService,在系统启动的时候会创建不同的ZpzengResilience4jFeign,然后会调用ZpzengCircuitBreakerFactory#create方法创建不同的配置,例如熔断,限流配置,这些配置在FeignDecorators中的list中,而且这些配置都是实现了FeignDecorator接口
@FunctionalInterface
public interface FeignDecorator {
    CheckedFunction1<Object[], Object> decorate(CheckedFunction1<Object[], Object> invocationCall, Method method, MethodHandler methodHandler,
            Target<?> target);
}
public Builder withCircuitBreaker(CircuitBreaker circuitBreaker) {
            decorators.add((fn, m, mh, t) -> CircuitBreaker.decorateCheckedFunction(circuitBreaker, fn));
            return this;
        }
  • 当执行到PromotionService中的方法时,调用InvocationHandler#invoke方法,通过代码我们看到,其实就是调用我们配置的不同的FeignDecorators,然后循环执行decorator
private Map<Method, CheckedFunction1<Object[], Object>> decorateMethodHandlers(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
                                                                                   SubaoCircuitBreakerFactory circuitBreakerFactory, Target<?> target,
                                                                                   FeignContext feignContext) {
        final Map<Method, CheckedFunction1<Object[], Object>> map = new HashMap<>();
        for (final Map.Entry<Method, InvocationHandlerFactory.MethodHandler> entry : dispatch.entrySet()) {
            final Method method = entry.getKey();
            final InvocationHandlerFactory.MethodHandler methodHandler = entry.getValue();
            //circuitBreakerFactory#create方法创建了不同的FeignDecorators
            //decoreate方法是循环执行FeignDecorators中的decorator,
            map.put(method, circuitBreakerFactory.create(target,feignContext).decorate(methodHandler::invoke, method, methodHandler, target));
        }
        return map;
    }

 @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args)
            throws Throwable {
            //获取FeignDecorators
        return decoratedDispatch.get(method).apply(args);
    }
  • 循环执行decorator,对于CircuitBreaker来说,就是执行CircuitBreaker#decorateCheckedFunction方法,针对成功或者异常,进行OnSuccess处理,或者OnError处理

4、来测试一把,可以看到前两次请求有MonitorInterceptor日志,因为我在Service上加了aop拦截,后面两次直接走到fallback中,说明请求被拦截下来,没有走Service。
Resilience4j+Feign实现熔断,fallback_第3张图片

5、以上都是在resilience4j-feign的基础上弄的,支持的功能也只是熔断,限流,Fallback,那如果要去实现其他组件的功能,比如限时timelimiter,需要额外新加maven,然后copy一堆代码出来,既然如此可以fork一下这个分支,在自己的分支上去做改造。改造点除了上面罗列的,还有就是FeignDecorators,增加timerlimiter包装成Decorate的方法,类似于

 public Builder withCircuitBreaker(CircuitBreaker circuitBreaker) {
            decorators.add((fn, m, mh, t) -> CircuitBreaker.decorateCheckedFunction(circuitBreaker, fn));
            return this;
        }

你可能感兴趣的:(java,spring,中间件)