一、Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性、容错性与局部应用的弹性,是一个实现了超时机制和断路器模式的工具类库。
二、Hystrix的设计原则
防止任何单独的依赖耗尽资源(线程)
过载立即切断并快速失败,防止排队
尽可能提供回退以保护用户免受故障
使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
通过近实时的指标,监控和告警,确保故障被及时发现
通过动态修改配置属性,确保故障及时恢复
防止整个依赖客户端执行失败,而不仅仅是网络通信
三、Hystrix的工作原理
使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行。
每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
记录请求成功,失败,超时和线程拒绝。
服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
请求失败,被拒绝,超时或熔断时执行降级逻辑。
近实时地监控指标和配置的修改。
当使用Hystrix封装每个基础依赖项时,每个依赖项彼此隔离,受到延迟时发生饱和的资源的限制,并包含回退逻辑,该逻辑决定了在依赖项中发生任何类型的故障时做出什么响应。
Hystrix特性
请求熔断: 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN).这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
服务降级:Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了。
依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池.比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。
请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。
请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。
Hystrix流程图
Hystrix流程说明:
1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.
2:执行execute()/queue做同步或异步调用.
3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果 关闭进入步骤.
4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续骤.
5:调用HystrixCommand的run方法.运行依赖逻辑
5a:依赖逻辑调用超时,进入步骤8.
6:判断逻辑是否调用成功
6a:返回成功调用结果
6b:调用出错,进入步骤8.
7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态.
8:getFallback()降级逻辑.以下四种情况将触发getFallback调用:
(1):run()方法抛出非HystrixBadRequestException异常。
(2):run()方法调用超时
(3):熔断器开启拦截调用
(4):线程池/队列/信号量是否跑满
8a:没有实现getFallback的Command将直接抛出异常
8b:fallback降级逻辑调用成功直接返回
8c:降级逻辑调用失败抛出异常
9:返回执行成功结果
Hystrix 是 Netflix 开源的一款容错系统
Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包(request collapsing,即自动批处理),以及监控和配置等功能。
Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。
微服务通常出现的服务雪崩效应:
解决方案:
解决灾难性雪崩效应的方式通常有:降级、隔离、熔断、请求缓存、请求合并。
在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在pom文件中都需要引入下述依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
通常来说,开发的时候,使用ribbon处理服务灾难雪崩效应,开发的成本低。维护成本高。使用feign技术处理服务灾难雪崩效应,开发的成本较高,维护成本低。
降级
降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问题。处理手法都是在application client中实现。我们需要在application client相关工程中导入hystrix依赖信息。并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解生效。
引入依赖:
<!-- hystrix依赖, 处理服务灾难雪崩效应的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
启动器代码:
/**
* @EnableCircuitBreaker - 开启断路器。就是开启hystrix服务容错能力。
* 当应用启用Hystrix服务容错的时候,必须增加的一个注解。
*/
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
在调用application service相关代码中,增加新的方法注解@HystrixCommand,代表当前方法启用Hystrix处理服务雪崩效应。
@HystrixCommand注解中的属性:fallbackMethod - 代表当调用的application service出现问题时,调用哪个fallback快速失败处理方法返回托底数据。
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 服务降级处理。
* 当前方法远程调用application service服务的时候,如果service服务出现了任何错误(超时,异常等)
* 不会将异常抛到客户端,而是使用本地的一个fallback(错误返回)方法来返回一个托底数据。
* 避免客户端看到错误页面。
* 使用注解来描述当前方法的服务降级逻辑。
* @HystrixCommand - 开启Hystrix命令的注解。代表当前方法如果出现服务调用问题,使用Hystrix逻辑来处理。
* 重要属性 - fallbackMethod
* 错误返回方法名。如果当前方法调用服务,远程服务出现问题的时候,调用本地的哪个方法得到托底数据。
* Hystrix会调用fallbackMethod指定的方法,获取结果,并返回给客户端。
* @return
*/
@HystrixCommand(fallbackMethod="downgradeFallback")
public List<Map<String, Object>> testDowngrade() {
System.out.println("testDowngrade method : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference<List<Map<String, Object>>> type =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
ResponseEntity<List<Map<String, Object>>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List<Map<String, Object>> result = response.getBody();
return result;
}
/**
* fallback方法。本地定义的。用来处理远程服务调用错误时,返回的基础数据。
*/
private List<Map<String, Object>> downgradeFallback(){
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("id", -1);
data.put("name", "downgrade fallback datas");
data.put("age", 0);
result.add(data);
return result;
}
}
缓存
缓存是指请求缓存。通常意义上说,就是将同样的GET请求结果缓存起来,使用缓存机制(如redis、mongodb)提升请求响应效率。
使用请求缓存时,需要注意非幂等性操作对缓存数据的影响。
请求缓存是依托某一缓存服务来实现的。在案例中使用redis作为缓存服务器,那么可以使用spring-data-redis来实现redis的访问操作。需要在application client相关工程中导入下述依赖:
<!-- hystrix依赖, 处理服务灾难雪崩效应的。 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- spring-data-redis spring cloud中集成的spring-data相关启动器。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在Spring Cloud应用中,启用spring对cache的支持,需要在启动类中增加注解@EnableCaching,此注解代表当前应用开启spring对cache的支持。简言之就是使spring-data-redis相关的注解生效,如:@CacheConfig、@Cacheable、@CacheEvict等。
启动器:
/**
* @EnableCircuitBreaker - 开启断路器。就是开启hystrix服务容错能力。
* 当应用启用Hystrix服务容错的时候,必须增加的一个注解。
*/
@EnableCircuitBreaker
/**
* @EnableCaching - 开启spring cloud对cache的支持。
* 可以自动的使用请求缓存,访问redis等cache服务。
*/
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplicationClientApplication.class, args);
}
}
spring cloud会检查每个幂等性请求,如果请求完全相同(路径、参数等完全一致),则首先访问缓存redis,查看缓存数据,如果缓存中有数据,则不调用远程服务application service。如果缓存中没有数据,则调用远程服务,并将结果缓存到redis中,供后续请求使用。
如果请求是一个非幂等性操作,则会根据方法的注解来动态管理redis中的缓存数据,避免数据不一致。
注意:使用请求缓存会导致很多的隐患,如:缓存管理不当导致的数据不同步、问题排查困难等。在商业项目中,解决服务雪崩效应不推荐使用请求缓存。
实现类:
/**
* 在类上,增加@CacheConfig注解,用来描述当前类型可能使用cache缓存。
* 如果使用缓存,则缓存数据的key的前缀是cacheNames。
* cacheNames是用来定义一个缓存集的前缀命名的,相当于分组。
*/
@CacheConfig(cacheNames={
"test.hystrix.cache"})
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 请求缓存处理方法。
* 使用注解@Cacheable描述方法。配合启动器中的相关注解,实现一个请求缓存逻辑。
* 将当期方法的返回值缓存到cache中。
* 属性 value | cacheNames - 代表缓存到cache的数据的key的一部分。
* 可以使用springEL来获取方法参数数据,定制特性化的缓存key。
* 只要方法增加了@Cacheable注解,每次调用当前方法的时候,spring cloud都会先访问cache获取数据,
* 如果cache中没有数据,则访问远程服务获取数据。远程服务返回数据,先保存在cache中,再返回给客户端。
* 如果cache中有数据,则直接返回cache中的数据,不会访问远程服务。
*
* 请求缓存会有缓存数据不一致的可能。
* 缓存数据过期、失效、脏数据等情况。
* 一旦使用了请求缓存来处理幂等性请求操作。则在非幂等性请求操作中必须管理缓存。避免缓存数据的错误。
* @return
*/
@Cacheable("testCache4Get")
public List<Map<String, Object>> testCache4Get() {
System.out.println("testCache4Get method thread name : " + Thread.currentThread().getName());
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/test");
System.out.println("request application service URL : " + sb.toString(