【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建
说明
原创不易,如若转载 请标明来源!
欢迎关注本人微信公众号:壹枝花算不算浪漫
更多内容也可查看本人博客:一枝花算不算浪漫
前言
前情回顾
上一个系列文章讲解了Feign的源码,主要是Feign动态代理实现的原理,及配合Ribbon实现负载均衡的机制。
这里我们讲解一个新的组件Hystrix,也是和Feign进行融合的。
本讲目录
这一讲开始讲解Hystrix相关代码,当然还是基于上一个组件Feign的基础上开始讲解的,这里默认你对Feign已经有了大致的了解。
使用过spring cloud的小伙伴对这个组件都不会陌生,Hystrix是保证系统高可用一个很重要的组件,主要提供一下几个功能:
- 对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护
- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延,服务A->服务B->服务C,服务C故障了,服务B也故障了,服务A故障了,整套分布式系统全部故障,整体宕机
- 提供fail-fast(快速失败)和快速恢复的支持
- 提供fallback优雅降级的支持
- 支持近实时的监控、报警以及运维操作
目录如下:
- Hystrix基础原理
- Hystrix Demo搭建
- Hystrix源码阅读及调试说明
- Hystrix入口程序初探
组件分析
Hystrix基础原理
命令模式
将所有请求外部系统(或者叫依赖服务)的逻辑封装到 HystrixCommand 或者 HystrixObservableCommand 对象中。
Run()方法为实现业务逻辑,这些逻辑将会在独立的线程中被执行当请求依赖服务时出现拒绝服务、超时或者短路(多个依赖服务顺序请求,前面的依赖服务请求失败,则后面的请求不会发出)时,执行该依赖服务的失败回退逻辑(Fallback)。
隔离策略
Hystrix 为每个依赖项维护一个小线程池(或信号量);如果它们达到设定值(触发隔离),则发往该依赖项的请求将立即被拒绝,执行失败回退逻辑(Fallback),而不是排队。
隔离策略分线程隔离和信号隔离。
-
线程隔离
第三方客户端(执行Hystrix的run()方法)会在单独的线程执行,会与调用的该任务的线程进行隔离,以此来防止调用者调用依赖所消耗的时间过长而阻塞调用者的线程。
使用线程隔离的好处:
- 应用程序可以不受失控的第三方客户端的威胁,如果第三方客户端出现问题,可以通过降级来隔离依赖。
- 当失败的客户端服务恢复时,线程池将会被清除,应用程序也会恢复,而不至于使整个Tomcat容器出现故障。
- 如果一个客户端库的配置错误,线程池可以很快的感知这一错误(通过增加错误比例,延迟,超时,拒绝等),并可以在不影响应用程序的功能情况下来处理这些问题(可以通过动态配置来进行实时的改变)。
- 如果一个客户端服务的性能变差,可以通过改变线程池的指标(错误、延迟、超时、拒绝)来进行属性的调整,并且这些调整可以不影响其他的客户端请求。
- 简而言之,由线程供的隔离功能可以使客户端和应用程序优雅的处理各种变化,而不会造成中断。
线程池的缺点
-
线程最主要的缺点就是增加了CPU的计算开销,每个command都会在单独的线程上执行,这样的执行方式会涉及到命令的排队、调度和上下文切换。
-
Netflix在设计这个系统时,决定接受这个开销的代价,来换取它所提供的好处,并且认为这个开销是足够小的,不会有重大的成本或者是性能影响。
-
信号隔离
信号隔离是通过限制依赖服务的并发请求数,来控制隔离开关。信号隔离方式下,业务请求线程和执行依赖服务的线程是同一个线程(例如Tomcat容器线程)。
观察者模式
- Hystrix通过观察者模式对服务进行状态监听
- 每个任务都包含有一个对应的Metrics,所有Metrics都由一个ConcurrentHashMap来进行维护,Key是CommandKey.name()
- 在任务的不同阶段会往Metrics中写入不同的信息,Metrics会对统计到的历史信息进行统计汇总,供熔断器以及Dashboard监控时使用
Metrics
- Metrics内部又包含了许多内部用来管理各种状态的类,所有的状态都是由这些类管理的
- 各种状态的内部也是用ConcurrentHashMap来进行维护的
熔断机制
熔断机制是一种保护性机制,当系统中某个服务失败率过高时,将开启熔断器,对该服务的后续调用,直接拒绝,进行Fallback操作。
熔断所依靠的数据即是Metrics中的HealthCount所统计的错误率。
如何判断是否应该开启熔断器?
必须同时满足两个条件:
- 请求数达到设定的阀值;
- 请求的失败数 / 总请求数 > 错误占比阀值%。
降级策略
当construct()或run()执行失败时,Hystrix调用fallback执行回退逻辑,回退逻辑包含了通用的响应信息,这些响应从内存缓存中或者其他固定逻辑中得到,而不应有任何的网络依赖。
如果一定要在失败回退逻辑中包含网络请求,必须将这些网络请求包装在另一个 HystrixCommand 或 HystrixObservableCommand 中,即多次降级。
失败降级也有频率限时,如果同一fallback短时间请求过大,则会抛出拒绝异常。
缓存机制
同一对象的不同HystrixCommand实例,只执行一次底层的run()方法,并将第一个响应结果缓存起来,其后的请求都会从缓存返回相同的数据。
由于请求缓存位于construct()或run()方法调用之前,所以,它减少了线程的执行,消除了线程、上下文等开销。
Hystrix基础原理总结
用一张简单地流程图总结:
Hystrix Demo搭建
Demo工程还是使用之前的项目,git地址:https://github.com/barrywangmeng/spring-cloud-learn
eureka-server:注册中心
serviceA: 提供对外接口
serviceB: 通过feign调用serviceA接口
在serviceB项目中添加hystrix相关pom依赖及配置,这里就不列出来了,小伙伴们可以直接下载这个项目看一下。
接着就是改造对serviceA调用的FeignClient:
我们可以调整serviceB中feign调用超时时间配置类模拟触发Hystrix降级逻辑:
Hystrix源码阅读及调试说明
我们在调试的过程中,为了方便走正常不降级逻辑的debug调试,特地会修改feign及hystrix的超时时间。
因为hystrix中大量使用了响应式编程(rxJava),代码中包含大量的观察者模式设计,各种回调函数糅杂在一起,所以代码显得很难懂。
这里我们不纠结更多的rxJava源码,为了调试,每个回调方法都会打上断点。
关于Hystrix daboard相关的内容这里也不会讲解,实际项目中会使用其他第三方组件来做服务监控,这里不做更多研究。
Hystrix入口程序初探
之前我们讲过,如果不配置feign.hystrix.enabled:true这个配置的话,默认用的是DefaultTargeter
, 配置了的话就改变为HystrixTargeter
。
我们来看看HystrixTargeter.target()
方法:
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
// 里面包含encoder、decoder等feign的组件信息
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
// factory.getName: serviceA 返回的setterFactory 是null
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
// 获取设置的feignClient的fallback属性
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
// 获取设置的feignClient的fallbackFactory属性
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
// 配置了降级factory的话,直接进入这个逻辑
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
// 调用我们自定义的fallback工厂中的create方法
Object exampleFallback = fallbackFactory.create(new RuntimeException());
Assert.notNull(exampleFallback,
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
feignClientName));
// target.type() 就是ServiceAFeignClient 这个feignClient接口的名称 这里就是做些判断
if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
throw new IllegalStateException(
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
feignClientName, exampleFallback.getClass(), target.type()));
}
// 执行HystrixFeign中的target方法
return builder.target(target, fallbackFactory);
}
}
我们设置的这个FallbackFactory负责在每次超时、拒绝(线程池满)、异常的时候,create()方法返回一个降级机制的对象
从服务(ServiceA)的独立的spring容器中取出来一个独立的FallbackFactory,调用每个服务的时候,他对应的FallbackFactory都是存在于那个服务关联的独立的spring容器中的。
接着进入到Hystrix.target()
中:
public final class HystrixFeign {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder {
private Contract contract = new Contract.Default();
private SetterFactory setterFactory = new SetterFactory.Default();
/**
* @see #target(Class, String, FallbackFactory)
*/
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
return build(fallbackFactory).newInstance(target);
}
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
// 设置invocationHandlerFactory为HystrixInvocationHandler
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
// 设置contact为HystrixDelegatingContract
super.contract(new HystrixDelegatingContract(contract));
// 调用父类的build方法
return super.build();
}
}
}
public class ReflectiveFeign extends Feign {
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 和之前一样,生成一个JDK动态代理对象
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
}
最终实际用来去处理这个请求的,其实是InvocationHandler,他是JDK动态代理的核心,基于JDK动态代理机制,生成一个动态代理的对象之后,对这个对象所有的方法调用,都会走关联的那个InvocationHandler。
我们这里设置的是HystrixInvocationHandler
,来看下它的构造参数:
- target:你要调用的服务,这里是HardCodedTarget,里面包含服务名称等信息
- dispatch:map,接口的每个方法的Method对象 -> SynchronousMethodHandler
- setterFactory:空
- nullableFallbackFactory:我们给的那个降级对象的工程,fallback工程
接下来还设置了contract信息,Contract是解析第三方注解的组件,设置为了HystrixDelegatingContract,顾名思义,就是说,设置了这个组件之后,后面就可以解析你在各个接口上hystrix相关的一些注解。
总结
上面已经分析了Hystrix基础原理与Demo的搭建,基础原理中用一张简单地图画了Hystrix实现的流程,后面会更加详细的依据这个图进行讲解。
申明
本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫