首页 文章详情

SpringCloud下基于Hystrix的服务实践

ProjectDaedalus | 149 2021-11-09 06:47 0 0 0
UniSMS (合一短信)

Hystrix是Netflix开源的一款分布式容错框架,其为微服务提供了一整套服务保护、容错机制。从而避免由于个别服务故障而引起的级联故障,即所谓的服务雪崩效应

abstract.jpeg

服务降级

所谓服务降级是指所调用服务发生意外时,调用自己本地方法(即fallback降级方法)返回缺省值。而导致服务降级的原因包括但不限于服务超时、异常、宕机、熔断、服务资源不足(例如线程、信号量等)等。实践过程中,首先在payment服务的POM文件引入Hystrix依赖,如下所示

<dependencyManagement>
  <dependencies>

    <!--Spring Boot-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.2.2.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <!--Spring Cloud-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.SR1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

  </dependencies>
</dependencyManagement>

<dependencies>
  
  <!-- Hystrix -->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  </dependency>

</dependencies>

可通过@HystrixCommand注解,配置该方法所对应的降级方法。具体通过fallbackMethod属性配置一个入参、出参类型一致的降级方法。进一步地,还可以通过commandProperties属性设置相关降级配置。但如果需要对每个@HystrixCommand注解都添加重复的配置,显然十分麻烦。故可以在类上添加@DefaultProperties注解设置该类默认的降级属性配置,其会对该类中添加了@HystrixCommand注解的方法,提供默认的降级配置。特别地对于该类默认的降级方法,一方面通过defaultFallback属性设置;另一方面该降级方法是无入参的,但出参类型需要与被降级的方法保持一致

@RestController
@RequestMapping("pay2")
// 该类默认的降级配置
@DefaultProperties(defaultFallback = "defaultFallback")
public class PaymentController2 {

    @GetMapping("/test1")
    // 单独配置该方法的降级方法, 并将方法的超时配置由默认1s改为5s
    @HystrixCommand(fallbackMethod = "timeoutFallback", commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String test1(@RequestParam Integer time) {
        // 模拟业务耗时
        try {
            Thread.sleep( time );
        } catch (Exception e) {
        }

        String msg = "[Payment Service - test1], time:" + time;
        return msg;
    }

    @GetMapping("/test2")
    // 使用@DefaultProperties注解提供的默认降级相关配置
    @HystrixCommand
    public String test2(@RequestParam Integer time) {
        // 模拟业务耗时
        try {
            Thread.sleep( time );
        } catch (Exception e) {
        }

        String msg = "[Payment Service - test2], time:" + time;
        return msg;
    }

    @GetMapping("/test3")
    // 使用@DefaultProperties注解提供的默认降级相关配置
    @HystrixCommand
    public String test3(@RequestParam Integer num) {
        int result = 10/num;
        String msg = "[Payment Service - test3], num:" + num;
        return msg;
    }

    /**
     * 超时降级方法
     * @param time
     * @return
     */

    public String timeoutFallback(Integer time) {
        return "payment服务发生超时, param: time: " + time;
    }

    /**
     * 默认降级方法
     * @return
     */

    public String defaultFallback() {
        return "payment服务暂不可用";
    }

}

最后,在启动类上添加@EnableHystrix注解以启用Hystrix

@SpringBootApplication
@EnableDiscoveryClient // 使用Consul作为注册中心时使用
@EnableHystrix // 启用Hystrix
public class PaymentApplication {
    public static void main(String[] args) {
        SpringApplication.run(PaymentApplication.classargs);
    }
}

测试效果如下,符合预期

figure 1.jpeg

服务熔断

Hystrix的服务熔断采用断路器模式的思想进行设计。在该模式中存在如下三种状态

  • Closed:关闭状态。接受请求,服务的主逻辑可以被访问调用
  • Open:打开状态。拒绝请求,服务的主逻辑无法执行。该服务如果有相应的降级方法则执行降级方法,否则直接抛出错误响应
  • Half-Open:半开状态。处于该状态时,其会允许一部分请求流量通过,尝试执行主逻辑。如果发现调用成功则说明当前服务可以被恢复,进而变为Closed状态;否则调用失败,继续变为Open状态

从上可以看到,正是由于Half-Open状态的存在,为服务的自我恢复提供了一种思路。关于上述三种状态的转换逻辑,如下所示。在服务初始阶段,断路器肯定处于Closed状态。在断路器统计的滑动时间窗口内,满足一定的打开条件时,断路器才会进入Open状态。当断路器进入Open状态达到一定时间后会进入Half-Open状态,让一部分请求通过以验证服务是否可以被恢复,并根据验证结果决定进入Closed或Open状态

figure 2.jpeg

这里需要补充说明的是,断路器统计的滑动时间窗口可通过 metrics.rollingStats.timeInMilliseconds 进行配置。而断路器的打开条件则是指在该时间窗口内,其接受的请求数、请求的失败率均达到阈值。需要注意的是,这两个阈值必须同时满足。可分别通过 circuitBreaker.requestVolumeThresholdcircuitBreaker.errorThresholdPercentage 进行配置。最后,对于断路器在进入Open状态后需要多长时间才会进入Half-Open状态,则可通过 circuitBreaker.sleepWindowInMilliseconds 进行配置

为了方便验证测试,我们在PaymentController2类继续添加一个新的方法test4

@RestController
@RequestMapping("pay2")
// 该类默认的降级配置
@DefaultProperties(defaultFallback = "defaultFallback")
public class PaymentController2 {

    ...

    @GetMapping("/test4")
    @HystrixCommand( commandProperties = {
        // 打开断路器
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
        // 断路器统计的滑动时间窗口, Unit: ms
        @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000"),
        // 断路器的请求数阈值
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
        // 断路器的请求失败率阈值, Unit: %
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"),
        // 断路器进入Open打开状态多长时间后, 允许再次尝试处理部分请求(即Half-Open半开状态), Unit: ms
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
    })
    public String test4(@RequestParam Integer num) {
        if(num < 50) {
            throw new RuntimeException("非法参数异常");
        }

        String msg = "[Payment Service - test4], num:" + num;
        return msg;
    }

    /**
     * 默认降级方法
     * @return
     */

    public String defaultFallback() {
        return "payment服务暂不可用";
    }

}

测试结果如下所示。第①次访问时,可以看到访问正常,执行了主流程;第②次访问时,发送了5个请求且访问结果均失败被降级;然后马上开始第③次访问,从测试结果可以看出由于该服务已经被熔断了,直接走降级方法;等过了一段时间后,服务恢复第④次访问成功

figure 3.jpeg

服务限流

Hystrix提供了两种隔离策略:线程池隔离、信号量隔离。这里我们以前者为例介绍如何进行限流,信号量隔离同理。在线程池隔离的场景下,可通过控制线程池相关参数实现流量控制。这样当线程池资源不足时,对于新的请求就进行降级。为了方便验证测试,我们在PaymentController2类继续添加一个新的方法test5

@RestController
@RequestMapping("pay2")
// 该类默认的降级配置
@DefaultProperties(defaultFallback = "defaultFallback")
public class PaymentController2 {

    ...
    
    @GetMapping("/test5")
    @HystrixCommand(
        commandProperties = {
            // 方法超时配置, Unit: ms
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "15000"),
            // 资源隔离模式: 线程池隔离
            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")
        },
        threadPoolProperties = {
            // 核心线程数
            @HystrixProperty(name = "coreSize", value = "2"),
            // 等待队列容量, -1表示不启用
            @HystrixProperty(name = "maxQueueSize", value = "-1")
        }
    )
    public String test5(@RequestParam Integer num) {
        if(num < 50) {
            throw new RuntimeException("非法参数异常");
        }

        // 模拟业务耗时
        try {
            Thread.sleep( 10000 );
        } catch (Exception e) {
        }

        String msg = "[Payment Service - test5], num:" + num;
        return msg;
    }

    /**
     * 默认降级方法
     * @return
     */

    public String defaultFallback() {
        return "payment服务暂不可用";
    }

}

对于该服务接口其最大并发量为2。这样同时发送3个请求时,可以看到前2个请求正常处理。但第3个请求由于(线程池)资源不足被限流了,故进行降级处理。测试结果符合预期

figure 4.jpeg

服务监控

Hystrix还提供了Dashboard以便通过可视化的方式实现服务监控。这里我们建立一个HystrixDashboard服务对目标服务进行监控。首先在POM中我们引入 spring-cloud-starter-netflix-hystrix-dashboard 依赖,如下所示

<dependencyManagement>
  <dependencies>

    <!--Spring Boot-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.2.2.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <!--Spring Cloud-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.SR1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

  </dependencies>
</dependencyManagement>

<dependencies>
  
  <!-- Hystrix Dashboard-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
  </dependency>

  <!--SpringBoot-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

</dependencies>

然后为该服务添加一个启动类即可,并通过添加@EnableHystrixDashboard注解实现Hystrix Dashboard的启用

@SpringBootApplication
// 启用 Hystrix Dashboard
@EnableHystrixDashboard
public class HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.classargs);
    }
}

对于HystrixDashboard服务而言,其配置文件如下所示

spring:
  application:
    name: HystrixDashboard

server:
  port: 9111

这里我们以监控payment服务为例展开说明,在此之前我们需要对被监控服务payment进行一些必要的配置。首先需要在POM文件中添加 spring-boot-starter-actuator 依赖

<dependencies>
  
  <!-- Spring Boot Actuator -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.2.RELEASE</version>
  </dependency>

</dependencies>

与此同时,需要配置Actuator以开启 hystrix.stream 端点,相关配置如下所示

# Actuator配置: 开启 hystrix.stream 端点
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
      base-path: /actuator

启动payment、HystrixDashboard服务,分别使用8006、9111端口。首先访问payment服务的 hystrix.stream 端点验证是否有相关监控数据,打开 http://localhost:8006/actuator/hystrix.stream 页面。效果如下,符合预期

figure 5.jpeg

然后访问HystrixDashboard服务,打开 http://localhost:9111/hystrix 页面。并在页面上填写被监控服务的 hystrix.stream 端点地址、延迟时间、被监控服务名称

figure 6.jpeg

最后,点击Monitor Stream开启监控,效果如下所示

figure 7.jpeg

参考文献

  1. Spring微服务实战 John Carnell著
good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter