极致无处不在,可以小到调整一行代码提交,可以大到整个研发流程。极致可以运用在技术上,也可体现在团队管理…
–> 返回专栏总目录 <–
代码下载地址: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的一个子模块,旨在便于让Feign
和Hystrix
完成集成,并且让具体集成细节对使用者透明。
源码解析
由于整合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));
}
}
该默认实现给与两个重要信息:
- groupKey由target的name属性决定,同一个类下面的所有方法会是同一个分组
- 每个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高工、架构师 系列群大家庭学习和交流。
- [享学Feign] 一、原生Feign初体验,Netflix Feign还是Open Feign?
- [享学Feign] 二、原生Feign的注解介绍及使用示例
- [享学Feign] 三、原生Feign的核心API详解(一):UriTemplate、HardCodedTarget…
- [享学Feign] 四、原生Feign的核心API详解(二):Contract、SynchronousMethodHandler…
- [享学Feign] 五、原生Feign的编码器Encoder、QueryMapEncoder
- [享学Feign] 六、原生Feign的解码器Decoder、ErrorDecoder
- [享学Feign] 七、请求模版对象RequestTemplate和标准请求对象feign.Request
- [享学Feign] 八、Feign是如何生成接口代理对象的?Feign实例的构建器Feign.Builder详解
- [享学Feign] 九、Feign + OkHttp和Feign + Apache HttpClient哪个更香?
- [享学Feign] 十、Feign通过feign-jackson模块天然支持POJO的编码和解码
- [享学Feign] 十一、Feign通过feign-slf4j模块整合logback记录日志
转载:https://blog.csdn.net/f641385712/article/details/104336110