这篇文章将介绍feign与hystrix结合来实现调用的同时进行熔断降级防止服务雪崩
使用feign的话自然少不了@FeignClient注解,而@FeignClient注解中除了最基本的name等属性之外,如果要整合hystrix一起使用的话,我们可能会用到fallbackFactory或者是fallback属性,所以一个@FeignClient注解看起来可能会是这样的
@FeignClient(name = "ServiceName",fallbackFactory = MyFallbackFactory.class)
fallbackFactory属性中可以填自己定义的fallbackFactory的类,你需要实现FallbackFactory接口,然后将熔断降级的逻辑自己来实现一下,看起来可能是这个样子的
public class MyFallbackFactory implements FallbackFactory<InterfaceName> {
@Override
public InterfaceName create(Throwable throwable) {
return new InterfaceName() {
@Override
public String sayHello(Long id, String name, Integer age) {
return null;
}
};
}
}
feign实现声明式调用的关键就是通过了动态代理,请求会交给动态代理去处理,让我们使用起来就像使用接口调用一样简单
既然feign与hystrix整合的话,那么请求处理的逻辑肯定会有所变化,具体就体现在了feign的动态代理中,整合后的动态代理变为了另外一个,也就是hystrix相关的动态代理,我们来分析一下这里面做了哪些事情
已知的条件就是@FeignClient中提供的信息,包括了需要调用的服务名称,和fallbackFactory的实现,fallbackFactory中包含了接口中方法执行fallback降级的方法名称、参数、逻辑等信息,这些信息组合起来就会生成一个动态代理,叫做HystrixInvocationHandler
现在我们得到了hystrix的动态代理HystrixInvocationHandler,那么之后的接口调用就会通过HystrixInvocationHandler的invoke()方法了,在这里会通过一个setterMethodMap来构建一个HystrixCommand,这个那么这个setterMethodMap里面是些什么东西呢?
setterMethodMap一看就是一个map,在这里面key是method,也就是方法,里面包含了方法名称,参数,返回值类型等,value的话就要复杂一点,里面主要包含了groupKey和commandKey,groupKey可以认为是hystrix进行熔断降级的那个线程池,hystrix最多只会消耗完这个线程池的资源,而不会导致整个服务线程资源耗尽,默认的话groupKey其实就是服务名称,也就是@FeignClient注解中的name属性,而commandKey可以认为是服务对应的接口,默认值是接口名称 + 接口中的方法(参数类),举个例子,假如接口是greetingService,方法是hello(String name),那么commandKey就是greetingService#hello(String)
上面罗里吧嗦了一大堆,其实简单来说就是HystrixInvocationHandler中已经知道要调用的接口里面的方法信息,对于已知的流程我们来画个图
ok,现在我们已经得到了HystrixCommand,HystrixCommand里面有两个方法,一个是run(),一个是getFallback(),run()方法自然就是接口正常执行的逻辑,我们已经有了方法的各种信息,再加上调用接口的参数,接下来的事情就交给feign吧,feign会替我们完成后面一系列的工作,通过和ribbon整合完成负载均衡获取要发送的机器的信息,向那台机器构造http请求,发送请求并处理结果,这里就不再展开了
我们来看看getFallback()方法,这个方法会在HystrixCommand正常逻辑执行失败的时候触发,稍微分析一下里面的流程
首先会判断fallbackFactory是不是为null,如果为null的话就说明没有配置fallback的方法,那么执行失败的话就直接给你报错了,之后会调用fallbackFactory的create()方法,这个方法的具体实现其实就是我们上面说的@FeignClient注解中的fallbackFactory配置的类的create()方法,方法中传入一个参数就是抛出的异常,你可以根据不同的异常类型来做不同的处理
create()方法会返回一个fallback,这里面就包含了降级之后的具体处理逻辑了,接着就会根据具体的方法,配合里面的参数调用fallback中的降级逻辑来完成降级
看到这里你可能发现讲来讲去还是没有讲到hystrix的核心逻辑,别急,马上来了,HystrixInvocationHandler的invoke()方法的最后,会调用HystrixCommand的execute()方法,核心逻辑就在这个里面,我们来看看这里都做了些啥
public R execute() {
try {
return queue().get();
} catch (Exception e) {
throw Exceptions.sneakyThrow(decomposeException(e));
}
}
核心就是queue().get()方法,这里的queue()返回的是一个Future
public Future<R> queue() {
/*
* The Future returned by Observable.toBlocking().toFuture() does not implement the
* interruption of the execution thread when the "mayInterrupt" flag of Future.cancel(boolean) is set to true;
* thus, to comply with the contract of Future, we must wrap around it.
*/
final Future<R> delegate = toObservable().toBlocking().toFuture();
// 省略代码,通过上面的注释,可以了解到这里的delegate其实不具备处理中断等异常原因的能力,所以对delegate进行了进一步封装
}
之后就不再贴源码了,因为hystrix的源码中大量使用了rxjava,里面各种Observable对象,调用来调用去,逻辑比较乱,我就直接说哪一步干了些什么
这里首先会去检查一下requestCacheEnabled,默认是不开启的,如果开启了request cache的话,那么就会去request cache中找一下是否存在我们需要的结果,存在的话则直接返回
之后有一个判断是circuitBreaker.attemptExecution(),circuitBreaker是什么?circuitBreaker就是熔断器,说白了就是一个开关,如果circuitBreaker打开了,那么就不会去执行正常的逻辑了,直接走fallback进行降级。所以这一步的意思就是尝试执行circuitBreaker,如果被熔断了的话,那么就直接去走降级的逻辑
熔断器放行的话,我们知道hystrix有两种隔离的策略,一种是基于线程池,还有一种是基于信号量,绝大多数情况下使用线程池就可以了,所以我们这里分析一下线程池的执行逻辑
线程池的话,可以创建线程执行任务,如果核心工作线程满了的话,就会将任务加入到队列中等待线程执行完成其他任务后再执行,hystrix的线程池默认情况下,队列是没有排队效果的,假如你有一个10个线程的线程池,那么当10个线程同时都在工作的话,新来的任务会直接走拒绝策略,当然你可以根据自己的需要自己通过参数自定义线程池,这里通过线程池和队列的大小来限制了资源,保证了资源不会被耗尽
接着会执行HystrixCommand中的run()方法来完成我们自己编写的逻辑
如果在运行上面步骤的时候出现了异常,比如说线程池满了、队列满了、请求超时了、请求报错了,都会由circuitBreaker监听到,那么如果在一个时间窗口内(默认10秒),请求总量大于20次,且异常比例大于50%的话,那么熔断器就会打开,之后的逻辑都会直接走fallback降级了
有一个参数,circuitBreakerSleepWindowInMilliseconds(默认5秒),在circuitBreaker打开之后经过5秒之后状态会由打开变为半开状态(OPEN -> HALF_OPEN),此时再次去尝试执行Command,执行成功的话就将circuitBreaker关闭,否则再次打开
来一个整个执行流程的图
在我之前的文章有总结hystrix的运行流程,感兴趣的可以看这篇文章