-
前言
-
实现容错的手段
-
Hystrix 简介
-
Hystrix 线程隔离策略
-
通用方式整合 Hystrix
-
Feign 使用 Hystrix
-
Hystrix 的监控
-
使用 Hystrix Dashboard 可视化监控数据
-
使用 Turbine 聚合监控数据
-
使用 Rabbitmq 消息中间件收集监控数
前言
通过前文《使用 Feign 实现声明式 REST 调用》https://mp.weixin.qq.com/s/npx_w5Sx0NJDumyhfkohLQ ,至此,我们已经用 Eureka 实现了微服务的注册与发现, Ribbon 实现了客户端侧的负载均衡,Feign 实现了声明式的 API 调用。
本文主要讨论如何使用 Hystrix 实现微服务的容错 。
实现容错的手段
如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的崩溃。
当依赖的服务不可用时,服务自身会不会被拖垮?这是我们要考虑的问题。
雪崩效应
微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们知道,任何微服务都并非 100% 可用,网络往往也很脆弱,因此难免有些请求会失败。
我们常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。
如何容错
要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点:
-
为网络请求设置超时 -
使用断路器模式 -
如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无谓消耗资源。例如,设置了超时时间为 1s ,如果短时间内有大量的请求无法在 1s 内得到响应,就没有必要再去请求依赖的服务了。 -
断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。 -
断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无须再浪费 CPU 时间去等待长时间的超时。 -
断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复” — 当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。
断路器状态转换的逻辑,如下图所示:

-
正常情况下,断路器关闭,可正常请求依赖的服务。 -
当一段时间内,请求失败率达到一定阈值(例如错误率达到 50% ,或 100 次/分钟等),断路器就会打开 此时,不会再去请求依赖的服务。 -
断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
Hystrix 简介
Hystrix 一个实现了超时机制和断路器模式的工具类库。
Hystrix 是由 Netflix 开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
Hystrix 主要通过以下几点实现延迟和容错:
-
包裹请求:使用 HystrixCommand (或 HystrixObservableCommand )包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。 -
跳闸机制:当某服务的错误率超过一定阈值时,Hystrix 可以自动或者手动跳闸,停止请求该服务一段时间。 -
资源隔离:Hystrix 为每个依赖都维护了一个小型的线程池(或者信号量) 。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。 -
监控:Hystrix 可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。 -
回退机制:当请求失败、超时、被拒绝,或当断路器打开时, 执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。 -
自我修复::断路器打开一段时间后,会自动进入“半开”状态。
Hystrix 线程隔离策略
execution.isolation.strategy
Hystrix 的隔离策略有两种:线程隔离和信号量隔离。
-
THREAD(线程隔离):使用该方式, HystrixCommand 将在单独的线程上执行,并发请求受到线程池中的线程数量的限制。 -
SEMAPHORE (信号量隔离):使用该方式,HystrixCommand 将在调用线程上执行,开销相对较小, 并发请求受到信号量个数的限制。
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){
//...
}
小结:
-
Hystrix 的隔离策略有 THREAD 和 SEMAPHORE 两种,默认是 THREAD。 -
正常情况下,保持默认即可。 -
如果发生找不到上下文的运行时异常,可考虑将隔离策略设置为 SEMAPHORE 。
通用方式整合 Hystrix
-
复制项目 micro-consumer-movie-ribbon ,将 artifactId 修改为 micro-consumer-movie-ribbon-hystrix 。
-
添加依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
-
在启动类上添加注解 @EnableCircuitBreaker
,为项目启用断路器支持。
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MicroConsumerMovieHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(MicroConsumerMovieHystrixApplication.class, args);
}
}
-
修改 MovieController ,让其中的 findById 方法具备容错能力。
@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})
。
Feign 使用 Hystrix
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 的监控
除实现容错外,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.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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 Dashboard 可视化监控数据
访问 /hystrix.stream 端点获得的数据是以文字形式展示的。很难通过这些数据,一眼看出系统当前的运行状态。
可使用 Hystrix Dashboard ,让监控数据图形化、可视化。
下面来编写一个 Hystrix Dashboard 。
-
使用 Spring Boot 快速构建项目 micro-hystrix-dashboard 。
-
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</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” 即可看到可视化监控数据。

左上角的圆圈代表了该方法的流量和状态:
-
圆圈越大代表方法流量越大; -
圆圈为绿色代表断路器健康、黄色代表断路器偶发故障、红色代表断路器故障;
右上角的计数器(三列数字):
第一列从上到下:
-
绿色代表当前成功调用的数量 -
蓝色代表短路请求的数量 -
蓝绿色代表错误请求的数量
第二列从上到下:
-
黄色代表超时请求的数量 -
紫色代表线程池拒绝的数量 -
红色代表失败请求的数量
第三列:
-
过去10s的错误请求百分比
Thread Pools:
-
Hystrix 会针对一个受保护的类创建一个对应的线程池,这样做的目的是 Hystrix 的命令被调用的时候,不会受方法请求线程的影响(或者说Hystrix的工作线程和调用者线程相互之间不影响)。
左上角的圆圈代表了该线程池的流量和状态:
-
圆圈越大代表线程池越活跃,流量越大 -
圆圈颜色代表的是线程池的健康状况
左下角从上至下:
-
Active 代表线程池中活跃线程的数量 -
Queued 代表排队的线程数量,该功能默认禁止,因此默认情况下始终为0 -
Pool Size 代表线程池中线程的数量
右下角从上至下:
-
Max Active 代表最大活跃线程,这里展示的数据是当前采用周期中,活跃线程的最大值 -
Execcutions 代表线程池中线程被调用执行 Hystrix 命令的次数 -
Queue Size 代表线程池队列的大小,默认禁用,无意义
Hystrix Dashboard 参数说明见:https://blog.csdn.net/qq_41125219/article/details/121370276
尝试将隔离策略设为 SEMAPHORE ,此时上图中的 ThreadPool 一栏将不再显示。这是由于 THREAD 和 SEMAPHORE 的实现机制不同所导致。
使用 Turbine 聚合监控数据
使用微服务架构的应用系统一般会包含若干个微服务,每个微服务通常都会部署多个实例。如果每次只能查看单个实例的监控数据,就必须在 Hystrix Dashboard 上切换想要监控的地址,这显然很不方便。那要如何解决该问题呢?
Turbine 简介
Turbine 是一个聚合 Hystrix 监控数据的工具,它可将所有相关 Hystrix.stream 端点的数据聚合到一个组合的 turbine.stream 中,从而让集群的监控更加方便。

-
使用 Spring Boot 快速构建项目 micro-hystrix-turbine 。
-
为项目添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</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” 即可看到可视化监控数据。

使用 Rabbitmq 消息中间件收集监控数据
一些场景下,例如微服务与 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.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</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.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</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 微服务架构实战》 周立
原文始发于微信公众号(知行chen):使用 Hystrix 实现微服务的容错处理
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/45963.html