一、概述
Hystrix是一个用于处理分布式系统的延迟和容错开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能保证在一个依赖出现问题时,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
二、Hystrix能做什么
1、对调用其他服务造成的异常和超时提供保护和控制
2、在复杂的分布式系统中防止级联失败
3、快速失败和迅速恢复
4、当必要时fallback和优雅的降级
5、提供实时监控,警告和可选择的控制
三、hystrix怎么去执行它的目标
1、将外部系统的所有调用包装在HystrixCommand对象中,该对象通常在一个单独线程中执行,这是命令模式的一种示例。
2、可以自定义超时阈值,虽然有默认值。
3、对每个依赖(微服务)维持一个小的线程池(或信号量semaphore),如果它满了,请求会立即被拒绝而不是排队等候。
4、跳闸断路器,在一段时间内停止对特定服务的所有请求,如果服务的错误百分比超过阈值。
5、执行fallback,当请求失败、拒绝、熔断或超时等等。
6、实时的监控配置和指标的改变
四、理论介绍
在分布式系统中,一个微服务响应超时或异常,如果不进行处理,那么,前端不断请求,势必会造成链路地阻塞和资源地耗尽,会导致应用系统的其他微服务也无法使用,造成系统雪崩,为了防止这种情况的发生,hystrix提供了五种理论技术,如下:
1、降级:当调用出现异常或超时等无法返回正常数据时,返回一个合理的结果或实现fallback方法,针对客户端而言。
2、熔断:当失败率达到阈值自动触发降级,通俗理解为:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内不在请求。熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给service,如果服务可正常执行并返回结果,则关闭熔断器,恢复服务。如果仍调用失败,则继续返回fallback,熔断器持续开启。
3、请求缓存:服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存,不在访问微服务B,即使出现大量请求,不会对B产生高负荷。请求缓存可以使用spring cache实现。
4、请求合并:当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负荷就会减少。使用@HystrixCollapser。方法返回值必须为Future
5、隔离:隔离分为线程池隔离合信号量隔离。通过判断线程池或信号量是否满,超过容量的请求直接降级,从而达到限流。
熔断中的熔断器
状态改变:CLOSE->OPEN
1、如果容量超过了阈值(线程池的大小或信号量)
(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
2、如果错误百分比超过了阈值
(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
状态改变:OPEN->CLOSE
当过了一段时间,默认是5秒(可以自定义) ,下一个单一请求允许通过(这是一个half-open的状态),如果请求还是失败的,在sleep window的时间段内断路器返回OPEN的状态。如果请求成功,断路器转变为CLOSE关闭状态,继续接受请求。
HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
隔离
隔离分为了线程池隔离和信号量隔离。
线程池隔离:它的目的是为每个微服务设置各自的线程池,互不影响,这样,如果某个微服务的线程池满了,不会影响其他微服务的线程池,而是调用fallback方法返回。引入线程池势必会造成一定的系统压力,因为线程池的上下文切换,调度,排队等会增加系统开销,但是hystrix在设计之初,认为它提供的好处远远可以忽略这些开销。
信号量隔离:它的目的是为每个微服务限流,能够限制并发的请求数。信号量隔离使用的其实就是并发框架中的semaphore信号量类,我们通过设定semaphore的信号数,相当于设置了此微服务的并发请求数,每一个微服务进来,都会执行semaphore的acquire方法来获得信号量,同时,返回结果后执行release方法释放信号量。
下面我们来看看hystrix的整体执行流图,如下:
第一步:构造一个HystrixCommand或HystrixObservableCommand对象来表示对某个依赖项(微服务)的请求。传入必要的参数,下面只演示HystrixCommand。
HystrixCommand command = new HystrixCommand(arg1, arg2);
第二步:执行Command,execute()方法和queue()方法都能够执行command。
execute():返回从依赖项收到的单个响应(或在出现错误时引发异常)
queue():返回可以从依赖项获得单个响应的Future
K value = command.execute();
Future<K> fValue = command.queue();
第三步:检查是否有缓存。如果我们开启了请求缓存且缓存了响应数据,那么,此次就能够直接获取缓存。注意,范围是一次HTTP请求。
第四步:检查熔断是否开启。上面讲过,触发熔断的两个条件是:时间段内超过错误百分比阈值或线程池(信号量)超过阈值,如果开启了熔断,则直接调用fallback方法,如果没开启,则继续往后执行。
第五步:检查信号量或线程池是否满。如果我们开启了信号量或线程池隔离,且满了,则直接调用fallback方法,否则,继续往后执行。
第六步:执行调用。HystrixCommand对象执行run()方法请求微服务(依赖),它会返回一个异常或正常的响应对象,如果返回异常,执行fallback方法,执行期间,如果超时了,也会触发fallback方法。
五、技术实现
我们还是根据之前的Eureka、Ribbon的环境,创建一个OpenFeign-Hystrix模块。
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主方法
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
@EnableCircuitBreaker
@EnableHystrixDashboard
public class FeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(FeignHystrixApplication.class, args);
}
}
演示熔断降级
//调用端控制层
//调用provider服务中的home方法,模拟hystrix的熔断,降级
@HystrixCommand(fallbackMethod = "homeFallback", commandProperties = {
@HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED,value = "true"), //开启熔断机制
@HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "10000"), //时间段
@HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "40"), //错误百分比阈值
@HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "5"), //请求数量阈值
})
@RequestMapping("useHome")
public String useHome(){
String res = fhService.home();
return res;
}
//调用端服务层
@Service
@FeignClient("provider")
public interface FhService {
@RequestMapping("client")
public String home();
//提供端控制层
@RequestMapping("/client")
public String home() {
int i = 1/0; //异常
return "this is home function";
}
上述演示了10秒超过百分之40的错误后,将开启熔断,在此期间,针对请求,直接返回fallback方法,而不会去请求提供端。但把提供端的异常注掉,一段时间后,请求正常。
演示请求合并
//请求端controller层
@RequestMapping("/client2")
public String client(String name){
try{
Future<String> res1 = clientService.demo3("张三");
Future<String> res3 = clientService.demo3("王五");
System.out.println(res1.get());
System.out.println(res3.get());
}catch(Exception e){
e.printStackTrace();
}
return "OK";
}
//请求端service层
Future<String> demo3(String name);
//请求端serviceImplement层
@HystrixCollapser(batchMethod = "mybatch",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
@HystrixProperty(name= HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS,value = "10"),
@HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH,value = "5")
}
)
@Override
public Future<String> demo3(String name) {
System.out.println("此方法恒不执行");
RestTemplate restTemplate = new RestTemplate();
String result= restTemplate.getForObject("http://localhost:8082/demo9?name={1}",String.class,name);
return null;
}
@HystrixCommand
public List<String> mybatch(List<String> names){
System.out.println("合并后的参数:"+names);
RestTemplate restTemplate = new RestTemplate();
List<String> res = restTemplate.postForObject("http://localhost:8082/demo9",names,List.class);
System.out.println("取出来的结果:"+res);
return res;
}
上述其实就是在用demo3方法收集在时间段内的所有该请求并合并请求参数,然后调用批处理方法mybatch执行并返回结果。但是注意,demo3返回的必须是Future方法。
演示线程池隔离
//请求端controller层
@RequestMapping("/thread")
public String thread(){
clientService.thread1();
clientService.thread2();
return "OK";
}
//请求端service层
String thread1();
String thread2();
//请求端serviceimple层
@HystrixCommand(groupKey = "jqk",commandKey = "abc",threadPoolKey = "jqk",threadPoolProperties = {
@HystrixProperty(name=HystrixPropertiesManager.CORE_SIZE,value = "8"),
@HystrixProperty(name=HystrixPropertiesManager.MAX_QUEUE_SIZE,value = "5"),
@HystrixProperty(name=HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value = "2"),
@HystrixProperty(name=HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value = "5"),
})
@Override
public String thread1() {
System.out.println(Thread.currentThread().getName());
return null;
}
@Override
public String thread2() {
System.out.println(Thread.currentThread().getName());
return null;
}
演示信号量隔离
@HystrixCommand(fallbackMethod = "semaphoreFallback",commandProperties = {
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="2"),
@HystrixProperty(name=HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="2"),
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "2000")
})
@Override
public String semaphore() {
try{
Thread.sleep(3000L);
} catch (Exception e) {
e.printStackTrace();
}
return "OK";
}
这里需要使用jmeter,不做演示
演示请求缓存
//请求端service层
@Service
public class CacheService {
@CacheResult //该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用
@HystrixCommand(commandKey = "commandKey2")
public Integer getRandomInteger(@CacheKey Integer id){
RestTemplate restTemplate = new RestTemplate();
//此次结果会被缓存
return restTemplate.getForObject("http://localhost:8082/testCache", Integer.class);
}
/**
* 使用注解清除缓存 方式3
* @CacheRemove 该注解用来让请求命令的缓存失效,失效的缓存根据commandKey进行查找。
*/
@CacheRemove(commandKey = "commandKey2")
@HystrixCommand
public void flushCache(Integer id){
//这个@CacheRemove注解直接用在更新方法上效果更好
}
}
六、总结
1、在项目中一般使用的是Hystrix的熔断和降级。
2、Hystrix的设计目的就是为了提高系统的弹性和高可用。弹性主要体现在Hystrix的熔断、降级、快速恢复和实时监控的功能,当设置了Hystrix的熔断和降级,由于某种原因导致服务熔断后,时间段内之间返回fallback,时间段之后Hystrix成半打开状态,检测服务是否可用,如果可用,熔断关闭,接收请求,否则,时间段内继续保持熔断开启。
3、Hystrix使用了舱壁模式和命令模式。舱壁模式主要体现在它的隔离理论技术,涉及到线程池隔离和信号量隔离,其中心思想就是将各个微服务独立开,互补干扰,解耦合。命令模式体现在Hystrix使用一个@HystrixCommand就将外部系统的所有调用用HystrixCommand对象包裹,该对象通常在一个单独线程中执行。
4、虽然现在Hystrix官网宣布进入维护模式,停更了,也是很老的微服务技术,现有很多的技术取而代之,但是,Hystrix的思想一直在,现在很多的技术仍然是借鉴的Hystrix理论知识,学习Hystrix,能让我们更好的学习其他的微服务知识。