Hystrix Feign 动态URL及熔断隔离

SpringCloud Feign Hystrix

背景

根据某确定协议,访问不同服务,这时候接口规范是固定的,但请求URL是配置的,又想用Feign声明式的接口风格。当一个服务不可用时,不影响其他服务调用。
即:

  1. 动态配置 URL
  2. 根据不同 URL 熔断隔离

动态URL

指定url的写法

@FeignClient(name = "XXX-CLIENT", url = "${api.xxx.url}",
        fallbackFactory = XXXFallbackFactory.class,
        configuration = FeignConfiguration.class)
public interface XXXClient {

    @PostMapping("query_token")
    String queryToken(@RequestBody Request request);

    @PostMapping("query_data")
    String queryData(@RequestHeader("token") String token, @RequestBody Request request);
}

更改为:

@FeignClient(name = "XXX-CLIENT", url = "placeholder", //URL以占位符配置(可以是任何字符串,不配会报错)
        fallbackFactory = XXXFallbackFactory.class,
        configuration = FeignConfiguration.class)
public interface XXXClient {

     //第一个参数使用URI,动态传入
    @PostMapping("query_token")
    String queryToken(URI uri, @RequestBody Request request); 

    @PostMapping("query_data")
    String queryData(URI uri, @RequestHeader("token") String token, @RequestBody Request request);
}

根据URL熔断隔离

FeignClient 的实例化过程

  1. 在 FeignClientsRegistrar 中扫描所有 @FeignClient 注解的类。
  2. 注册类型信息,BeanDefinitionBuilder 设置为 FeignClientFactoryBean。
  3. FeignClientFactoryBean 中 getObject 方法实例化 Feign 对象。
  4. 实例 Feign 对象时调用 Targeter.target 方法,其子类 HystrixTargeter 在 target 方法构建对象时,设置 InvocationHandlerFactory 返回 HystrixInvocationHandler 对象。

FeignClient 的调用过程

  1. 进入代理类 HystrixInvocationHandler 调用其 invoke 方法。
  2. invoke 方法创建 HystrixCommand 执行 execute 方法。
  3. execute 过程中如果 hystrixCircuitBreaker.attemptExecution 或是 tryAcquire 返回 false 执行短路 fallback 方法

HystrixCircuitBreaker 如何实现不同接口隔离?

HystrixCircuitBreaker.Factory 里有个 ConcurrentHashMap<String, HystrixCircuitBreaker> 其 key 为 CommandKey,value 为 HystrixCircuitBreaker。

  1. HystrixCircuitBreaker.Factory.getInstance() 根据不同的 CommandKey 创建熔断器对象
  2. AbstractCommand 在调用 initCircuitBreaker 传入 CommandKey
  3. 实例化 HystrixCommand 时传入 commandKey
  4. HystrixInvocationHandler 初始化时候生成 CommandKey

需要自定义 commandKey 生成规则,而 HystrixInvocationHandler 非 public 而且为 final 无法通过继承扩展。自定义 HystrixInvocationHandler。

/**
 * @author ying.chen2
 * @date 2019/12/26
 */
class DynamicUrlHystrixInvocationHandler implements InvocationHandler {

     @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...
        HystrixCommand.Setter setter = setterMethodMap.get(method);

        Optional<Object> anyUri = Stream.of(args).filter(item -> item instanceof URI).findAny();
        if (anyUri.isPresent()) {
            URI uri = (URI) anyUri.get();
            // 根据URL自定义 CommandKey 规则
            setter.andCommandKey(HystrixCommandKey.Factory.asKey(convertCommandKey(uri.toString(), method)));
        }

        HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setter) {
            ...
        }
        ...
    }
}

自己生成FeignClient

/**
 * 动态FeignClient
 * @author ying.chen2
 * */
@Component
@Import(FeignConfiguration.class)
public class DynamicUrlFeignClientBuilder<T> {

    /**
     * 构建feignBuilder
     */
    private Feign.Builder feignBuilder;

    @Autowired
    public DynamicUrlFeignClientBuilder(Decoder decoder, Encoder encoder, Logger.Level level, FeignLoggerFactory feignLoggerFactory, Contract contract) {
        feignBuilder = Feign.builder()
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .logLevel(level)
                .logger(feignLoggerFactory.create(CecClient.class));

    }

    public T builder(Class<T> tClass, FallbackFactory fallbackFactory){
        SetterFactory setterFactory = new SetterFactory.Default();
        // 绑定自定义 DynamicUrlHystrixInvocationHandler
        InvocationHandlerFactory invocationHandlerFactory = (target, dispatch) -> new DynamicUrlHystrixInvocationHandler(target, dispatch, setterFactory, fallbackFactory);
        feignBuilder.invocationHandlerFactory(invocationHandlerFactory);
        FeignClient annotation = tClass.getAnnotation(FeignClient.class);
        Target.HardCodedTarget<T> hardCodedTarget = new Target.HardCodedTarget(tClass, annotation.name(), annotation.url());
        return feignBuilder.target(hardCodedTarget);
    }
}

总结

  1. 通过参数传入 URI 的方式实现动态 URL
  2. 自定义 InvocationHandler 自定义 CommandKey 的生成规则
  3. 将自定义的 InvocationHandler 配置至 Feign 的 InvocationHandlerFactory 属性
  4. 调用时用 Builder 构建 Feign 代替 @Autowired

同理,你也可以通过自定义 threadPoolKey 来实现线程池隔离


2020-01-13补充:
不难看出,上述做法只是将 feign-hystrix 源码做了些定制开发,那么,直接改源码就可以实现使用者无感知的改动。
更改 feign-hystrix 源码的实现:https://github.com/chenying1520/cy-feign

这样就只需要更改依赖即可:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-hystrix</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>cy-feign-hystrix</artifactId>
    <version>9.5.0</version>
</dependency>

如果你想自定义,也可以自己实现 InvocationRuntimeSetterFactory

创建于2019年12月30日 15:35
阅读量 903
留言列表

暂时没有留言

添加留言