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

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

–> 返回专栏总目录 <–
代码下载地址: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高工、架构师 系列群大家庭学习和交流。
往期精选

发布了377 篇原创文章 · 获赞 572 · 访问量 51万+
展开阅读全文

openfeign里面配置httpclient出错。

08-18

因为现在需要使用get方式传递对象参数,所以尝试在openfeign里面配置httpclient。 配置代码如下: 在yml文件里面增加了配置信息 ``` feign: httpclient: enabled: true ``` 在pom.xml文件中引入的依赖: ``` <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <!-- 使用Apache HttpClient替换Feign原生httpclient --> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>8.16.1</version> </dependency> ``` 配置好以后尝试使用get方式传递对象参数。 服务提供端代码: ``` @GetMapping(value = "testAddRole1",consumes = "application/json") public JsonResult addTest1(@RequestBody Role role){ roleService.addRole(role); return JsonResult.getInstant(ReturnCodeEnum.SUCCESS); } ``` 服务调用接口为: ``` @GetMapping(value = "/testAddRole1",consumes = "application/json") JsonResult testAddRole1(@RequestBody Role role); ``` 现在在尝试调用接口后发现服务提供方可以接受到参数并且能够写到数据库中,但是在服务调用方拿不到正确的返回值并且会报错,错误如下: ``` Caused by: java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response; ``` 查看源码得知,openfeign在接受返回值时调用的不是httpclient的feign-core包的代码而是调用的本身的feign-core的代码,而本身的feign-core包中的Response类没有create方法。 得知原因后我将httpclient的依赖在pom文件中的位置上移,使openfeign优先调用httpclient的feign-core包的代码,结果在启动项目时就报错,报错信息为: ``` org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'test1Controller' defined in file [D:\workspace\basic\openfeign\target\classes\cn\cloudscope\openfeignTest\Test1\controller\Test1Controller.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.cloudscope.openfeignTest.Test1.service.Test1Service': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'feignRetryer' defined in org.springframework.cloud.openfeign.FeignClientsConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [feign.Retryer]: Factory method 'feignRetryer' threw exception; nested exception is java.lang.NoSuchFieldError: NEVER_RETRY ``` 查看原因得知是两个feign-core包中的Retryer接口不一致导致的,求来个大神帮忙解决一下,或者怎么调整jar包的版本去解决这个问题。openfeign的feign-core版本为10.1.0 httpclient的版本为8.16.1 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览