前些日子我写了一篇关于搭建Hystrix监控的文章,但是有部分同学还不清楚Hystrix是什么,今天我们就来聊一聊Hystrix的使用。
Hystrix是什么?
Netflix公司开源的Hystrix框架,对延迟和故障可以提供强大的容错能力,在分布式系统中对请求远程系统、服务或者第三方库产生的错误,通过熔断、线程池隔离等手段,可以及时停止系统中的级联错误从而起到自适应调节的作用。
Hystrix为了解决什么问题?
1.对第三方接口潜在的依赖调用的失败提供保护和控制机制。
2.在分布式系统中隔离资源,降低耦合,防止导致级连失败,相互影响。
3.快速失败以及迅速恢复。
4.在合适的时机进行优雅的降级。
5.提供近实时的监控,报警。
在分布式系统中,外部用户发起一次请求访问服务器,在这次请求中或多或少会依赖其他一些第三方资源,如果第三方依赖都在正常运作,那么整个系统将良好地运行。
每一个第三方依赖的资源对调用方来说都是黑盒的,由于依赖众多、网络原因、代码逻辑问题以及其它各种多元化原因,导致第三方依赖中有一个或多个不能提供服务了,就会导致调用方发起的这次请求一直阻塞,进而导致调用方发起的其它请求阻塞,不能够被快速消费。
第三方依赖出问题是在所难免的,我们并不能避免某些因素导致的第三方依赖调用失败,但是我们可以尽可能的避免调用失败对调用方带来的影响,提前做好应急措施,遇到问题,可以及时启动应急预案,让系统进行自调节。如果不能及时有效隔离有问题的第三方依赖,所有的请求都会因为这个单点故障而阻塞,产生“雪崩效应”,整个应用服务器将不能正常对外提供服务。
因此Hystrix设计的原则:
阻止任何单一的第三方依赖使用掉整个容器的全部用户线程
快速失败而不是在队列中积压请求
提供fallback错误回调机制
任何第三方依赖之间相互隔离
通过近实时的监控和报警及时发现系统中的问题
对第三方依赖进行故障保护
Hystrix最终想达到的目的是:
通过Hystrix的隔离保护和自调节机制,提供强大的容错能力,避免任何一个第三方依赖的单点故障带来的级联影响,避免阻塞整个应用服务器对外提供服务。
Hystrix核心处理逻辑是这样的:
Hystrix支持两种方法构建Command对象,分别通过继承HystrixCommand和HystrixObservableCommand来进行构建,这两种Command对象共支持4种执行Command的方式,分别是:
R value = command.execute()
Future<R> value = command.queue()
Observable<R> value = command.observe()
Observable<R> value = command.toObservable()
前两种调用方式仅仅支持在通过继承HytrixCommand方式构造的Command对象中调用。第一种是同步阻塞性调用,第二种是异步非阻塞性调用,第三、四种是基于发布-订阅响应式的调用,本质上是观察者模式的一种具体实现。很有意思的一件事情是execute方法通过queue().get()来实现的,而queue方法是通过toObservable().toBlocking().toFuture()来实现的,observe方法是通过toObservable().subscribe(subject)来实现的,没错,每一种调用方式最后都是基于toObservable方法来实现的。Hystrix执行Command后,它本身会进行一系列的逻辑处理,首先判断当前请求的响应结果是否已经被缓存,如果命中缓存,直接从缓存中返回响应结果,如果没有命中缓存,接下来Hytrix会判断断路器的状态,如果断路器打开,那么Hytrix会直接调用HystrixCommand中的getFallback方法或者HystrixObservableCommand中的resumeWithFallback方法,采用一种fail-silently的方式调用回调方法返回响应(建议调用方重写这两个回调方法,否则Hystrix会抛出异常,产生错误),如果断路器关闭,Hytrix接下来会判断Semaphore或者Thread pool是否已经是拒绝状态,拒绝状态通常是因为消费速度落后于生产速度,如果是拒绝状态,那么会调用已实现的回调方法,如果不是拒绝状态,说明当前请求已经通过了Hystrix的保护逻辑的验证,Hystrix会在已实现的run或者construct核心方法中调用第三方依赖,如果对第三方依赖调用失败或者调用超时,默认同样会调用fallback回调方法,同时Hystrix会把当前这次请求的metrics数据收集,实时地对断路器的健康状态进行计算(默认失败率达到50%断路器自动打开),最后返回成功的响应结果。
从整体上对Hystrix进行了把握后,我们不妨通过具体代码来看下Hystrix提供的几个具体的功能。
引入依赖:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
一:“Hello World” 镇一楼:
1.继承HystrixCommand方式:
public class HelloHystrixCommand extends HystrixCommand<String> {
private String somebody;
public HelloHystrixCommand(String somebody) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello")));
this.somebody = somebody;
}
protected String run() throws Exception {//生产环境中此处为第三方依赖的具体调用逻辑
return new StringBuilder().append("Hello ").append(somebody).toString();
}
}
单元测试:
@Test
public void testHelloHystrix() {
HelloHystrixCommand helloHystrix = new HelloHystrixCommand("World");
String result = helloHystrix.execute();assertEquals("Hello World, Failure", result);
}
2:继承HystrixObservableCommand方式:
public class HelloHystrixObservableCommand extends HystrixObservableCommand<String> {
private String somebody;
public HelloHystrixObservableCommand(String somebody) {
super(HystrixCommandGroupKey.Factory.asKey("hello"));
this.somebody = somebody;
}
protected Observable<String> construct() {//生产环境中此处为第三方依赖的具体调用逻辑
return Observable.create(
new Observable.OnSubscribe<String>() {
public void call(Subscriber<? super String> subscriber) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext("Hello " + somebody);
subscriber.onCompleted();
}
}
}).subscribeOn(Schedulers.io());
}
}
单元测试:
@Test
public void testHelloHystrixObservable() {
HelloHystrixObservableCommand helloHystrix = new HelloHystrixObservableCommand("World");
Observable<String> observable = helloHystrix.observe();
observable.subscribe(new Observer<String>() {
public void onCompleted() {
System.out.println("completed");
}
public void onError(Throwable e) {
e.printStackTrace();
}
public void onNext(String s) {assertEquals("Hello World, Failure", s);
}
});
}
二:Command的四种执行方式:
1.同步阻塞调用:
@Test
public void testHystrixExecute () {
String result = new HelloHystrixCommand("World").execute();assertEquals("Hello World, Failure", result);
}
2.异步非阻塞调用:
@Test
public void testHystrixQueue () throws ExecutionException, InterruptedException {
Future<String> future = new HelloHystrixCommand("World").queue();
String result = future.get();assertEquals("Hello World, Failure", result);
}
3.热响应式调用,不管你是否已经订阅Hystrix会立即执行Command,Hystrix会提供一种“ReplaySubject”方式进行过滤,所以不必担心已经执行但是还没被订阅的Command的结果会丢失。
@Test
public void testHystrixObserve() {
Observable<String> observable = new HelloHystrixCommand("World").observe();
observable.subscribe(new Observer<String>() {
public void onCompleted() {
System.out.println("completed");
}
public void onError(Throwable e) {
e.printStackTrace();
}
public void onNext(String s) {
assertEquals("Hello World, Failure", s);}
});
}
4.冷响应式调用,直到你订阅这个Command,Hystrix才会执行Command
@Test
public void testHystrixToObservable() {
Observable<String> observable = new HelloHystrixCommand("World").toObservable();
observable.subscribe(new Action1<String>() {
public void call(String s) {assertEquals("Hello World, Failure", s);
}
});
}
三:fallback回调机制:
上文中的流程图我们看到各种错误比如run方法调用错误、调用超时、线程池或信号量拒绝、断路器处于短路状态等最终都会落脚到fallback方法,可见fallback方法的重要性。Hystrix主要提供两种fallback方法:
1.重写HystrixCommand中的getFallback方法:
public class HelloHystrixFailureCommand extends HystrixCommand<String> {
private String somebody;
public HelloHystrixFailureCommand(String somebody) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello_failure")));
this.somebody = somebody;
}
protected String run() throws Exception {
throw new RuntimeException();
}
@Override
protected String getFallback() {
return "Hello World, Failure";
}
}
单元测试:
@Test
public void testHelloHystrixFailure() {
HelloHystrixFailureCommand failureCommand = new HelloHystrixFailureCommand("World");
String result = failureCommand.execute();assertEquals("Hello World, Failure", result);
}
2.重写HystrixObservableCommand中的resumeWithFallback方法:
public class HelloHystrixFailureObservableCommand extends HystrixObservableCommand<String> {
private String somebody;
public HelloHystrixFailureObservableCommand(String somebody) {
super(HystrixCommandGroupKey.Factory.asKey("hello"));
this.somebody = somebody;
}
protected Observable<String> construct() {
throw new RuntimeException();
}
@Override
protected Observable<String> resumeWithFallback() {
return Observable.create(
new Observable.OnSubscribe<String>(){
public void call(Subscriber<? super String> subscriber) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext("Hello World, Failure");
subscriber.onCompleted();
}
}
}
).subscribeOn(Schedulers.io());
}
}
单元测试:
@Test
public void testHelloHystrixObservableFailure() {
Observable<String> observable = new HelloHystrixFailureObservableCommand("World").observe();
observable.subscribe(new Observer<String>() {
public void onCompleted() {
System.out.println("completed");
}
public void onError(Throwable e) {
e.printStackTrace();
}
public void onNext(String s) {assertEquals("Hello World, Failure", s);
}
});
}
四:Command Group、Command Name和Command Thread-Pool:
我们可以通过实现父类构造函数来指定Command分组、命名、线程池,当然通过Setter对象设置是一种最通用的方式。
Setter setter = Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello"))
.andCommandKey(HystrixCommandKey.Factory.asKey("hello command"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("hello thread pool"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(1000)
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10)
);
关于Command Group、Name和Thread-Pool三者的关系,在这里做一下说明,Group从业务逻辑上划分某些Command可以归为一组,每个独立的外部依赖放置于一个独立的Command中,拥有唯一的Command Name,每个独立的Command和Thread-Pool应该是一对一的关系,从而达到资源隔离的目的。
关于Hystrix其他高级的特性,比如Request Cache以及Request Collapser等,由于篇幅原因(公众号文章不能超过20K字),本篇暂时不做介绍,后续也会介绍Hystrix结合注解的一些使用方法,最大程度上降低和业务代码的耦合性。
总结,本篇文章从整体上介绍了Hystrix,结合流程图可以清晰地把握Hystrix的核心逻辑过程,同时结合具体的简单示例,介绍了Command的创建方式、Command的执行方式、Fallback的回调机制以及Command Group等属性的设置问题。