使用Hystrix实现微服务的容错处理
Posted shi_zi_183
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Hystrix实现微服务的容错处理相关的知识,希望对你有一定的参考价值。
使用Hystrix实现微服务的容错处理
至此,已用Eureka实现类微服务的注册与发现,Ribbon实现了客户端侧的负载均衡,Feign实现了声明式的API调用。
实现容错的手段
如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃。例如,曾经发生过一个案例——某电子商务网站在一个黑色星期五发生过载。过多的并发请求,导致用户支付的请求延迟很久都没有响应,在等待很长时间后最终失败。支付失败又导致用户重新刷新页面并再次尝试支付,进一步增加了服务器的负载,最终整个系统都崩溃了。
雪崩效应
微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们知道,任何微服务都并非100%可用,网络往往也很脆弱,因此难免有些请求会失败。
我们通常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。
如何容错
要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点。
- 为网络请求设置超时:必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应了。如果依赖的服务不可用或者网络有问题,那么响应时间就会变得很长(几十秒)。通常情况下,一次远程调用对应一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程越积越多,资源就会逐渐耗尽,最终导致服务的不可用。因此,必须为每个网络请求设置超时,让资源尽快释放。
- 使用断路器模式:如果对某个微服务的请求有大量的超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无谓消耗资源。例如,设置了超时时间为1s,如果短时间内有大量的请求无法在1s内得到响应,就没有必要再去请求依赖的服务了。
断路器可理解为容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
断路器也可以辅助诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的"自我修复"——当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。
断路器状态转换的逻辑
- 正常请求下,断路器关闭,可正常请求依赖的服务。
- 当一段时间内,请求失败率达到一定阈值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
- 断路器打开一段时间后,会自动进入"半开"状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
使用Hystrix实现容错
Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
- 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的"命令模式"。
- 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix开源自动或手动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就会被立即拒绝,而不是排队等待,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
- 自我修复:断路器打开一段时间后,会自动进入”半开状态“。
通用方式整合Hystrix
在Spring Cloud中,整合Hystrix非常方便。以项目microservice-consumer-movie-ribbon为例,我们来为它整合Hystrix。
1)复制项目microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-comsumer-movie-ribbon-hystrix。
2)为项目添加以下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3)在启动类上添加注解@EnableCircuitBreaker或@EnableHystrix,从而为项目启用断路器支持。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableHystrix
public class MicroserviceConsumerMovieRibbonHystrixApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicroserviceConsumerMovieRibbonHystrixApplication.class, args);
}
}
4)修改MovieController,让其中的findById方法具备容错能力。
package com.example.controller;
import com.example.entity.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
}
public User findByIdFallback(Long id){
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
}
由代码可知,为findById方法编写了一个回退方法findByIdFallback,该方法与findById方法具有相同的参数与返回值类型,该方法返回了一个默认的User。
在findById方法上,使用注解@HystrixCommand的fallbackMethod属性,指定回退方法是findByIdFallback。注解@HystrixCommand由名为javanica的Hystrix contrib库提供。javanica是一个Hystrix的子项目,用于简化Hystrix的使用。Spring Cloud自动将Spring bean与该注解封装在一个连接到Hystrix断路器的代理中。
@HystrixCommand的配置非常灵活,可使用注解@HystrixProperty的commandProperties属性来配置@HystrixCommand。
测试
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-ribbon-hystrix。
4)访问http://localhost:8010/user/1,可获得如下结果
5)停止microservice-provider-user
6)再次访问http://localhost:8010/user/1
我们知道,当请求失败、被拒绝、超时或者断路器打开时,都会进入回退方法。但进入回退方法并不意味着断路器已经被打开。那么,如何才能明确了解断路器当前的状态?
在很多场景下,我们需要获得造成回退的原因,只需在falllback方法上添加一个Throwable参数即可。
package com.example.controller;
import com.example.entity.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class MovieController {
private static final Logger LOGGER=LoggerFactory.getLogger(MovieController.class);
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
}
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, HystrixTimeoutException.class})
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
}
这样,即使在findById中发生IllegalArgumentException或者HystrixTimeoutException,也不会执行findByIdFallback方法。
Hystrix断路器的状态监控与深入理解
引入Spring Boot Actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
关闭安全管理,让health显示更多内容
management:
security:
enabled: false
断路器的状态也会暴露在Actuator提供的/health端点中,这样就可以直观地了解断路器的状态。
测试
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-ribbon-hystrix。
4)访问http://localhost:8010/user/1,可正常获得结果。
5)访问http://localhost:8010/health,可获得类似如下的结果。
"eureka": {
"description": "Remote status from Eureka server",
"status": "UP",
"applications": {
"MICROSERVICE-CONSUMER-MOVIE": 1,
"MICROSERVICE-PROVIDER-USER": 1
}
}
6)停止microservice-provider-user,再次访问
"eureka": {
"description": "Remote status from Eureka server",
"status": "UP",
"applications": {
"MICROSERVICE-CONSUMER-MOVIE": 1,
"MICROSERVICE-PROVIDER-USER": 1
}
}
我们发现尽管执行了回退逻辑,返回了默认用户,但此时Hystrix的状态依然是UP,这是因为我们失败率还没有达到阈值(默认是5s内20次失败)
8)持续快速的访问http://locahost:8010/user/1,直到请求快速返回。再次查看状态。
"hystrix": {
"status": "CIRCUIT_OPEN",
"openCircuitBreakers": ["MovieController::findById"]
}
Hystrix线程隔离策略与传播上下文
Hystrix的隔离策略有两种:分别是线程隔离和信号量隔离。
- THREAD(线程隔离):使用该方式,HystrixCommand将在单独的线程上执行,并发请求受到线程池中的线程数量的限制。
- SEMAPHORE(信号量隔离):使用该方法,HystrixCommand将在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。
Hystrix中默认并且推荐使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护层。
一般来说,只有当调用负载非常高时(例如每个实例每秒调用数百次)才需要使用信号量隔离,因为在这种场景下使用THREAD开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
可使用execution.isolation.strategy
属性指定隔离策略。
Feign使用Hystrix
前文是使用注解@HystrixCommand的fallbackMethod属性实现回退的。然而,Feign是以接口形式工作的,它没有方法体,前文讲解的方式显然不适用于Feign。
在Spring Cloud中,为Feign添加回退更加简单。事实上,Spring Cloud默认已为Feign整合了Hystrix,要想为Feign打开Hystrix支持,只需设置feign.hystrix.enabled=true
。
为Feign添加回退
1)复制项目microservice-consumer-movie-feign,将ArtifactId修改为microservice-consumer-movie-feign-hystrix-fallback。
2)在application.yml中添加feign.hystrix.enable: true
,从而开启Feign的Hystrix支持。
feign:
hystrix:
enabled: true
3)将之前编写的Feign接口修改成如下内容
package com.example.feign;
import com.example.entity.FeignClientFallback;
import com.example.entity.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "microservice-provider-user",fallback = FeignClientFallback.class)
public interface UserFeignClient {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
package com.example.entity;
import com.example.feign.UserFeignClient;
import org.springframework.stereotype.Component;
@Component
public class FeignClientFallback implements UserFeignClient {
@Override
public User findById(Long id){
User user = new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
}
测试
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-feign-hystrix-fallback。
4)访问http://localhost:8010/user/1,可正常获得结果。
5)停止microservice-provider-user。
6)再次访问http://localhost:8010/user/1
在Spring Cloud Dalston之间的版本中,Feign默认已开启Hystrix支持,无须设置feign.hystrix.enabled=true;从Spring Cloud Dalston开始,Feign的Hystrix支持默认关闭,必须设置该属性。
通过Fallback Factory检查回退原因
1)复制项目microservice-consumer-movie-feign,将ArtifactId修改为microservice-consumer-movie-feign-hystrix-fallback-factory。
2)在application.yml中添加feign.hystrix.enabled:true,从而开启Feign的Hystrix支持。
3)将UserFeignClient改为
package com.example.entity;
import com.example.feign.UserFeignClient;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.feign.FeignLoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
private static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory.class);
@Override
public UserFeignClient create(Throwable cause) {
return new UserFeignClient() {
@Override
public User findById(Long id) {
//日志最好放在各个fallback中,而不要直接放在create方法中
//否则在引用启动时,就会打印该日志
FeignClientFallbackFactory.LOGGER.info("fallback; reason was:",cause);
User user=new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
};
}
}
测试
1)启动microservice-discovery-eureka。
2)启动microservice-provider-user。
3)启动microservice-consumer-movie-feign-hystrix-fallback-factory。
4)访问http://localhost:8010/user/1能正常获得结果。
5)停止microservice-provider-user。
6)再次访问http://localhost:8010/user/1
注:fallbackFactory属性还有很多其他的用途,例如让不同的异常返回不同的回退结果,从而使Feign的回退更加灵活。
为Feign禁用Hystrix
前文说过,在Spring Cloud中,只要Hystrix在项目的classpath中,Feign就会使用断路器包裹Feign客户端的所有方法。这里虽然方便,但很多场景并不需要该功能。
- 为指定Feign客户端禁用Hystrix:借助Feign的自定义配置,可轻松为指定名称的Feign客户端禁用Hystrix。
package com.example.config;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class FeignDisableHystrixConfiguration {
@Bean
@Scope("proptotye")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
想要禁用Hystrix的@FeignClient,引用该配置类即可。
@FeignClient(name = "microservice-provider-user",configuration = FeignDisableHystrixConfiguration.class)
public interface UserFeignClient {
//....
}
- 全局禁用Hystrix:也可为Feign全局禁用Hystrix。只需在application.yml中配置
feign.hystrix.enable = false
即可。
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-consumer-movie-ribbon-hystrix已具备监控Hystrix的能力。
测试
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-ribbon-hystrix。
4)访问http://localhost:8010/hystrix.stream,可看到浏览器一直处于请求的状态,页面空白。这是因为此时项目中注解了@HystrixCommand的方法还没有被执行,因此也没有任何的监控数据。
5)访问http://localhost:8010/user/1后,再次访问http://localhost:8010/hystrix.stream
这是因为系统会不断地刷新以获得实时地监控数据。Hystrix地监控指标非常全面,例如HystrixCommand地名称、group名称、断路器状态、错误率、错误数等。
Feign项目地Hystrix监控
启动前文的microservice-consumer-movie-feign-hystrix-fallback项目,并使用类似的方法测试,访问http://localhost:8010/hystrix.stream,发现返回的是404。
查看项目的依赖树会发现,
以上是关于使用Hystrix实现微服务的容错处理的主要内容,如果未能解决你的问题,请参考以下文章
springCloud(11):使用Hystrix实现微服务的容错处理-简介与实现
springcloud-hystrix断路器对微服务的容错处理