SpringCloud下基于Hystrix的服务实践

ProjectDaedalus

共 4419字,需浏览 9分钟

 ·

2021-11-09 06:47

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

abstract.jpeg

服务降级

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

<dependencyManagement>
  <dependencies>

    
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-dependenciesartifactId>
      <version>2.2.2.RELEASEversion>
      <type>pomtype>
      <scope>importscope>
    dependency>

    
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-dependenciesartifactId>
      <version>Hoxton.SR1version>
      <type>pomtype>
      <scope>importscope>
    dependency>

  dependencies>
dependencyManagement>

<dependencies>
  
  
  <dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
  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>

    
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-dependenciesartifactId>
      <version>2.2.2.RELEASEversion>
      <type>pomtype>
      <scope>importscope>
    dependency>

    
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-dependenciesartifactId>
      <version>Hoxton.SR1version>
      <type>pomtype>
      <scope>importscope>
    dependency>

  dependencies>
dependencyManagement>

<dependencies>
  
  
  <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
  dependency>

  
  <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
  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>
  
  
  <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-actuatorartifactId>
    <version>2.2.2.RELEASEversion>
  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著
浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报