小言_互联网的博客

[享学Feign] 十二、Feign通过feign-hystrix模块使其拥有熔断、降级能力

310人阅读  评论(0)

极致无处不在,可以小到调整一行代码提交,可以大到整个研发流程。极致可以运用在技术上,也可体现在团队管理…

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/feign-learning

前言

此篇非常重要。这几个大字我放在第一行,是想强调断路器、熔断降级在微服务体系中的重要性。

由于Feign几乎是Spring Cloud技术栈中Http远程通信HC的唯一选择(RestTemplate + Nginx方式做负载的case应该很少吧~),所以实际场景中均是Feign和Hystrix一起集成使用而并不是让Feign裸奔。

为此,Feign也提供feign-hystrix这个子模块,让使用者可以非常方便的做到Feign和Hystrix的集成。另外一点,他俩均属于Netflix套件,所以融合起来还是蛮顺滑的。

说明:在开始本篇文章之前,希望你已经掌握了Hystrix的使用。可参考Hystrix专栏:三十六、Hystrix请求命令:HystrixCommand和HystrixObservableCommand


正文

在微服务场景中,通常会有很多层的服务调用。如果一个底层服务出现问题,故障会被向上传播给用户。因此我们需要一种机制,当底层服务不可用时,可以阻断故障的传播。这就是断路器的作用,他是系统服务稳定性的最后一重保障。

熔断器产品流行的有Hystrix,以及阿里开源的Sentinel和受netflix启发专为Java8函数式编程设计的轻量级容错框架Resilience4J
很显然,本文只会讲解hystrix和Feign的集成使用,毕竟同根的产品,融合起来会更加的简单高效些。


feign-hystrix

它是Feign的一个子模块,旨在便于让FeignHystrix完成集成,并且让具体集成细节对使用者透明。


源码解析

由于整合hystrix熔断降级功能较多,需要重写一些组件加入熔断降级的能力,所以该jar的源码类相对会多一些,理解起来也稍显费力点。


SetterFactory

用于控制hystrix command的属性,包括从静态配置或者注解中读取配置,它是预先解析的(不会每次执行请求时执行)。

com.netflix.hystrix.HystrixCommand.Setter一句话解释:为构建一个HystrixCommand实例的配置接口,用于构建配置类。

public interface SetterFactory {

	// 根据目标target代理以及方法本身,生成一个用于熔断对象的HystrixCommand.Setter配置
	HystrixCommand.Setter create(Target<?> target, Method method);
}

该接口有且仅有一个实现类:Default

final class Default implements SetterFactory {

    @Override
    public HystrixCommand.Setter create(Target<?> target, Method method) {
		// target的name属性之前一直没用到过,这里是唯一使用的地方。作为groupKey,代表着配置的分组
		// 因为target是类级别的,所以这里认为一个类里面的所有方法属于同一个组
      String groupKey = target.name();
		// 使用configKey作为唯一键,和method绑定
      String commandKey = Feign.configKey(target.type(), method);

	 // 创建一个setter,放置进去了groupKey和commandKey,从而可以快速的加载到配置
      return HystrixCommand.Setter
          .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
          .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
    }

}

该默认实现给与两个重要信息:

  1. groupKey由target的name属性决定,同一个类下面的所有方法会是同一个分组
  2. 每个commandKey是和method方法绑定的,具有全局唯一性

该Defualt实现最终会作为新增属性配置在feign.hystrix.HystrixFeign上:

private SetterFactory setterFactory = new SetterFactory.Default();

在构造HystrixInvocationHandler的时候发挥作用。


FallbackFactory

用于生成对应接口的fallabck熔断降级的实例对象

// 它是个泛型接口
public interface FallbackFactory<T> {

	// 根据异常的原因,返回一个实例(注意是一个实例哦)
	// 比如遇上NOP就返回null之类的降级方案
	T create(Throwable cause);
}

同样的该接口有只有一个实现Defualt:

final class Default<T> implements FallbackFactory<T> {
	
	// 这里说得很清楚,使用jul完全仅是为了不增加外部依赖,它也知道juc不好。。。
    // jul to not add a dependency
    final Logger logger;
    // 实例对象,并不是接口哦
    // 比如接口A#a方法需要回滚,就会找到实例T的同名的a方法
    // 所以fallabck的方法签名,需要和接口的一毛一样(类名不一样)
    final T constant;
	
	... // 省略构造器
    @Override
    public T create(Throwable cause) {
      // 记录日志,fallabck的原因:"fallback due to: " + cause.getMessage()
	  // 返回返回这个常量
      return constant;
    }
    ...
}

可以看到这个实现非常的简单,它被用于:HystrixFeign上,可以为每个接口定制降级逻辑。

说明:create返回的是一个实例,它与target所有方法拥有相同的方法签名。所以你的这个实例请务必也实现目标接口,这样才能fallback


HystrixDelegatingContract

从命名能看出,它是采用代理模式加入了Hystrix逻辑。它的作用是:当方法返回值是HystrixCommand/rx.Observable/rx.Single/rx.Completable/java.util.concurrent.CompletableFuture等类型时,均能让他们能被正确的解码。

public final class HystrixDelegatingContract implements Contract {

  // 目标Contract 
  private final Contract delegate;
  // 唯一构造器
  public HystrixDelegatingContract(Contract delegate) {
    this.delegate = delegate;
  }

  // 唯一接口方法
  @Override
  public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
  	// 重要:先让代理完全解析完成
  	List<MethodMetadata> metadatas = this.delegate.parseAndValidatateMetadata(targetType);

	// 在加入hystrix自己的逻辑
	for (MethodMetadata metadata : metadatas) {
		Type type = metadata.returnType();
		// ParameterizedType -> 参数化类型(泛型返回值)
		if(type instanceof ParameterizedType && ((ParameterizedType) type).getRawType().equals(HystrixCommand.class)){
			Type actualType = resolveLastTypeParameter(type, HystrixCommand.class);
			metadata.returnType(actualType);
		} else if( ... ) ...

		// 加工处理后的returnType是剥出来的,所以能被正确的解码了
		return metadatas;
	}
  }
}

很显然,这个实现会代理掉已经设置好的任何实现,从而增加了对更多类型返回值的支持。

说明:其实这个和Hystrix无关,只是增加了更多类型的支持。而只是因为使用Hystrix时,这些返回值类型会被常用到而已(特别是HystrixCommand类型)


HystrixInvocationHandler

我们已经知道Feign它默认使用的代理处理器是FeignInvocationHandler,它仅仅起到一个调度的作用。而本处的HystrixInvocationHandler它需要在调度执行的过程中,加入熔断降级等能力,所以该类是整个实现的核心。

final class HystrixInvocationHandler implements InvocationHandler {

 // 这两参数同FeignInvocationHandler 
  private final Target<?> target;
  private final Map<Method, MethodHandler> dispatch;

	// 本类独有的三大参数
  // 降级策略工厂。可以为null
  private final FallbackFactory<?> fallbackFactory; 
  // 每个方法对应的降级时候执行的方法
  private final Map<Method, Method> fallbackMethodMap;
  // 每个方法对应的com.netflix.hystrix.HystrixCommand.Setter配置
  // 比如超时时间、恢复时间等等参数的配置
  private final Map<Method, Setter> setterMethodMap;

  //唯一构造器,对以上属性进行赋值
  //重点是对fallbackMethodMap和setterMethodMap的赋值
  // 分别交给toFallbackMethod()和toSetters()方法完成

  // 仅仅对Method执行了一句:method.setAccessible(true);而已
  // key和value完全相同的Method对象:可知:回退方法的方法签名请保持和原方法一模一样才行
  static Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
    Map<Method, Method> result = new LinkedHashMap<Method, Method>();
    for (Method method : dispatch.keySet()) {
      method.setAccessible(true);
      result.put(method, method);
    }
    return result;
  }

  // 为每个Method方法,使用SetterFactory创建了一个Setter配置实例
  // 此key将和Method方法强相关
  static Map<Method, Setter> toSetters(SetterFactory setterFactory,
                                       Target<?> target,
                                       Set<Method> methods) {
    Map<Method, Setter> result = new LinkedHashMap<Method, Setter>();
    for (Method method : methods) {
      method.setAccessible(true);
      result.put(method, setterFactory.create(target, method));
    }
    return result;
  }
  ...
}

不同于Feign原生的、简陋的FeignInvocationHandler,该类新增了fallbackFactory支持,让每个接口方法都具有熔断的能力,并且提供外部化配置支持,具有一定弹性。下面需要看看它的invoke()方法:

HystrixInvocationHandler:

  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
  	...// 同FeignInvocationHandler一样,免疫掉equals/hashCode/toString方法

	// FeignInvocationHandler只有一句代码了:return dispatch.get(method).invoke(args);
	// 而这里会用Hystrix命令包装一下:HystrixCommand
	// 让所有的执行都在这里面执行:从而具有降级的能力
	HystrixCommand<Object> hystrixCommand = new HystrixCommand<>(setterMethodMap.get(method)) {
		
          @Override
          protected Object run() throws Exception {
            try {
              return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
           
            // 若执行目标方法发生异常:比如超时、server端报错等,直接抛出此异常
            // 这样就可以进行fallback程序了
            } catch (Throwable e) {
              throw e;
            } 
          }


		// fallback回退、降级程序
          @Override
          protected Object getFallback() {
          	// 若你没指定fallback,父类就是直接抛出大名鼎鼎的异常
          	// new UnsupportedOperationException("No fallback available.");
          	// 这个异常应该是你经常在控制台看见的:No fallback available.
            if (fallbackFactory == null) {
              return super.getFallback();
            }

			// 若指定了fallbackFactory,那就执行对应的fallabck方法喽
			//根据异常类型,拿到一个fallabck的实例对象,也就是对应接口的fallback实例
			Object fallback = fallbackFactory.create(getExecutionException());

			// 调用fallback 实例的同名、同方法签名的方法,这样就得到一个降级的结果result了
			Object result = fallbackMethodMap.get(method).invoke(fallback, args);

			// 对result结果类型进行兼容
			// 如果是HystrixCommand/Observable类型等等分别怎么执行等等
              if (isReturnsHystrixCommand(method)) {
                return ((HystrixCommand) result).execute();
              } else if (isReturnsObservable(method)) {
              	return ((Observable) result).toBlocking().first();
              } ...
              
              } else if (isReturnsCompletableFuture(method)) {
                return ((Future) result).get();
              } else { // 如果不是这些类型直接返回就好
                return result;
              }
		  }
			
	}
	

	//得到了hystrixCommand 对象,现在就是执行它喽
	// 同时也支持到了返回值类型为HystrixCommand/Observable...等类型
	
	// 若是接口默认方法,直接execute()
	if (Util.isDefault(method)) { 
		return hystrixCommand.execute();
	} else if (isReturnsHystrixCommand(method)) {
		return hystrixCommand;
	} ...
	
	// 绝大部分都是执行此处:不需要类型转化,直接执行
	return hystrixCommand.execute();
  }

HystrixInvocationHandler相较于普通的FeignInvocationHandler,增加了为每个方法提供fallback方法的能力(请保持方法签名一致,回退实例由FallbackFactory产生~)。

在微服务场景中,几乎不太可能直接使用FeignInvocationHandler,而是使用这个更加安全、治理能力更强、弹性更大的HystrixInvocationHandler,所以理解它是重点。

说明:Hystrix中每一个请求都是一个Command实例,所以这里每次方法的执行都被认为是个新的请求,均为为其创建一个HystrixCommand实例去执行~


HystrixFeign

它用于把普通的Feign接口都包装成返回一个HystrixCommand,从而让所有的接口方法都加上断路器circuit breakers,这是通过整合使用了HystrixInvocationHandler它来实现的。

为了对使用者无感,feign-hystrix模块通过扩展feign.Feign.Builder的方式,写了一个自己的feign.hystrix.HystrixFeign.Builder,从而更加方便使用者来构建具有熔断、降级功能的Feign Client

public final class HystrixFeign {

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

	// 该构建器继承自feign.Feign.Builder,做出了扩展
  public static final class Builder extends Feign.Builder {

	// 这里显示的写出contract ,是因为下面要代理词默认提取器
    private Contract contract = new Contract.Default();
    private SetterFactory setterFactory = new SetterFactory.Default();
	...

	// 请注意:fallback的类型也是泛型T哦
	// 也就是说:fallback实例也是必须实现目标接口的
	// 若fallback == null,那就是木有回退工厂,那就无法执行回滚,出现你熟悉的"No fallback available."
    public <T> T target(Target<T> target, T fallback) {
      return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
    }
    // 若你有自己的工厂实现,传入自己的工厂也行(也就是实例通过自己构造,而不是传进来)
    public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
      return build(fallbackFactory).newInstance(target);
    }
	... // 省略其余多个参数的target方法

	// 请注意:这里复写了父类的build方法,最终采用一个内部方法build来实现了
	// build方法是本类的精髓。因为所有的target方法都调用了此build方法~~~~
    @Override
    public Feign build() {
      return build(null);
    }


    //所有的target方法都调用了此build方法~~~~
	Feign build(final FallbackFactory<?> nullableFallbackFactory) {
		
		// 重要:使用HystrixInvocationHandler代替默认的FeignInvocationHandler
		// 从而让每个方法都具有熔断器的能力
		super.invocationHandlerFactory((target,dispatch) -> new HystrixInvocationHandler(target, dispatch, setterFactory,nullableFallbackFactory));

		// 代理目标提取器,以支持更多类型
		super.contract(new HystrixDelegatingContract(contract));
		// 参数都设置好后,执行父类的构建逻辑~~~
		return super.build();
	}
	...
  }

}

简单说你若是直接使用Feign,使用feign.Feign.Builder构建即可,但若你想要带有熔断降级能力的Feign,请务必使用feign.hystrix.HystrixFeign.Builder构建。


Feign + Hystrix使用示例

有了以上理论基础做支撑,使用起来就毫无障碍了。

直接书写一个fallback实现,提供接口级别的降级、回退能力:

public class DemoClientFallback implements DemoClient {
    @Override
    public String getDemo1(String name) {
        return "this are fallback info...";
    }
}

当然,你也可以通过FallbackFactory创建出来,这么做更具有弹性(能根据异常不同做不一样的处理):

public class DemoClientFallbackFactory implements FallbackFactory<DemoClient> {

    @Override
    public DemoClient create(Throwable cause) {
        return new DemoClient() {
            @Override
            public String getDemo1(String name) {
                if (cause instanceof HystrixTimeoutException) {
                    return name + ":你执行超时了HystrixTimeoutException";
                } else {
                    return "this are fallback info...";
                }
            }
        };
    }
}

使用HystrixFeign构建具有熔断、降级能力的FeignClient:

@Test
public void fun1() {
    // 必须使用HystrixFeign哦
    DemoClient client = HystrixFeign.builder()
            // 指定fallback实例为DemoClientFallback
            //.target(DemoClient.class, "http://localhost:8080", new DemoClientFallback());
            .target(DemoClient.class, "http://localhost:8080", new DemoClientFallbackFactory());

    String result = client.getDemo1("YourBatman");
    System.out.println(result);
}

运行程序,控制台输出:

YourBatman:你执行超时了HystrixTimeoutException

总结

关于Feign通过feign-hystrix模块使其拥有熔断、降级能力就介绍到这,对本文的了解程度很大程度上基于你对Hystrix的了解程度。对于整合类文章,知识一般都是相扣的,所以理解全面掌握起来一般都比较费劲。

另外,若想彻头彻尾的了解Hystrix的内容,你不用找第二家,直接这里电梯直达吧:
[享学Netflix] 十六、Hystrix断路器:初体验及RxJava简介

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。


转载:https://blog.csdn.net/f641385712/article/details/104336110
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场