使用 Hystrix 实现微服务的容错处理

使用 Hystrix 实现微服务的容错处理

前言

通过前文《使用 Feign 实现声明式 REST 调用》https://mp.weixin.qq.com/s/npx_w5Sx0NJDumyhfkohLQ ,至此,我们已经用 Eureka 实现了微服务的注册与发现, Ribbon 实现了客户端侧的负载均衡,Feign 实现了声明式的 API 调用。

本文主要讨论如何使用 Hystrix 实现微服务的容错 。

实现容错的手段

如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的崩溃。

当依赖的服务不可用时,服务自身会不会被拖垮?这是我们要考虑的问题。


雪崩效应

微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们知道,任何微服务都并非 100% 可用,网络往往也很脆弱,因此难免有些请求会失败。

我们常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程


如何容错

要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点:

  • 为网络请求设置超时
  • 使用断路器模式
    • 如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无谓消耗资源。例如,设置了超时时间为 1s ,如果短时间内有大量的请求无法在 1s 内得到响应,就没有必要再去请求依赖的服务了。
    • 断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
    • 断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无须再浪费 CPU 时间去等待长时间的超时。
    • 断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复” – 当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器状态转换的逻辑,如下图所示:

8.断路器状态转换图

  • 正常情况下,断路器关闭,可正常请求依赖的服务。
  • 当一段时间内,请求失败率达到一定阈值(例如错误率达到 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

  1. 复制项目 micro-consumer-movie-ribbon ,将 artifactId 修改为 micro-consumer-movie-ribbon-hystrix 。

  2. 添加依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
  1. 在启动类上添加注解@EnableCircuitBreaker ,为项目启用断路器支持。
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MicroConsumerMovieHystrixApplication {
    public static void main(String[] args) {
        SpringApplication.run(MicroConsumerMovieHystrixApplication.class, args);
    }
}
  1. 修改 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() 。

  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。

  2. 停止服务提供者: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 属性 。

  1. 创建项目。复制项目 micro-consumer-movie-feign ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback 。

  2. 修改 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 客户端添加回退。

  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。

  2. 停止服务提供者:micro-provider-user ,再次访问:http://localhost:8010/movie/v1/1 ,返回回退方法里的默认结果。


对于 Feign ,如何获得回退原因呢?可使用注解 @FeignClient 的 fallbackFactory 属性。

  1. 创建项目。复制项目 micro-consumer-movie-feign ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback-factory 。

  2. 修改 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;
            }
        };
    }
}
  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback-factory ;访问:http://localhost:8010/movie/v1/1 ,正常返回结果。

  2. 停止服务提供者: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 的监控信息了。

  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix ;访问:http://localhost:8010/hystrix.stream ,可看到浏览器一直处于请求的状态,页面空白。这是因为此时项目中注解了 @HystrixCommand 的方法还没有被执行,因此也没有任何的监控数据。

  2. 访问 http://localhost:8010/movie/v1/1 后,再次访问:http://localhost:8010/hystrix.stream ,可看到页面会不断出现监控数据。

这是因为系统会不断地刷新以获得实时的监控数据。 Hystrix 的监控指标非常全面,例如 HystrixCommand 的名称、 group 名称、断路器状态、错误率、错误数等。


Feign 项目的 Hystrix 监控

  1. 创建项目。复制项目 micro-consumer-movie-feign-hystrix-fallback ,将 artifactId 修改为 micro-consumer-movie-feign-hystrix-fallback-stream 。

  2. 为项目添加依赖:

<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>
  1. 在启动类上添加 @EnableCircuitBreaker ,这样就可使用 /hystrix.stream 端点了。

  2. 修改启动端口号,后续聚合多个 /hystrix.stream 端点时用到。

server:
  port: 8020
  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-feign-hystrix-fallback-stream 。

  2. 访问 http://localhost:8020/movie/v1/1 后,再次访问:http://localhost:8020/hystrix.stream ,可看到页面会不断出现监控数据。

使用 Hystrix Dashboard 可视化监控数据

访问 /hystrix.stream 端点获得的数据是以文字形式展示的。很难通过这些数据,一眼看出系统当前的运行状态。

可使用 Hystrix Dashboard ,让监控数据图形化、可视化。

下面来编写一个 Hystrix Dashboard 。

  1. 使用 Spring Boot 快速构建项目 micro-hystrix-dashboard 。

  2. 为项目添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
  1. 在启动类上添加注解 @EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}
  1. 设置服务启动端口号。
server:
  port: 8030
  1. 启动测试。启动 Eureka Server: micro-discovery-eureka 、服务提供者:micro-provider-user 、服务消费者:micro-consumer-movie-ribbon-hystrix 、Hystrix Dashboard :micro-hystrix-dashboard 。

  2. 访问 http://localhost:8010/movie/v1/1 后,再次访问:http://localhost:8010/hystrix.stream ,可看到页面会不断出现监控数据。

  3. 访问 http://localhost:8030/hystrix ,并输入 http://localhost:8010/hystrix.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。

9.Hystrix Dashboar可视化监控

左上角的圆圈代表了该方法的流量和状态:

  • 圆圈越大代表方法流量越大;
  • 圆圈为绿色代表断路器健康、黄色代表断路器偶发故障、红色代表断路器故障;

右上角的计数器(三列数字):

第一列从上到下:

  • 绿色代表当前成功调用的数量
  • 蓝色代表短路请求的数量
  • 蓝绿色代表错误请求的数量

第二列从上到下:

  • 黄色代表超时请求的数量
  • 紫色代表线程池拒绝的数量
  • 红色代表失败请求的数量

第三列:

  • 过去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 中,从而让集群的监控更加方便。

10.Hystrix引入Turbine聚合监控数据

  1. 使用 Spring Boot 快速构建项目 micro-hystrix-turbine 。

  2. 为项目添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
  1. 在启动类上添加注解@EnableTurbine
@SpringBootApplication
@EnableTurbine
public class TurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(TurbineApplication.class, args);
    }
}
  1. 修改配置文件
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 这两个微服务,并聚合两个微服务的监控数据。

  1. 启动测试。启动 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 。

  2. 访问 http://localhost:8010/movie/v1/1 和 http://localhost:8020/movie/v1/1 ,再次访问: http://localhost:8031/turbine.strea。

  3. 访问 http://localhost:8030/hystrix ,并输入 http://localhost:8031/turbine.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。

11.Hystrix Dashboar可视化监控Turbine

使用 Rabbitmq 消息中间件收集监控数据

一些场景下,例如微服务与 Turbine 网络不通,此时,可借助消息中间件实现数据收集各个微服务将 Hystrix Command 的监控数据发送至消息中间件,Turbine 消费消息中间件中的数据。

12.Turbine MQ方式收集数据架构

  1. 采用 Docker + Docker-Composer 在虚拟机上快速部署一个 Rabbitmq 。

  2. 创建项目。复制 micro-consumer-movie-ribbon-hystrix ,修改 artifactId 为 micro-consumer-movie-ribbon-hystrix-turbine-mq 。

  3. 为项目添加依赖:

<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>
  1. 修改配置文件
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

  1. 创建项目。复制项目 micro-hystrix-turbine ,修改 artifactId 为 micro-hystrix-turbine-mq 。

  2. 为项目添加依赖:

<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 依赖。

  1. 修改启动类,添加注解@EnableTurbineStream
@SpringBootApplication
@EnableTurbineStream
public class TurbineMqApplication {
    public static void main(String[] args) {
        SpringApplication.run(TurbineMqApplication.class, args);
    }
}
  1. 修改配置文件
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 配置。

  1. 启动测试。启动 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 。

  2. 访问 http://localhost:8010/movie/v1/1 ,再次访问: http://localhost:8031/turbine.strea。

  3. 访问 http://localhost:8030/hystrix ,并输入 http://localhost:8031/turbine.stream ,点击 “Monitor Stream" 即可看到可视化监控数据。

代码仓库

https://gitee.com/chentian114/spring-cloud-practice

公众号

知行chen

参考

《Spring Cloud 与Docker 微服务架构实战》 周立

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值