通过前文《使用 Feign 实现声明式 REST 调用》https://mp.weixin.qq.com/s/npx_w5Sx0NJDumyhfkohLQ ,至此,我们已经用 Eureka 实现了微服务的注册与发现, Ribbon 实现了客户端侧的负载均衡,Feign 实现了声明式的 API 调用。
本文主要讨论如何使用 Hystrix 实现微服务的容错 。
如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的崩溃。
当依赖的服务不可用时,服务自身会不会被拖垮?这是我们要考虑的问题。
雪崩效应
微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们知道,任何微服务都并非 100% 可用,网络往往也很脆弱,因此难免有些请求会失败。
我们常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。
如何容错
要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点:
断路器状态转换的逻辑,如下图所示:
Hystrix 一个实现了超时机制和断路器模式的工具类库。
Hystrix 是由 Netflix 开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
Hystrix 主要通过以下几点实现延迟和容错:
execution.isolation.strategy
Hystrix 的隔离策略有两种:线程隔离和信号量隔离。
Hystrix 中默认且推荐使用线程隔离(THREAD) ,因为这种方式有一个除网络超时以外的额外保护层。
一般来说,只有当调用负载非常高时(例如每个实例每秒调用数百次)才需要使用信号量隔离,因为在这种场景下使 THREAD 开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
可使用 execution.isolation.strategy 属性指定隔离策略。
@HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})
@GetMapping("/{id}")
public User findById(@PathVariable Long id){
//...
}
小结:
复制项目 micro-consumer-movie-ribbon ,将 artifactId 修改为 micro-consumer-movie-ribbon-hystrix 。
添加依赖。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
@EnableCircuitBreaker
,为项目启用断路器支持。@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MicroConsumerMovieHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(MicroConsumerMovieHystrixApplication.class, args);
}
}
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/{id}")
public User findById(@PathVariable Long id){
User entity = restTemplate.getForObject("http://micro-provider-user/user/v1/"+id, User.class);
return entity;
}
public User findByIdFallback(Long id){
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
为 findById() 方法编写了一个回退方法 findByIdFallback() 该方法与 findById() 方法具有相同的参数与返回值类型,该方法返回了一个默认 User 。
在 findById() 方法上,使用注解 @HystrixCommand(fallbackMethod = "findByIdFallback")
fallbackMethod 属性,指定回退方法 findByIdFallback() 。
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。
停止服务提供者:micro-provider-user ,再次访问:http://localhost:8010/movie/v1/1 ,返回回退方法里的默认结果。
当请求失败、被拒绝、超时或者断路器打开时,都会进入回退方法。但进入回退方法并不意味着断路器已经被打开。
如需获得导致 fallback 的原因,只需在 fallback 方法上添加 Throwable 参数即可:
public User findByIdFallback(Long id, Throwable throwable){
logger.error("error:",throwable);
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
多数场景下,当发生业务异常时,我们并不想触发 fallback 此时要怎么办呢? Hystrix 有个 HystixBadRequestException 类,这是一个特殊的异常类,当该异常发生时,不会触发回退。因此,可将自定义的业务异常继承该类,从而达到业务异常不回退的效果。
另外,HystrixCommand 为我们提供了 ignoreExceptions 属性,也可借助该属性来配置不想执行回退的异常类。例:
@HystrixCommand(fallbackMethod = "findByIdFallback", ignoreExceptions={IllegalArgumentException.class, MyBusinessException.class})
。
Spring Cloud 默认已为 Feign 整合了 Hystrix 。
Spring Cloud Dalston 之前的版本中,Feign 默认已开启 Hystrix 支持,无须设置 feign.Hystrix.enabled=true 。
Spring Cloud Dalston 开始, Feign 的 Hystrix 支持,默认关闭,必须设置 feign.Hystrix.enabled=true 属性 。
创建项目。复制项目 micro-consumer-movie-feign ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback 。
修改 Feign 接口。
@FeignClient(name = "micro-provider-user", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@RequestMapping(value = "/user/v1/{id}", method = RequestMethod.GET)
User findById(@PathVariable("id") Long id);
}
@Component
public class UserFeignClientFallback implements UserFeignClient {
@Override
public User findById(Long id) {
User user = new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
}
只需使用 @FeignClient
注解的 fallback 属性,就可为指定名称的 Feign 客户端添加回退。
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。
停止服务提供者:micro-provider-user ,再次访问:http://localhost:8010/movie/v1/1 ,返回回退方法里的默认结果。
对于 Feign ,如何获得回退原因呢?可使用注解 @FeignClient
的 fallbackFactory 属性。
创建项目。复制项目 micro-consumer-movie-feign ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback-factory 。
修改 Feign 接口。
@FeignClient(name = "micro-provider-user", fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
@RequestMapping(value = "/user/v1/{id}", method = RequestMethod.GET)
User findById(@PathVariable("id") Long id);
}
@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
private static final Logger logger = LoggerFactory.getLogger(UserFeignClientFallbackFactory.class);
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient() {
@Override
public User findById(Long id) {
// 日志最好放在各个 fallback 方法中,而不要直接放在 create 方法中,否则在应用启动时,就会打印该日志
logger.info("fallback; reason was:", throwable);
User user = new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
};
}
}
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback-factory ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。
停止服务提供者:micro-provider-user ,再次访问:http://localhost:8010/movie/v1/1 ,返回回退方法里的默认结果。
除实现容错外,Hystrix 还提供了近乎实时的监控。
HystrixCommand 和 HystrixObservableCommand 在执行时,会生成执行结果和运行指标,比如每秒执行的请求数、成功数等,这些监控数据对分析应用系统的状态很有用。
使用 Hystrix 的模块 hystrix-metrics-event-stream ,就可将这些监控的指标信息以 text/event-stream 的格式暴露给外部系统。spring-cloud-starter-hystrix 中已包含该模块。
为项目添加 spring-boot-starter-actuator ,就可使用 /hystrix.stream 端点获得 Hystrix 的监控信息了。
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix ;访问:http://localhost:8010/hystrix.stream ,可看到浏览器一直处于请求的状态,页面空白。这是因为此时项目中注解了 @HystrixCommand
的方法还没有被执行,因此也没有任何的监控数据。
访问 http://localhost:8010/movie/v1/1 后,再次访问:http://localhost:8010/hystrix.stream ,可看到页面会不断出现监控数据。
这是因为系统会不断地刷新以获得实时的监控数据。 Hystrix 的监控指标非常全面,例如 HystrixCommand 的名称、 group 名称、断路器状态、错误率、错误数等。
Feign 项目的 Hystrix 监控
创建项目。复制项目 micro-consumer-movie-feign-hystrix-fallback ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback-stream 。
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
在启动类上添加 @EnableCircuitBreaker
,这样就可使用 /hystrix.stream 端点了。
修改启动端口号,后续聚合多个 /hystrix.stream 端点时用到。
server:
port: 8020
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback-stream 。
访问 http://localhost:8020/movie/v1/1 后,再次访问:http://localhost:8020/hystrix.stream ,可看到页面会不断出现监控数据。
访问 /hystrix.stream 端点获得的数据是以文字形式展示的。很难通过这些数据,一眼看出系统当前的运行状态。
可使用 Hystrix Dashboard ,让监控数据图形化、可视化。
下面来编写一个 Hystrix Dashboard 。
使用 Spring Boot 快速构建项目 micro-hystrix-dashboard 。
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
dependency>
@EnableHystrixDashboard
。@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
server:
port: 8030
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix 、Hystrix Dashboard :micro-hystrix-dashboard 。
访问 http://localhost:8010/movie/v1/1 后,再次访问:http://localhost:8010/hystrix.stream ,可看到页面会不断出现监控数据。
访问 http://localhost:8030/hystrix ,并输入 http://localhost:8010/hystrix.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。
左上角的圆圈代表了该方法的流量和状态:
右上角的计数器(三列数字):
第一列从上到下:
第二列从上到下:
第三列:
Thread Pools:
左上角的圆圈代表了该线程池的流量和状态:
左下角从上至下:
右下角从上至下:
Hystrix Dashboard 参数说明见:https://blog.csdn.net/qq_41125219/article/details/121370276
尝试将隔离策略设为 SEMAPHORE ,此时上图中的 ThreadPool 一栏将不再显示。这是由于 THREAD 和 SEMAPHORE 的实现机制不同所导致。
使用微服务架构的应用系统一般会包含若干个微服务,每个微服务通常都会部署多个实例。如果每次只能查看单个实例的监控数据,就必须在 Hystrix Dashboard 上切换想要监控的地址,这显然很不方便。那要如何解决该问题呢?
Turbine 简介
Turbine 是一个聚合 Hystrix 监控数据的工具,它可将所有相关 Hystrix.stream 端点的数据聚合到一个组合的 turbine.stream 中,从而让集群的监控更加方便。
使用 Spring Boot 快速构建项目 micro-hystrix-turbine 。
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-turbineartifactId>
dependency>
@EnableTurbine
。@SpringBootApplication
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
server:
port: 8031
spring:
application:
name: micro-hystrix-turbine
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
turbine:
appConfig: micro-consumer-movie,micro-consumer-movie-feign-hystrix-fallback-stream
clusterNameExpression: "'default'"
Turbine 会在 Eureka Server 中找到 micro-consumer-movie,micro-consumer-movie-feign-hystrix-fallback-stream 这两个微服务,并聚合两个微服务的监控数据。
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix 和 micro-consumer-movie-feign-hystrix-fallback-stream、Hystrix Dashboard :micro-hystrix-dashboard 、Turbine :micro-hystrix-turbine。访问 http://localhost:8031/turbine.stream 。
访问 http://localhost:8010/movie/v1/1 和 http://localhost:8020/movie/v1/1 ,再次访问: http://localhost:8031/turbine.strea。
访问 http://localhost:8030/hystrix ,并输入 http://localhost:8031/turbine.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。
一些场景下,例如微服务与 Turbine 网络不通,此时,可借助消息中间件实现数据收集各个微服务将 Hystrix Command 的监控数据发送至消息中间件,Turbine 消费消息中间件中的数据。
采用 Docker + Docker-Composer 在虚拟机上快速部署一个 Rabbitmq 。
创建项目。复制 micro-consumer-movie-ribbon-hystrix ,修改 artifactId 为 micro-consumer-movie-ribbon-hystrix-turbine-mq 。
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-hystrix-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
server:
port: 8010
spring:
application:
name: micro-consumer-movie
rabbitmq:
host: 192.168.233.131
port: 5672
username: root
password: Pwd
virtual-host: vhost
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
改造 Turbine
创建项目。复制项目 micro-hystrix-turbine ,修改 artifactId 为 micro-hystrix-turbine-mq 。
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-turbine-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
删除 spring-cloud-starter-turbine 依赖。
@EnableTurbineStream
@SpringBootApplication
@EnableTurbineStream
public class TurbineMqApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineMqApplication.class, args);
}
}
server:
port: 8031
spring:
application:
name: micro-hystrix-turbine
rabbitmq:
host: 192.168.233.131
port: 5672
username: root
password: Pwd
virtual-host: vhost
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
删除 turbine.appConfig 和 turbine.clusterNameExpression 配置。
启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix-turbine-mq、Hystrix Dashboard :micro-hystrix-dashboard 、Turbine :micro-hystrix-turbine-mq。访问 http://localhost:8031/turbine.stream 。
访问 http://localhost:8010/movie/v1/1 ,再次访问: http://localhost:8031/turbine.strea。
访问 http://localhost:8030/hystrix ,并输入 http://localhost:8031/turbine.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。
https://gitee.com/chentian114/spring-cloud-practice
《Spring Cloud 与Docker 微服务架构实战》 周立