第七章 使用Hystrix实现微服务的容错处理
实现容错的手段
- 如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃。
雪崩效应
-
我们把“**基础服务故障”**导致“级联故障”的现象称为雪崩效应,雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。
-
如下:A作为服务提供者(基础服务),B为A的服务消费者,C和D是B的服务消费者。当A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D,雪崩效应因此形成。
如何容错
- 容错机制需要实现以下两点
- 为网络请求设置超时:正常情况下,,一个远程调用一般在几十毫秒内就能得到响应。如果依赖的服务不可用或者为网络有问题,那么响应时间就会变得很长(几十秒)。
- 通常情况下,一次远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程越积越多,资源就会被耗尽,最终导致服务的不可用。
- 必须为每个网络请求设置超时,让资源尽快释放。
- 使用断路器模式。(如家中的断路器,电路一旦过载就会跳闸,从而可以保护电路的安全。在电路超载的问题被解决后,只需关闭断路器,电路就可以恢复正常)
- 如果对某一个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该微服务已经没有任何意义,只会无畏消耗资源。
- 断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
- 断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
- 断路器可以实现快速失败,如果在一段时间内检测到许多类似的错误(如:超时)就会在之后的一段时间内,强迫对该服务的快速失败,即不再请求所依赖的服务。这样,应用程序就无需再浪费CPU时间去等待长时间的超时。
- 断路器也可以自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。
- 为网络请求设置超时:正常情况下,,一个远程调用一般在几十毫秒内就能得到响应。如果依赖的服务不可用或者为网络有问题,那么响应时间就会变得很长(几十秒)。
断路器状态转换
- 正常情况下,断路器关闭,可正常请求依赖的服务。
- 当一段时间内,请求失败率达到一定阈值(如:错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
使用Hystrix实现容错
Hystrix简介
-
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统,服务或者第三方库,防止级联失败,从而提升系统可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
- 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
- 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,如成功,失败,超时和被拒绝的请求等。
- 回退机制:当请求失败,超时,被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发者自行提供,如返回一个缺省值。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态。
通用方式整合Hystrix
- 创建Maven项目(基于microservice-consumer-movie-ribbon)
-
添加Hystrix依赖
-
<!-- Hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
-
修改启动类添加注解**@EnableCircuitBreaker或@EnableHystrix**
-
@EnableDiscoveryClient //在spring cloud Edgware以上版本中上面注解可有可无 @EnableHystrix //@EnableCircuitBreaker || @EnableHystrix,为项目启用断路器支持 @SpringBootApplication public class ConsumerMovieApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerMovieApplication.class,args); } }
-
-
修改MovieController,让其findById方法具备容错能力。
-
@RestController public class MovieController{ private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class); @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient; @HystrixCommand(fallbackMethod = "findByIdFallback") /** * @HystrixCommand(fallbackMethod = "findByIdFallback") * fallbackMethod属性指定回退方法是findByIdFallback * 为findById方法编写一个回退方法findByIdFallback, * 该方法与findById方法具有相同的参数和返回值类型,该方法返回一个默认的User。 */ @GetMapping("/user/{id}") public User findById(@PathVariable Long id) { return this.restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); } @GetMapping("/log-user-instance") public void logUserInstance() { ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user"); // 打印当前选择的是哪个节点 MovieController.LOGGER.info("{}:{}:{}", serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort()); } public User findByIdFallback(Long id) { User user = new User(); user.setId(-1L); user.setName("默认用户"); return user; } }
-
-
测试
- 启动microservice-discovery-eureka
- 启动microservice-provider-user
- 启动microservice-consumer-movie-ribbon-hystrix
- 访问http://localhost:8010/user/1,如下
- 停止microservice-provider-user
- 再次访问http://localhost:8010/user/1,如下,说明当前用户微服务不可用,进入了回退方法。
-
当请求失败,被拒绝,超时或者断路器打开时,都会进入回退方法。但进入回退方法并不意味着断路器已经被打开。
-
在一些场景下,我们需要获得造成回退的原因,此时只需要在fallback方法上添加一个Throwable参数即可。如下:
-
/** * * @param id id * @param throwable 异常 * @return 用户 */ public User findByIdFallback(Long id,Throwable throwable) { LOGGER.error("==========进入回退方法,异常是=========="+throwable); User user = new User(); user.setId(-1L); user.setName("默认用户"); return user; }
-
控制台输出异常
-
-
但在多数场景下,当发生异常是,我们并不想触发fallback。此时该如何处理?
- Hystrix提供了HystrixBadRequestException类,这是一个特殊的异常类,当该异常发生时,不会触发回退。因此,可将自定义的业务异常继承该类,从而达到业务异常不回退的效果。
-
同时,@HystrixCommand提供了ignoreExceptions属性,可以使用该属性配置不想执行回退的异常类,如:
-
@HystrixCommand(fallbackMethod = "findByIdFallback" ,ignoreExceptions = IllegalArgumentException.class) /** * @HystrixCommand(fallbackMethod = "findByIdFallback") * fallbackMethod属性指定回退方法是findByIdFallback * ignoreExceptions属性配置不想执行回退的异常类 * 为findById方法编写一个回退方法findByIdFallback, * 该方法与findById方法具有相同的参数和返回值类型,该方法返回一个默认的User。 */ @GetMapping("/user/{id}") public User findById(@PathVariable Long id) { return this.restTemplate.getForObject("http://microservice-provider-user/"+id, User.class); }
-
这样即使在findById中发生了IllegalArgumentException异常,也不会执行findByFallback方法。
-
Hystrix断路器的状态监控与深入理解
-
之前在项目中引入的Spring Boot Actuator依赖,断路器的状态也会暴露在Actuator提供的==/health==中,这样就可以直观地了解断路器的状态。
-
测试
- 启动microservice-discovery-eureka
- 启动microservice-provider-user
- 启动microservice-consumer-movie-ribbon-hystrix
- 访问http://localhost:8010/user/1
- 访问http://localhost:8010/health,如下
- 停止microservice-provider-user
- 访问http://localhost:8010/user/1,如下
- 此时访问http://localhost:8010/health,得到结果和第一次访问一样?
- 这是因为我们的失败率还没有达到阈值(默认是5秒内失败20次),注意:执行回退逻辑并不代表断路器已经打开。请求失败,超时,被拒绝以及断路器打开时等都会执行回退逻辑。
- 此时快速的访问http://localhost:8010/user/1,以达到阈值。
- 访问http://localhost:8010/health
- 此时Hystrix的状态是CIRCUIT_OPEN,说明断路器已经打开,不会再去请求用户用户微服务了。
Hystrix线程隔离策略与传播上下文
- Hystrix的隔离策略有THREAD和SEMAPHORE两种,默认是THREAD。
- 正常情况下保持默认即可。
- 如果发生找不到上下文的运行时异常,可考虑将隔离策略设置为SEMAPHORE
Feign使用Hystrix
-
前面介绍的回退是使用**@HystrixCommand的fallbackMethod属性实现回退的,但是Feign是以接口的形式存在的,没有方法体,所以前面讲解的方式不适合于Feign**。
-
Spring Cloud已经为Feign整合了Hystrix,要想为Feign打开Hystrix的支持,只需要配置
feign.hystrix.enabled=true
-
注:在Spring Cloud Dalston之前的版本中,Feign默认开启了Hystrix支持,无需设置
feign.hystrix.enabled=true
;从Spring Cloud Dalston开始,Feign的Hystrix支持默认关闭,必须设置该属性。 -
创建Maven项目(基于microservice-consumer-movie-feign)
-
修改application.yml
-
server: port: 8010 spring: application: name: microservice-consumer-movie eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true feign: client: config: microservice-provider-user: loggerLevel: full hystrix: enabled: true #开启Feign的Hystrix支持 logging: level: com.ym.cloud.study.feign.UserFeignClient: DEBUG
-
-
修改UserFeignClient接口
-
/** * 使用@FeignClient的fallback属性指定回退类 */ @FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class) public interface UserFeignClient { @RequestMapping("/{id}") public User findById(@PathVariable("id")Long id); } /** * 创建回退类需要实现UserFeignClient接口 */ @Component class FeignClientFallback implements UserFeignClient{ @Override public User findById(Long id) { User user = new User(); user.setId(1L); user.setUsername("默认用户"); return user; } }
-
-
测试
- 启动microservice-discovery-eureka
- 启动microservice-provider-user
- 启动microservice-consumer-movie-feign-hystrix-fallback
- 访问http://localhost:8010/user/1,正常返回如下。
- 停止microservice-provider-user
- 再次访问http://localhost:8010/user/1,如下
通过Fallback Factory检查回退原因
- 前面在使用**@HystrixCommand时,如需获得造成回退的原因,只需在回退的方法上添加一个Throwable**参数。
- 在Feign中,可使用**@FeignClient的fallbackFactory**属性获得造成回退的原因。
实践
- 创建Maven项目(基于microservice-consumer-movie-feign)
-
修改application.yml,添加feign.hystrix.enabled: true
-
server: port: 8010 spring: application: name: microservice-consumer-movie eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true feign: client: config: microservice-provider-user: loggerLevel: full # 开启feign对Hystrix的支持 hystrix: enabled: true logging: level: com.ym.cloud.study.feign.UserFeignClient: DEBUG
-
-
修改UserFeignClient
-
/** * 使用@FeignClient的fallbackFactory属性指定回退类 */ @FeignClient(name = "microservice-provider-user" ,fallbackFactory = FeignClientFallbackFactory.class) public interface UserFeignClient { @RequestMapping("/{id}") public User findById(@PathVariable("id")Long id); } /** * UserFeignClient的fallbackFactory类,该类需实现FallbackFactory接口,并重写create() */ @Component class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> { private static final Logger LOGGER = LoggerFactory .getLogger(FeignClientFallbackFactory.class); @Override public UserFeignClient create(Throwable throwable) { return new UserFeignClient() { @Override public User findById(Long id) { /** * 日志最好放在各个fallback方法中,而不要直接放在create方法中, * 否则在引用启用时,就会打印该日志 */ FeignClientFallbackFactory.LOGGER.info("========产生回退原因:",throwable); User user = new User(); user.setId(1L); user.setName("默认用户"); return user; } }; } }
-
-
测试
- 启动microservice-discovery-eureka
- 启动microservice-provider-user
- 启动microservice-consumer-movie-feign-hystrix-fallback-factory
- 访问http://localhost:8010/user/1,正常访问。
- 停止microservice-provider-user
- 再次访问http://localhost:8010/user/1,如下
- 控制台如下,说明已经进入回退的方法
2020-02-14 15:52:06.098 INFO 12248 --- [provider-user-1] c.y.c.s.f.FeignClientFallbackFactory : ========产生回退原因: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: microservice-provider-user
Hystrix的监控
-
Hystrix还为我们提供了近乎实时的监控。HystrixCommand和HystrixObservableCommand在执行时,会生成执行结果和运行指标,如:每秒执行的请求数,成功数等,这对于我们分析应用系统的状况很有用。
-
使用Hystrix的模块hystrix-metrics-event-stream,就可将这些监控的指标信息以text/event-stream的格式暴露给外部系统。Spring-cloud-starter-netflix-hystrix已经包含该模块,在此基础上,只需为项目添加spring-boot-starter-actuator,就可以使用hystrix.stream端点获得Hystrix的监控信息了。
-
测试
- 启动microservice-discovery-eureka
- 启动microservice-provider-user
- 启动microservice-consumer-movie-ribbon-hystrix
- 访问http://localhost:8010/hystrix.stream,此时浏览器一直处于请求的状态,页面为空,这是因为此时项目中的注解了**@HystrixCommand**的方法还美誉被执行,因此也没有任何的监控数据
- 访问http://localhost:8010/user/1后,再次访问http://localhost:8010/hystrix.stream,如下(注:火狐会弹出一个下载,建议用Google)
Feign项目的Hystrix监控
-
启动之前的microservice-consumer-movie-feign-hystrix-fallback,使用类似的方法进行测试,会404,
-
解决方案
-
为项目添加spring-cloud-starter-netflix-hystrix依赖
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
-
在启动类上添加**@EnableCircuitBreaker**
-
使用Hystrix Dashboard可视化监控数据
-
前面介绍的Hystrix的监控,访问**/hystrix.stream**端点获得的数据是以文字形式展示的。很难一眼看出当前系统的运行状态。
-
Hystrix Dashboard是一个可视化工具,让监控数据图形化,可视化。
-
创建Maven项目(microservice-hystrix-dashboard)
-
添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
-
编写启动类添加==@EnableHystrixDashboard==
@SpringBootApplication @EnableHystrixDashboard //启用Hystrix Dashboard public class HystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication.class,args); } }
-
application.yml中添加端口
server: port: 8030