对Hystrix每个步骤的实现原理分析
1. 构建一个HystrixCommand或者HystrixObservableCommand
一个HystrixCommand或一个HystrixObservableCommand对象,代表了对某个依赖服务发起的一次请求或者调用。构造的时候,可以在构造函数中传入任何需要的参数。HystrixCommand主要用于仅仅会返回一个结果的调用;HystrixObservableCommand主要用于可能会返回多条结果的调用
HystrixCommand command = new HystrixCommand(arg1, arg2);
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
2. 调用command的执行方法
要执行Command,需要在4个方法中选择其中的一个:execute(),queue(),observe(),toObservable()。其中execute()和queue()仅仅对HystrixCommand适用
- execute():调用后直接block住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常
- queue():返回一个Future,属于异步调用,后面可以通过Future获取单条结果
- observe():订阅一个Observable对象,Observable代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable对象的拷贝对象
- toObservable():返回一个Observable对象,如果我们订阅这个对象,就会执行command并且获取返回结果
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe();
Observable<K> ocValue = command.toObservable();
3. 检查是否开启缓存
如果这个command开启了请求缓存,request cache,而且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。一般来说,所谓的request cache只是针对一次request context,类似于web应用中响应一个请求去调用多个依赖服务,对同一个依赖的相同参数的调用可以放到缓存从而减少网络请求来提升性能。具体实现可以通过添加过滤器来添加一个HystrixRequestContext
public class HystrixRequestContextServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
}
在Command实现中有CacheKey的设定方法:
public class CommandUsingRequestCache extends HystrixCommand<Boolean> {
...
@Override
protected String getCacheKey() {
return String.valueOf(value);
}
}
4.检查是否开启了短路器
检查这个command对应的依赖服务是否开启了短路器。如果断路器被打开了,那么hystrix就不会执行这个command,而是直接去执行fallback降级机制。
断路器的实现原理:
控制短路器是否允许工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发短路,默认是true,一般不需要修改
HystrixCommandProperties.Setter() .withCircuitBreakerEnabled(boolean value)
只要执行一个command,这个请求就一定会经过短路器,如果在一定时间内经过短路器的流量超过了一定的阈值,才会进行后面的断路器相关处理。可以通过以下配置修改,默认值是20
HystrixCommandProperties.Setter() .withCircuitBreakerRequestVolumeThreshold(int value)
如果断路器统计到的异常调用的占比超过了一定的阈值,才会打开断路器开关。默认是50%的异常比例
HystrixCommandProperties.Setter() .withCircuitBreakerErrorThresholdPercentage(int value)
经过以上步骤,然后断路器从close状态转换到open状态
- 断路器打开的时候,所有经过该断路器的请求全部被短路,不调用后端服务,直接走fallback降级
经过了一段时间之后,断路器会进入half-open状态,让一条请求经过短路器,看能不能正常调用。如果调用成功了,那么就自动恢复,转到close状态。时间可以通过以下配置来修改,默认为5000毫秒
HystrixCommandProperties.Setter() .withCircuitBreakerSleepWindowInMilliseconds(int value)
可以强迫打开短路器,一般不使用
HystrixCommandProperties.Setter() .withCircuitBreakerForceOpen(boolean value)
强迫关闭短路器,一般不使用
HystrixCommandProperties.Setter() .withCircuitBreakerForceClosed(boolean value)
5.检查线程池/队列/semaphore是否已经满了
如果command对应的线程池/队列/semaphore已经满了,那么也不会执行command,而是直接去调用fallback降级机制
6.执行command
调用HystrixObservableCommand.construct()或HystrixCommand.run()来实际执行这个command
- HystrixCommand.run()是返回一个单条结果,或者抛出一个异常
- HystrixObservableCommand.construct()是返回一个Observable对象,可以获取多条结果
如果HystrixCommand.run()或HystrixObservableCommand.construct()的执行,超过了timeout时长的话,那么command所在的线程就会抛出一个TimeoutException。如果timeout了,也会去执行fallback降级机制,而且就不会管run()或construct()返回的值了。这里要注意的一点是,我们是不可能终止掉一个调用严重延迟的依赖服务的线程的,只能说给你抛出来一个TimeoutException,但是还是可能会因为严重延迟的调用线程占满整个线程池的,即使这个时候新来的流量都被限流了。如果没有timeout的话,那么就会拿到一些调用依赖服务获取到的结果,然后hystrix会做一些logging记录和metric统计。
有一个很重要的点,command的执行强烈建议我们设置一个timeout的时间,来避免所有资源都被占用导致系统整体性能下降,可以通过以下来配置:
//默认是true打开超时控制
HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(boolean value)
//默认1000ms
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(int value)
当command执行超时之后会直接进行fallback降级处理
7.短路健康检查
Hystrix会将每一个依赖服务的调用成功,失败,拒绝,超时,等事件,都会发送给circuit breaker断路器。短路器就会对调用成功/失败/拒绝/超时等事件的次数进行统计。短路器会根据这些统计次数来决定,是否要进行短路,如果打开了短路器,那么在一段时间内就会直接短路,然后如果在之后第一次检查发现调用成功了,就关闭断路器
8.调用fallback降级机制
一般来说有四种情况都会去调用fallback降级机制:
- hystrix调用各种外部接口,或者访问外部依赖,mysql,redis,zookeeper,kafka,等等,出现了任何异常的情况
- 对外部的依赖调用所使用的线程池已满,或者使用信号量限流资源到达极限
- 访问时间过长,可能就会导致超时,报一个TimeoutException异常
- 基于上述三种情况都会发送异常事件到断路器中去进行统计,如果异常达到一定的比例直接开启短路circuit breaker
两种常见的降级处理是,维护内存ecache直接获取一份过期的数据,另一种方案是设定一个默认值返回
public class CommandHelloFailure extends HystrixCommand<String> {
private final String name;
public CommandHelloFailure(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
throw new RuntimeException("this command always fails");
}
@Override
protected String getFallback() {
return "Hello Failure " + name + "!";
}
}
HystrixObservableCommand,是实现resumeWithFallback方法
一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,尽量在这里不要再进行网络请求了。即使在降级中,一定要进行网络调用,也应该将那个调用放在一个HystrixCommand中,进行隔离
设置fallback.isolation.semaphore.maxConcurrentRequests,这个参数设置了HystrixCommand.getFallback()最大允许的并发请求数量,默认值是10,也是通过semaphore信号量的机制去限流。如果超出了这个最大值,那么直接被reject
HystrixCommandProperties.Setter().withFallbackIsolationSemaphoreMaxConcurrentRequests(int value)