hystrix作为netflix开源中一个组件,主要负责服务熔断部分,防止服务的失败或超时响应带来级联影响。可以想象到,在微服务架构中,应用的整体运行依赖于各个服务的相互之间的互相依赖及调用,但是如果因为某个甚至某些服务不可用或者响应过慢而导致依赖它的服务也响应失败或响应过慢,那么这个服务就会导致调用他的服务失败或者响应过慢。要知道,当服务失败或响应过慢导致调用这个服务的consumer(有可能也是一个服务)处于一种超时持有资源的状态,那么长久下去必然会导致此服务器器内部资源被占尽,从而导致他对外界提供服务时会相应过慢,这样级联下去,结果就是整个应用中由底至上的不可用。导致本来不太相干或者可以想办法避免想干的服务因为其中一个服务不可用而导致整体不可用,这也是我们在微服务领域中需要解决的一个非常重要的问题,也属于微服务治理范畴。
上面说了这么多,那么hystrix要想避免那么多问题,要必须具备下面几种能力,这里也介绍它确实具有的能力:
1,服务的快速失败,熔断——使用hystrix后,能在你定义的访问范围内(默认是10秒内20个请求总如果失败或超时数达到50%则会熔断(开路),导致后续访问直接访问失败或者使用你的降级服务)如果达到熔断标准,会快速熔断
2,在失败后要还能检测出服务是否回复正常(默认的hystrix是在熔断后的5秒后处于一个半开路状态),也就是这时会尝试访问断开的服务,如果这时访问成功,那么就闭合访问链——使调用者可以继续访问刚刚熔断的服务,快速恢复。
当然,除了上面两点必须具备的能力外,hustrix还提供下面的能力:
1,提供缓存功能——原先访问过的参数返回结果可以复用
2,合并请求——能够将短时间内(你可以自己定义)多个请求合并成为一个请求,去访问你的服务。
3,和ribbon结合后能在一段时间后(达到一定的失败标准),自动过滤掉出错的服务服务访问路径,而只保留成功的访问,待一段时间后,如果失败的访问路径服务恢复,又能恢复负载均衡功能。
下面就关于hystrix的使用及详细配置给出(在上篇的基础上):
pom.xml配置在client端添加如下配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
application.yml文件的配置:
@SpringBootApplication
//@ComponentScan(value="com")
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class Eurekaclient2Application {
public static void main(String[] args) {
SpringApplication.run(Eurekaclient2Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
其中@EnableCircuitBreaker是启动熔断器。
在hystrix中有两种模式,线程池模式和信号量模式,线程池模式可以将调用任务方和执行任务方使用不同线程,而信号量方式则是使用同一个线程,那么有人可能会问,信号量模式那怎么实现任务异步执行,这个你可以去研究研究观察者模式,我这里就不细讲了,下面贴上很多人也都对这两种模式的分析:
线程池隔离 | 信号量隔离 | |
线程 | 与调用任务线程非同线程 | 与调用任务线程同线程 |
开销 | 排队,调度,在一次调用任务过程中有线程上下文切换 | 在一次调用任务过程中无上下文切换 |
异步 | 同一次调用任务过程中支持调用线程和任务执行线程异步 | 同一次调用任务过程中不支持调用线程和任务执行线程异步执行 |
并发支持 | 支持(线程池最大值)且允许有等待队列 | 支持(信号量最大值)不允许有等待队列 |
使用hyatrix熔断器,有注释法,继承HystrixCommand和HystrixObservableCommand几种。在这里,我只是实现了继承HystrixObservableCommand方式。至于HystrixCommand和HystrixObservableCommand区别,主要是后者通过HystrixObservableCommand$toObserver()返回的Observable对象,可以执行任务时多次返回结果,而HystrixObservableCommand$observe()返回Observable对象的使用效果则基本跟HystrixCommand$queue效果一样了都只能对一次返回的对象做处理,也都是异步执行。
先给出注释方式实现方式:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
@Component
public class HystrixService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod="fallback", commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
})
public String getInfo() {
// System.out.println(restTemplate.getForEntity("http://eureka-client1/user/getInfo", String.class).getBody().toString());
// System.out.println("===========================================================================");
return restTemplate.getForEntity("http://eureka-client1/user/getInfo", String.class).getBody().toString();
}
public String fallback() {
return "error!";
}
}
上面,在配置hystrix时可以很方便的在commandPerties中配置包括是使用semaphore(信号量)还是默认的线程池模式,以及支持的最大并发数数量,及队列数量和超时时间都可以在这里配置(信号量不支持超时设置)
使用处:
@Autowired
private HystrixService service;
@RequestMapping("/getInfo1")
public String getInfo() {
return service.getInfo();
}
下面给出继承HystrixCommand的实现:
先给出HystrixCommand的实现
/**
* 这里构造函数设置了groupid,commandid,threadpoolid(默认threadpool id和group id相同),之所以在这里设置threadpool id就是利用hystrix的隔离特性,让我们这里设置的thread方式运行调用服务过程的不同服务能有不同的线程池对应
* @author jumprn
*
*/
@Component
public class MyHytrixCommand extends HystrixCommand<String> {
RestTemplate restTemplate;
protected MyHytrixCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("group1"))
.andCommandKey(HystrixCommandKey.Factory.asKey("command1"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("pool1"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)));
this.restTemplate = restTemplate;
}
@Override
protected String run() throws Exception {
return restTemplate.getForEntity("http://eureka-client1/user/getInfo", String.class).getBody().toString();
}
@Override
protected String getFallback() {
return "error-------";
}
}
这里hystrix的配置在在构造函数中,super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("group1"))
.andCommandKey(HystrixCommandKey.Factory.asKey("command1"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("pool1"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)));
this.restTemplate = restTemplate;
}设置,至于其他设置可以看api。
再给出使用HystrixCommand的方式:
/**
* 同步
* @return
*/
@RequestMapping("/getInfo2")
public String getInfo1() {
MyHytrixCommand myHystrixCommand = new MyHytrixCommand(restTemplate);
return myHystrixCommand.execute();
}
/**
* 异步
* @return
*/
@RequestMapping("/getInfo3")
public String getInfo2() {
MyHytrixCommand myHystrixCommand= new MyHytrixCommand(restTemplate);
Future<String> future = myHystrixCommand.queue();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
return future.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
先启动eureka server,然后再启动两个eureka client(服务提供方),再启动eureka client(服务消费方)这样你就能在前端看到访问效果,此时看到每次都正常访问。然后关闭其中一个服务提供方,可以看到,一段时间内会有失败也有成功的(因为这里我们使用了ribbon负载均衡)当你提供访问频率后,一段时间后你会发现在一段时间内你所有访问都会返回error-------,这是因为达到hystrix的熔断要求了。再一段时间后,当你再访问时会发现所有访问都会成功,这就是失败的快速恢复(hystrix结合ribbon能找到可用的访问服务)。
至于使用HystrixObsercableCommand方式,也给出如下例子:
package com.cloud.eurekaclient2.consumer;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixObservableCommand.Setter;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
public class MyHystrixObservableCommand extends HystrixObservableCommand<String> {
private RestTemplate restTemplate;
protected MyHystrixObservableCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("group1"))
.andCommandKey(HystrixCommandKey.Factory.asKey("command1"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)));
this.restTemplate = restTemplate;
}
@SuppressWarnings("deprecation")
@Override
protected Observable<String> construct() {
return Observable.create(new OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> t) {
try {
if(!t.isUnsubscribed()) {
String value1 = restTemplate.getForEntity("http://eureka-client1/user/getInfo", String.class).getBody().toString();
t.onNext(value1);
String value2 = restTemplate.getForEntity("http://eureka-client1/user/getInfo", String.class).getBody().toString();
t.onNext(value2);
t.onCompleted();
}
} catch (Exception e) {
t.onError(e);
}
}
});
}
@Override
protected Observable<String> resumeWithFallback(){
return Observable.just("error!");
}
}
上面的onNext()是你每次获取到你需要的结果后通知观察者获取到结果,此时,观察者可以通过实现onNext来接受值去做你想做的处理,下面有实现。
使用处:
@RequestMapping("/getInfo4")
public String getInfo4() {
MyHystrixObservableCommand myHystrixCommand = new MyHystrixObservableCommand(restTemplate);
rx.Observable<String> observable =myHystrixCommand.toObservable();
List<String> list = new ArrayList<>();
String reStr ="";
@SuppressWarnings({ "rawtypes", "unchecked" })
rx.Observer<String> observer = new rx.Observer() {
public String reStr;
public rx.Observer accept(String reStr) {
this.reStr = reStr;
return this;
}
public String getStr() {
return reStr;
}
@Override
public void onCompleted() {
for(String i : list) {
// System.out.println(reStr);
reStr+=i+"<br>++++++++++++++++++++++";
}
System.out.println(reStr+"===============");
}
@Override
public void onError(Throwable e) {
System.out.println("++++++++++++++++++++++++++++++++++++++\n++++++++++++++++++++++++++++++++");
System.out.println("++++++++++++++++++++++++++++++++++++++\n++++++++++++++++++++++++++++++++");
System.out.println("++++++++++++++++++++++++++++++++++++++\n++++++++++++++++++++++++++++++++");
System.out.println("++++++++++++++++++++++++++++++++++++++\n++++++++++++++++++++++++++++++++");
}
@Override
public void onNext(Object t) {
list.add((String) t);
}
}.accept(reStr);
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
observable.subscribe(observer);
return null;
}
onComplete()是被观察者整个任务过程全部结束才会调用,onNext()就是对应中间被观察者触发onNext()的,onError()显而易见吧,与被观察者的onError()都是表明各自处理过程中的错误,与前面HystrixCommand方式的resumeFallback()类似,只不过分的过程更详细了(其实这也正常)。其实你可以忽略上面我给observer匿名函数传值部分,因为刚开始我试图让其在onComplete()中将最后的值给返回给前端,但是失败了,最终还是只能在onComplete()方法中将最终整合结果打印出来。至此,我们将Hystrix的使用及配置方法都给介绍了,其他更深入的,自己研究。
最后给出hystrix在spring cloud中的默认配置值和配置项,觉得很烦看的不明白可以结合hystrx 配置对应 来对应着看,问题不大
static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second)
private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second
private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;// default => statisticalWindowVolumeThreshold: 20 requests in 10 seconds must occur before statistics matter
private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;// default => sleepWindow: 5000 = 5 seconds that we will sleep before trying again after tripping the circuit
private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;// default => errorThresholdPercentage = 50 = if 50%+ of requests in 10 seconds are failures or latent then we will trip the circuit
private static final Boolean default_circuitBreakerForceOpen = false;// default => forceCircuitOpen = false (we want to allow traffic)
/* package */ static final Boolean default_circuitBreakerForceClosed = false;// default => ignoreErrors = false
private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second
private static final Boolean default_executionTimeoutEnabled = true;
private static final ExecutionIsolationStrategy default_executionIsolationStrategy = ExecutionIsolationStrategy.THREAD;
private static final Boolean default_executionIsolationThreadInterruptOnTimeout = true;
private static final Boolean default_executionIsolationThreadInterruptOnFutureCancel = false;
private static final Boolean default_metricsRollingPercentileEnabled = true;
private static final Boolean default_requestCacheEnabled = true;
private static final Integer default_fallbackIsolationSemaphoreMaxConcurrentRequests = 10;
private static final Boolean default_fallbackEnabled = true;
private static final Integer default_executionIsolationSemaphoreMaxConcurrentRequests = 10;
private static final Boolean default_requestLogEnabled = true;
private static final Boolean default_circuitBreakerEnabled = true;
private static final Integer default_metricsRollingPercentileWindow = 60000; // default to 1 minute for RollingPercentile
private static final Integer default_metricsRollingPercentileWindowBuckets = 6; // default to 6 buckets (10 seconds each in 60 second window)
private static final Integer default_metricsRollingPercentileBucketSize = 100; // default to 100 values max per bucket
private static final Integer default_metricsHealthSnapshotIntervalInMilliseconds = 500; // default to 500ms as max frequency between allowing snapshots of health (error percentage etc)
@SuppressWarnings("unused") private final HystrixCommandKey key;
private final HystrixProperty<Integer> circuitBreakerRequestVolumeThreshold; // number of requests that must be made within a statisticalWindow before open/close decisions are made using stats
private final HystrixProperty<Integer> circuitBreakerSleepWindowInMilliseconds; // milliseconds after tripping circuit before allowing retry
private final HystrixProperty<Boolean> circuitBreakerEnabled; // Whether circuit breaker should be enabled.
private final HystrixProperty<Integer> circuitBreakerErrorThresholdPercentage; // % of 'marks' that must be failed to trip the circuit
private final HystrixProperty<Boolean> circuitBreakerForceOpen; // a property to allow forcing the circuit open (stopping all requests)
private final HystrixProperty<Boolean> circuitBreakerForceClosed; // a property to allow ignoring errors and therefore never trip 'open' (ie. allow all traffic through)
private final HystrixProperty<ExecutionIsolationStrategy> executionIsolationStrategy; // Whether a command should be executed in a separate thread or not.
private final HystrixProperty<Integer> executionTimeoutInMilliseconds; // Timeout value in milliseconds for a command
private final HystrixProperty<Boolean> executionTimeoutEnabled; //Whether timeout should be triggered
private final HystrixProperty<String> executionIsolationThreadPoolKeyOverride; // What thread-pool this command should run in (if running on a separate thread).
private final HystrixProperty<Integer> executionIsolationSemaphoreMaxConcurrentRequests; // Number of permits for execution semaphore
private final HystrixProperty<Integer> fallbackIsolationSemaphoreMaxConcurrentRequests; // Number of permits for fallback semaphore
private final HystrixProperty<Boolean> fallbackEnabled; // Whether fallback should be attempted.
private final HystrixProperty<Boolean> executionIsolationThreadInterruptOnTimeout; // Whether an underlying Future/Thread (when runInSeparateThread == true) should be interrupted after a timeout
private final HystrixProperty<Boolean> executionIsolationThreadInterruptOnFutureCancel; // Whether canceling an underlying Future/Thread (when runInSeparateThread == true) should interrupt the execution thread
private final HystrixProperty<Integer> metricsRollingStatisticalWindowInMilliseconds; // milliseconds back that will be tracked
private final HystrixProperty<Integer> metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow
private final HystrixProperty<Boolean> metricsRollingPercentileEnabled; // Whether monitoring should be enabled (SLA and Tracers).
private final HystrixProperty<Integer> metricsRollingPercentileWindowInMilliseconds; // number of milliseconds that will be tracked in RollingPercentile
private final HystrixProperty<Integer> metricsRollingPercentileWindowBuckets; // number of buckets percentileWindow will be divided into
private final HystrixProperty<Integer> metricsRollingPercentileBucketSize; // how many values will be stored in each percentileWindowBucket
private final HystrixProperty<Integer> metricsHealthSnapshotIntervalInMilliseconds; // time between health snapshots
private final HystrixProperty<Boolean> requestLogEnabled; // whether command request logging is enabled.
private final HystrixProperty<Boolean> requestCacheEnabled; // Whether request caching is enabled.