书籍地址: Spring Cloud 微服务架构开发实战
我们在实践微服务的时候,通常会将业务拆分成一个个微服务,微服务之间通过网络进行互相调用,从而形成了微服务之间的依赖关系。由于网络原因或者自身的原因,微服务并不能保证服务百分之百可用。当遇到问题时,可以及时的启动应急预案,让系统进行自我调节和保护,如果不能及时有效的隔离有问题的微服务,则所有的服务请求有可能会因为这个单节点故障而阻塞,从而产生“雪崩效应”。此时就需要我们对微服务进行容错保护,及时发现微服务故障并进行处理。
在 Netflix 中,关于微服务的容错保护有这样一个方案,就是Hystrix
,通过该库我们可以解决以下问题:
Hystrix
是根据“断路器”模式而创建的。当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的服务降级处理(fallback),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间不必要的占用,从而避免了故障在分布式系统中的蔓延乃至崩溃。
当然,在请求失败频率较低的情况下,Hystrix
还会直接把故障返回给客户端。只有当失败次数达到阈值(默认在20秒内失败5次)时,断路器才会被打开并且不再进行后续通信。从而直接进行服务降级(fallback)处理。
Hystrix 的开发和实现遵守如下设计理念:
Hystrix
支持大多数属性的动态更改,从而允许开发者可以实时对低延迟反馈循环进行修改和优化。Hystrix
接下来看一下如何快速的在之前搭建的示例中引用 Hystrix
.
对于微服务容错来说保护的是服务消费方
因此示例中也就是商品服务的product-service工程,接下来代码更改主要集中在对product-service工程进行修改。
1. pom.xml
引入Netflix
的Hystrix
库
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
<version>2.1.3.RELEASEversion>
dependency>
2. 开启Hystrix注解
/**
* @EnableCircuitBreaker 该注解告诉Spring
* 微服务需要在项目中使用 Hystrix库,通过该库开启对服务的容错保护
*/
@MapperScan("com.turnsole.productservice.mapper")
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
@SpringBootApplication
public class ProductServiceApplication {
//创建一个RestTemplate Bean
// @Bean(value = "restTemplate")
// @LoadBalanced
// RestTemplate restTemplate(){
// return new RestTemplate();
// }
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
3. 对UserService 添加服务降级处理
public class UserServiceImpl implements UserService {
@Autowired
private RestTemplate restTemplate;
@Override
@HystrixCommand(fallbackMethod = "loadFallback")
public UserDto load(Integer id) {
return restTemplate.getForEntity("http://USERSERVICE/users/"+id,UserDto.class).getBody();
}
/**
* 该方法是load方法的降级方法,当restTemplate无法访问到USERSERVICE时,调用该方法返回一个匿名用户信息
* @param id
* @return
*/
private UserDto loadFallback(Integer id) {
return new UserDto(id,"xukai","xukai.jpg",1);
}
}
此处我们看到又用到了 restTemplate,这个时候 记得将 ProductServiceApplication类中关于restTemplate的注释开启
4. 容错测试
@HystrixCommand
注解方式实现。HystrixCommand
类来实现。使用注解可以最小程度的侵入代码,可以快速让原功能支持服务降级。
HystrixCommand
注解。需要说明的是:
通过 fallbackMethod 所指定的方法要与原方法具有相同的方法签名。否则会降级失败。
@HystrixCommand 注解的属性说明如下:
HystrixCommand
分组的名称。HystrixCommand
的名称。HystrixCommand
执行线程池的名称。HystrixCommand
服务降级所使用的方法名称,注意该方法需要与主方法定义在同一个类中,并且方法签名也要一致。HystrixCommand
属性,比如:断路器失败百分比,断路器时间窗口大小等HystrixCommand
所执行线程池的属性,比如,线程池的大小,线程池等待队列长度等。HystrixCommand
执行服务降级处理时需要忽略的异常,也就是当出现这些异常时不会执行服务降级处理。HystrixCommand
执行的方式;HystrixCommand
默认的服务降级处理方法,如同时设定了fallbackMethod,该属性无效。还有,该属性所指定的方法无形参,所以需注意 与主方法返回值的兼容性。后者是用在所依赖服务返回多个操作结果的时候。
在实现服务降级时,如果是继承 HystrixCommand,则需要实现
getFallback()
方法,如果是继承 HystrixObservableCommand, 则需要实现resumeWithFallback()
方法。
这里书中并没有给出很实用的案例,先记下,后面找文章学会再来补. 前面有个记下的,我还没忘.是关于feign中如何实现负载均衡的问题
注意:不要被restTemplate的代码影响
1. 增加用户微服务回退实现
@Component
public class UserServiceFallback implements UserService {
@Override
public UserDto load(Integer id) {
return this.loadFallback(id);
}
/**
* 用户回退的方法
*/
private UserDto loadFallback(Integer id) {
return new UserDto(id,"xukai","xukai.jpg",1);
}
}
2. 修改Feign所注解的用户服务
@FeignClient(value = "USERSERVICE", fallback = UserServiceFallback.class)
public interface UserService {
/**
* 我们暂时只写测试用到的load方法
* 注意请求的mapping映射一定要与用户微服务所提供的一致
*/
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
UserDto load(@PathVariable("id") Integer id);
}
这里最重要的修改,在原来的的
@FeignClient
注解中增加 fallback 属性。并将属性的值设置为我们上面所定义的类UserServiceFallback.class
3. 开启feign的容错开关
#feign开启容错保护
feign.hystrix.enabled=true
4. 测试
开启内容,测试流程和上面一个帕一样的。
Hystrix 是如何实现上述目标的呢?
以下为HystrixCommand的执行流程图:
该流程图描述了从用户提交请求到服务处理完返回的整个过程。我们来分步进行解读。
1.命令封装与执行
Hystrix
主要是通过使用命令模式,将用户对业务服务调用请求的操作进行封装,通过该封装实现了调用者与实现者的解耦。Hystrix
通过该模式来完成对整个请求的改造处理,从而实现了在不侵入微服务业务逻辑的情况下,为微服务增加了一层服务容错处理功能;2.结果缓存是否可用
当我们为Hystrix
开启了缓存功能时,Hystrix
在执行命令时首先会检查是否缓存命中,如果是,则立即将缓存的结果以Observable对象的形式返回,并不再继续执行该命令。
3.断路器是否已经打开
当结果没有命中时,Hystrix
将继续执行该命令,但在执行前将先判断断路器的状态。如果断路器已打开,则说明响应的服务已不可用,那么这时Hystrix
将会转入服务降级处理,否则将继续执行。
4.是否有资源执行
接下来,Hystrix
将判断与该命令相关的线程池和队列是否已满(如果使用的是信号量隔离,则判断信号量是否已满),如果已满,那么Hystrix
将不执行该命令,而是转入到服务降级处理。
5.执行业务逻辑
如果前面的执行条件都满足,Hystrix
将会调用HystrixCommand
的run() 或者 HystrixObservableCommand
的 construct() 方法来执行具体的业务逻辑处理,Hystrix
使用 run() 还是 construct() 方法,是由前面所编写的方法来决定的,简单说:
在命令执行过程中如果执行时间超时,那么执行线程(如果该命令没有在其自身线程中执行,则会使用一个单独线程)将会抛出一个 TimeoutException 异常,这时Hystrix
将会转入到fallback处理。
同时,如果线程没有被取消或者中断,那么run() 或者 construct() 返回的结果将会被抛弃,该超时时间可以通过 execution.isolation.thread.timeoutInMilliseconds
设置,默认值为 1000ms.
6.更新断路器健康数据
在上面过程中,Hystrix
将会把采集到的“成功”,“失败”,“拒绝”和“超时”等数据提交给断路器,断路器则会把这些统计数据更新到一系列的计数器中,然后根据这些统计数据计算断路器是否需要打开;一旦断路器打开,在恢复期结束之前Hystrix
都会对该服务进行熔断处理,在恢复期之后会根据采集到的数据再次进行判断,如果仍未达到健康状态,则将继续对该服务实施熔断处理的操作,直至符合健康状态为止;
7.服务降级处理
断路器已打开,无资源执行命令(线程池,信号量,队列已满),执行命令失败,执行命令超时,以上情况都会进入服务降级处理。
当使用HystrixCommand
时降级处理逻辑将通过 getFallback() 来实现,如果HystrixObservableCommand
,降级逻辑则是通过 resumeWithFallback()实现。
在实现服务降级处理时,最好能够提供一个默认的处理结果,该结果最好是从内存缓存中或者一个静态逻辑处理中计算得到,不再有任何网络调用的依赖。
这是因为,一旦降级处理中包含网络处理,那么势必需要再次对该网络进行HystrixCommand/HystrixObservableCommand 封装处理,从而造成级联处理,增大了系统的不稳定性,并且降级处理终究还是要回归到一个能够稳定返回的实现上。
8.返回结果
一旦Hystrix
命令执行成功(缓存返回,降级处理返回,执行成功返回都是成功),将根据我们调用的不同返回直接处理结果或 Observable。
HystrixCommand
与HystrixObservableCommand
Hystrix
主要是通过HystrixCommand
和HystrixObservableCommand
两个类来实现对服务的保护,现在来看下两者区别:
HystrixCommand
是一个阻塞型命令,当执行命令时可以直接获取到执行结果.而HystrixObservableCommand
是一个非阻塞型命令,该命令的调用者通过订阅其返回对象来获取执行的结果. 不过HystrixCommand
命令也提供了 observe() 方法,可以返回一个非阻塞性对象,但返回的 Observable 对象只能想调用者发送一次数据.HystrixCommand
命令的业务逻辑写在 run() 方法中. 服务降级逻辑写在 getFallback() 方法中; 而HystrixObservableCommand
的业务逻辑写在 construct() 方法中, 服务降级逻辑写在 resumeWithFallback() 方法中.HystrixCommand
的 run() 是由新创建的线程执行; 而HystrixObservableCommand
的 construct() 则是由调用程序线程执行.HystrixCommand
只能返回一个执行结果; 而HystrixObservableCommand
则可以按顺序向调用者发送多条执行结果.分布式架构中,当某个服务单元发生故障之后, 通过断路器的故障监控,向调用方返回一个服务降级处理或错误响应,而不是让客户端长时间的等待. 这样就不会使得线程因调用故障服务被长时间占用而不释放. 避免了故障在分布式系统中蔓延.
Hystrix
中的断路器正是起到这种服务容错保护的作用, Hystrix
在运行过程中会向每个命令对应的断路器报告成功,失败,超时和拒绝的状态, 断路器维护计算统计的数据,根据这些统计的信息来确定断路器是否打开.
如果打开,后续的请求都会进行服务降级处理. 然后会隔一段时间尝试半开, 放入一部分流量请求进来, 相当于对依赖服务进行一次健康检查, 如果恢复,那么断路器会关闭,随后完全恢复调用.
Hystrix
配置参数:
Hystrix
将允许执行任何命令,而不做任何检查,默认值为false。Hystrix
才进行错误百分比计算。滚动统计窗口时长默认为10秒,可以通过 metrics.rollingStats.timeInMilliseconds 进行设置。Hystrix
会尝试释放出一部分命令进行试探,判断相应的服务是否恢复。从上面的配置中可以得知,当满足以下条件时,Hystrix
就会开启断路器:
也就是在默认情况下,错误率超过50%且10秒内超过20个命令请求进行中断拦截,这时候断路器将会被打开,Hystrix
将会对所有命令执行请求进行服务降级处理。
当断路器打开一段时间之后(该值通过 sleepWindowInMilliseconds设置),Hystrix
就会进入半开(Half-Open State) 状态,当一个命令请求通过这个断路器时,断路器则尝试不阻断这个命令请求,而是直接将这个命令请求通过,如果这个命令请求仍然执行失败,那么断路器会直接回到打开状态。如果这个命令请求执行成功,那么断路器就会关闭,并且开始进行下一次统计。
Hystrix
异常----HystrixBadRequestException在 HystrixCommand 的 run() 方法中,如果运行时抛出了异常,就会执行降级处理,但是有一个异常例外,就是 HystrixBadRequestException。
所以开发者需要在命令执行过程中抛出不需要进行降级处理的异常时,就可以抛出该异常。
如果使用的时注解的方式,可以在注解 ignoreException 的值来设置忽略的异常
@HystrixCommand(ignoreException={HystrixBadRequestException.class})
public User load(Integer id){
...
}