Spring Cloud Hystrix
介绍
Spring Cloud Hystrix
也是基于Netflix的开源框架Hystrix
实现的, 主要在于通过控制访问远程系统、服务和第三方库的节点, 从而对延迟和故障提供更强大的容错能力. Hystrix
具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等功能.
使用方式
需引入的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类添加注解@EnableCircuitBreaker
开启断路器
注: 还可以使用Spring Cloud中的@SpringCloudApplication
注解修饰启动类, 该注解同时包含了@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
, 所以可以只引入一个注解.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
使用@HystrixCommand
注解指定失败回调, 当调用失败时则会执行失败回调返回error
@Service
public class HelloService {
@Resource
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "helleFallback")
public String helloService() {
return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
public String helleFallback() {
return "error";
}
}
工作流程
下图为官方提供的工作流程图 github
-
创建 HystrixCommand 或 HystrixObservableCommand 对象
- 首先构建一个
HystrixCommand
或者HystrixObservableCommand
对象, 用来表示对依赖服务操作的请求, 同时传递需要的参数. 这两个Command
分别针对不同的应用场景.HystrixCommand
: 用在依赖的服务返回单子操作结果HystrixObservableCommand
: 用在依赖的服务返回多个操作结果
- 首先构建一个
-
命令执行 从图中可以看到一共存在4中命令执行方式, 其中
HystrixCommand
实现了下面两个执行方式
-execute()
: 同步执行, 从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常.
-queue()
: 异步执行, 直接返回一个Future
对象, 其中包含了服务执行结束时要返回的单一结果对象.
R value = command.execute(); Future<R> fValue = command.queue();
HystrixObservableCommand
实现了两外两种执行方式
-observe()
: 返回Observable
对象, 代表了操作的多个结果, 是一个Hot Observable
(加了层 buffer 代理)
-toObservable()
: 返回Observable
对象, 代表了操作的多个结果, 返回的是一个Cold Observable
(observe
方法的lazy版本)
Observable<R> ohValue = command.observe(); Observable<R> ocValue = command.toObservable();
`Observable`对象就是`RxJava`中的内容, 可以理解为 `事件源` 或者是`被观察者`, 对应的`Subscriber`对象, 可以理解为`订阅者`或者是`观察者`. - `Observable` 用来向订阅者`Subscriber`对象发布事件, `Subscriber`对象则在接收到事件后对其进行处理, 而这里的事件指的是对依赖服务的调用. - 一个`Observable`可以发出多个事件, 知道结束或者是发生异常 - `Observable` 对象每发出一个事件, 就会调用对应观察者`Subscruver`对象的`onNext()`方法 - 每一个`Observable`的执行, 最后一定会通过调用`Subscriber.onCompleted()` 或者`Subscriber.onError()`来结束该事件的操作流
HystrixCommand
的execute()
、queue
也都使用了RxJava
来实现
-queue
是通过toObservable()
获得一个Cold Observable
,toBlocking()
将Observable
转换成BlockingObservable
, 可以把数据以阻塞的方式发射出来, 再通过toFuture
把BlockingObservable
转换为一个Future
-execute
通过queue
返回的异步对象Future<R>
的get()
方法实现同步执行 -
执行操作指令时,Hystrix首先会检查缓存内是否有对应指令的结果,如果有的话,将缓存的结果直接以
Observable
对象的形式返回。如果没有对应的缓存,Hystrix会检查Circuit Breaker
的状态。 -
如果
Circuit Breake
r的状态为开启状态,Hystrix将不会执行对应指令,而是直接进入失败处理状态(图中8 Fallback)。 -
如果Circuit Breaker的状态为关闭状态,Hystrix会继续进行线程池、任务队列、信号量的检查(图中5),确认是否有足够的资源执行操作指令。如果资源满,Hystrix同样将不会执行对应指令并且直接进入失败处理状态。
-
如果资源充足,Hystrix将会执行操作指令。操作指令的调用最终都会到这两个方法:
HystrixCommand.run() HystrixObservableCommand.construct()
如果执行时间超出了设置的超时阈值, 处理线程会抛出TimeoutException
(如果命令不在自身的线程中执行, 则会通过单独的计时线程抛出), 这种情况下, Hystrix会转接到fallback
处理逻辑(第 8 步), 如果执行指令成功,Hystrix会进行一系列的数据记录,然后返回执行的结果。 -
Hystrix会根据记录的数据来计算失败比率,一旦失败比率达到某一阈值将自动开启Circuit Breaker。
-
当命令执行失败, Hystrix会进入fallback尝试服务降级, 引起降级的情况有下面几种
1. 第四步, 命令处于熔断/短路
状态, 断路器是打开的时候
2. 第五步, 命令的线程池、请求队列或者信号量被占满的时候
3. 第六步,HystrixObservableCommand.construct()
或者HystrixCommand.run()
抛出异常的时候
如果我们在Command中实现了HystrixCommand.getFallback()方法(或HystrixObservableCommand.resumeWithFallback()方法,Hystrix会返回对应方法的结果。如果没有实现这些方法的话,从底层看Hystrix将会返回一个空的Observable对象,并且可以通过onError来终止并处理错误。如果降级执行失败的时候,, Hystrix会根据不同的执行方法做出不同的处理
- execute方法将会抛出异常
- queue方法将会返回一个失败状态的Future对象
- observe()和toObservable()方法都会返回上述的Observable对象 -
返回成功响应
熔断器 Circuit Breaker
下图为官方提供的流程架构和统计 github
每个熔断器默认维护10个bucket,每秒一个bucket,每个blucket记录成功,失败,超时,拒绝的状态,
默认错误超过50%且10秒内超过20个请求进行中断拦截.
隔离(Isolation)分析
Hystrix隔离方式采用线程/信号的方式,通过隔离限制依赖的并发量和阻塞扩散.
-
线程隔离
把执行依赖代码的线程与请求线程(如:jetty线程)分离,请求线程可以自由控制离开的时间(异步过程)。
通过线程池大小可以控制并发量,当线程池饱和时可以提前拒绝服务,防止依赖问题扩散。
线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。 -
线程隔离的优缺点
- 线程隔离的优点
- 使用线程可以完全隔离第三方代码,请求线程可以快速放回。
- 当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复。
- 可以完全模拟异步调用,方便异步编程。
- 线程隔离的缺点
- 线程池的主要缺点是它增加了cpu,因为每个命令的执行涉及到排队(默认使用SynchronousQueue避免排队),调度和上下文切换。
- 对使用ThreadLocal等依赖线程状态的代码增加复杂性,需要手动传递和清理线程状态。
Netflix公司内部认为线程隔离开销足够小,不会造成重大的成本或性能的影响。 Netflix 内部API 每天100亿的HystrixCommand依赖请求使用线程隔,每个应用大约40多个线程池,每个线程池大约5-20个线程。
- 线程隔离的优点
-
信号隔离
信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请), 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销.
线程隔离与信号隔离官方提供图
属性配置
HystrixCommand
的配置方式有两种
- 通过继承, 使用
Setter
对象对请求命令的属性设置Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(500))
- 使用注解
@HystrixCommand(fallbackMethod = "helleFallback", commandKey = "helloKey", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") })
Hystirx
配置优先级分为四种
-
全局默认值
如果没有设置下面三个级别的属性, 这个属性就是默认值, 代码中定义.
-
全局配置属性
配置文件中定义全局默认值, 在应用启动时或在与 Spring Cloud Config 和Spring Cloud Bus 实现的动态刷新配置功能配合下, 可以实现对 '全局默认值'的覆盖以及在运行期对 '全局默认值' 的动态调整
-
实例默认值
通过代码为实例定义默认值, 覆盖默认的全局配置
-
实例配置属性
通过配置文件为指定的实例进行属性配置, 覆盖前面三个默认值. 也可用 Spring Cloud Config 和 Spring Cloud Bus 实现的动态刷新配置功能实现对实例配置的动态调整
Command属性
Command
属性主要用来控制HystrixCommand
命令的行为, 配置源码在HystrixCommandProperties
中
//使用命令调用隔离方式,默认:采用线程隔离,ExecutionIsolationStrategy.THREAD
private final HystrixProperty<ExecutionIsolationStrategy> executionIsolationStrategy;
//使用线程隔离时,调用超时时间,默认:1秒
private final HystrixProperty<Integer> executionIsolationThreadTimeoutInMilliseconds;
//线程池的key,用于决定命令在哪个线程池执行
private final HystrixProperty<String> executionIsolationThreadPoolKeyOverride;
//使用信号量隔离时,命令调用最大的并发数,默认:10
private final HystrixProperty<Integer> executionIsolationSemaphoreMaxConcurrentRequests;
//使用信号量隔离时,命令fallback(降级)调用最大的并发数,默认:10
private final HystrixProperty<Integer> fallbackIsolationSemaphoreMaxConcurrentRequests;
//是否开启fallback降级策略 默认:true
private final HystrixProperty<Boolean> fallbackEnabled;
// 使用线程隔离时,是否对命令执行超时的线程调用中断(Thread.interrupt())操作.默认:true
private final HystrixProperty<Boolean> executionIsolationThreadInterruptOnTimeout;
// 统计滚动的时间窗口,默认:5000毫秒circuitBreakerSleepWindowInMilliseconds
private final HystrixProperty<Integer> metricsRollingStatisticalWindowInMilliseconds;
// 统计窗口的Buckets的数量,默认:10个,每秒一个Buckets统计
private final HystrixProperty<Integer> metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow
//是否开启监控统计功能,默认:true
private final HystrixProperty<Boolean> metricsRollingPercentileEnabled;
// 是否开启请求日志,默认:true
private final HystrixProperty<Boolean> requestLogEnabled;
//是否开启请求缓存,默认:true
private final HystrixProperty<Boolean> requestCacheEnabled; // Whether request caching is enabled.
例: execution
配置
execution
配置控制的时HystrixCommand.run()
执行
execution.isolation.strategy
: 设置HystrixCommand.run()
执行隔离策略THREAD
: 通过线程隔离的策略. 在独立线程上执行, 并且并发限制受线程池中线程数量的限制.SEMAPHORE
: 通过信号量隔离的策略. 在调用线程上执行, 并且并发限制受信号量计数的限制.
属性级别 | 默认值、配置方式、配置属性 |
---|---|
全局默认值 | THREAD |
全局配置属性 | hystrix.command.default.execution.isolation.strategy |
实例默认值 | 通过 HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) 设置, 也可以通过 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD") 注解设置 |
实例配置属性 | hystrix.command.HystrixCommandKey.execution.isolation.strategy |
execution.isolation.thread.timeoutInMilliseconds
: 配置HystrixCommand
执行的超时时间, 单位毫秒. 当HystrixCommand
执行时间超过该配置之后,Hystrix
会将该执行命令标记为TIMEOUT
并进入服务降级处理逻辑.
属性级别 | 默认值、配置方式、配置属性 |
---|---|
全局默认值 | 1000 毫秒 |
全局配置属性 | hystrix.command.default.execution.isolation.thread.timeoutinMilliseconds |
实例默认值 | 通过 HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500) 设置, 也可以通过 @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "500") 注解设置 |
实例配置属性 | hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutinMilliseconds |
- 熔断器(Circuit Breaker)配置
Circuit Breaker配置源码在HystrixCommandProperties,构造Command时通过Setter进行配置,每种依赖使用一个Circuit Breaker
// 熔断器在整个统计时间内是否开启的阀值,默认20秒。也就是10秒钟内至少请求20次,熔断器才发挥起作用
private final HystrixProperty<Integer> circuitBreakerRequestVolumeThreshold;
//熔断器默认工作时间,默认:5秒.熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
private final HystrixProperty<Integer> circuitBreakerSleepWindowInMilliseconds;
//是否启用熔断器,默认true. 启动
private final HystrixProperty<Boolean> circuitBreakerEnabled;
//默认:50%。当出错率超过50%后熔断器启动.
private final HystrixProperty<Integer> circuitBreakerErrorThresholdPercentage;
//是否强制开启熔断器阻断所有请求,默认:false,不开启
private final HystrixProperty<Boolean> circuitBreakerForceOpen;
//是否允许熔断器忽略错误,默认false, 不开启
private final HystrixProperty<Boolean> circuitBreakerForceClosed;
- 命令合并(Collapser)配置
Command配置源码在HystrixCollapserProperties,构造Collapser时通过Setter进行配置
//请求合并是允许的最大请求数,默认: Integer.MAX_VALUE
private final HystrixProperty<Integer> maxRequestsInBatch;
//批处理过程中每个命令延迟的时间,默认:10毫秒
private final HystrixProperty<Integer> timerDelayInMilliseconds;
//批处理过程中是否开启请求缓存,默认:开启
private final HystrixProperty<Boolean> requestCacheEnabled;
- 线程池(ThreadPool)配置
/**
配置线程池大小,默认值10个.
建议值:请求高峰时99.5%的平均响应时间 + 向上预留一些即可
*/
HystrixThreadPoolProperties.Setter().withCoreSize(int value)
/**
配置线程值等待队列长度,默认值:-1
建议值:-1表示不等待直接拒绝,测试表明线程池使用直接决绝策略+ 合适大小的非回缩线程池效率最高.所以不建议修改此值。
当使用非回缩线程池时,queueSizeRejectionThreshold,keepAliveTimeMinutes 参数无效
*/
HystrixThreadPoolProperties.Setter().withMaxQueueSize(int value)
本文由 anybbo 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Dec 17,2020