Hystrix

Hystrix

服务雪崩

多个微服务之间调用的时候, 假设微服务 A 调用微服务 B 和微服务 C, 微服务 B 和微服务 C 又调用其它的微服务, 这就是所谓的 “扇出”。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用, 对微服务 A 的调用就会占用越来越多的系统资源, 进而引起系统崩溃, 所谓的 “雪崩效应”。

对于高流量的应用来说, 单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟內饱和。

比失败更糟糕的是, 这些应用程序还可能导致服务之间的延迟增加, 备份队列, 线程和其他系统资源紧张, 导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理, 以便单个依赖关系的失败, 不能取消整个应用程序或系统。

所以,
通常当你发现—个模块下的某个实例失败后, 这时候这个模块依然还会接收流量, 然后这个有问题的模块还调用了其他的模块, 这样就会发生级联故障, 或者叫雪崩。

简介

官网,该项目已停止更新

分布式系统面临的问题:

复杂分布式体系结构中的应用程序有数十个依赖关系, 每个依赖关系在某些时候将不可避免地失败。


Hystrix

Hystrix 是一个用于处理分布式系统的 延迟容错 的开源库, 在分布式系统里, 许多依赖不可避兔的会调用失败, 比如超时、异常等, Hystrix 能够保证在一个依赖出问题的情况下, 不会导致整体服务失败, 避免级联故障, 以提高分布式系统的弹性

"断路器"本身是一种开关装置, 当某个服务单元发生故障之后, 通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个符合预期的、可处理的备选响应( Fallback), 而不是长时间的等待或者抛出调用方无法处理的异常, 这样就保证了服务调用方的线程不会被长时间、不必要地占用, 从而避免了故障在分布式系统中的蔓延, 乃至雪崩。

功能

  • 服务降级
    • 服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback
    • 哪些情况会触发降级
      • 程序运行异常
      • 超时
      • 服务熔断触发服务降级
      • 线程池/信号量打满也会导致服务降级
  • 服务熔断
    • 类比保险丝」达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
    • 服务的降级 -> 进而熔断 -> 恢复调用链路
  • 服务限流
    • 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行
  • 接近实时的监控

生产者

Pom

<dependencies>
    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <!--eureka client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <groupId>com.mrw</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Yaml

server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      defaultZone: http://eureka7001.com:7001/eureka

Main

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001
{
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }


    /**
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     *只要在自己的项目里配置上下面的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

Service

节约时间,此处 service 不再设置为接口

@Service
public class PaymentService {
    /**
     * 正常访问,肯定OK
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  paymentInfo_OK,id:  " + id + "\t" + "O(∩_∩)O哈哈~";
    }

    //####################降级注解####################
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String paymentInfo_TimeOut(Integer id) {
        //int age = 10/0;
        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗时(秒): 3";
    }
    //####################paymentInfo_TimeOut 降级方法####################
    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错,请稍后再试,id:  " + id + "\t" + "o(╥﹏╥)o";
    }

    //=====服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " + id;
    }

}

Controller

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentService.paymentInfo_OK(id);
        log.info("*****result: " + result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("*****result: " + result);
        return result;
    }

    //====服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: " + result);
        return result;
    }
}

启动访问

启动 Eureka7001、和该微服务

OK


TimeOut

Jmeter

使用 Jmeter 对 「TimeOut」接口进行 20000 次模拟请求

此时发现,正常的去访问「OK」接口响应也变慢了

原因:tomcat 的默认的工作线程数被打满了, 没有多余的线程来分解压力和处理。

消费者

Pom

<dependencies>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <!--eureka client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.mrw</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--一般基础通用配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Yaml

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

feign:
  hystrix:
    enabled: true

Main

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

Service

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" )
public interface PaymentHystrixService
{
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

Controller

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystirxController
{
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
}

启动访问

Jmeter

使用 Jmeter 对 「TimeOut」接口进行 20000 次模拟请求

此时发现,正常的去访问「OK」接口响应也变慢了


原因:

8001 同一层次的其它接口服务被困死, 因为 tomcat 线程池里面的工作线程已经被挤占完毕。
80 此时调用8001, 客户端访问响应缓慢, 转圈圈

总结

正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

解决问题

1、超时导致服务器变慢(转圈)

超时不再等待

对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级

2、出错(宕机或程序运行出错)

出错要有兜底

对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级

对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

服务降级

生产者

Main

@EnableCircuitBreaker」启用 Hystrix

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001
{
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }


    /**
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     *只要在自己的项目里配置上下面的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}
Service
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
	//指定当前线程的超时时间为 5 秒,超过了 5 秒则进行服务降级「fallback」处理
 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
})

@HystrixCommand

一旦调用服务方法失败并抛出了错误信息后,会自动调用 @HystrixCommand 标注好的 fallbackMethod 指定方法


注意

@HystrixCommand 内的配置修改建议重启微服务

@Service
public class PaymentService {
    /**
     * 正常访问,肯定OK
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  paymentInfo_OK,id:  " + id + "\t" + "O(∩_∩)O哈哈~";
    }

    /**
     * 暂停 6 秒钟
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
            //##############################设置5秒超时##############################
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String paymentInfo_TimeOut(Integer id) {
        //int age = 10/0;
        try {
            //##############################设置线程休眠6秒##############################
            TimeUnit.MILLISECONDS.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗时(秒): 3";
    }

    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错,请稍后再试,id:  " + id + "\t" + "o(╥﹏╥)o";
    }

    //=====服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " + id;
    }

}
重启访问

触发服务降级

总结

服务的「超时异常」和「运行异常」都会执行 fallbackMethod 所指定的方法给浏览器响应相应的内容

消费者

服务降级一般用在「服务调用者」端即 消费者端

Yaml
server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
########################################################################################
#开启 feign对hystrix的支持,可以直接在 @FeignClent 注解中的 fallback 属性指定回调的类#
########################################################################################
feign:
  hystrix:
    enabled: true
#############################################################################################################
#feign 集成了 ribbon,默认超时时间为 1 秒钟,如果配置的服务降级时间大于1秒钟,则需要修改ribbon的默认超时时间#
#否则,总会超时##############################################################################################
#############################################################################################################
ribbon:
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 15000
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000
Main

@EnableHystrix 源码自带 @EnableCircuitBreaker

两者都可以

@SpringBootApplication
@EnableFeignClients
//开启Hystrix
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}
Controller
@RestController
@Slf4j
public class OrderHystirxController
{
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
        	//###########设置1.5秒超时
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
    {
        //int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
    {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }
}

问题总结

  • 每个业务方法对应一个 fallback 方法,代码膨胀
  • 需要统一的 fallback 方法和自定义的 fallback 方法分开

问题解决

Controller

除了个别重要核心业务有专属, 其它普通的可以通过 @Default Properties(defaultFallback=") 统一跳转到统一处理方法

全局 fallback 方法也可以设置超时时间等配置

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystirxController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    @HystrixCommand
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "15000")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//        int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }

    // 下面是全局fallback方法
    public String payment_Global_FallbackMethod() {
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
    }
}

Feign 服务降级

使用 Feign 进行统一 fallback 配置

本次案例服务降级处理是在客户端 80 实现完成的,与服务端 8001 没有关系,只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)

微服务常见异常

  • 运行时异常
  • 超时异常
  • 服务宕机

Service

新建 PaymentFallbackService 类实现 PaymentHystrixService 接口

在实现类里去写每一个方法的 rollback 方法

PaymentHystrixService

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
public interface PaymentHystrixService
{
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

PaymentFallbackService

@Component
public class PaymentFallbackService implements PaymentHystrixService
{
    @Override
    public String paymentInfo_OK(Integer id)
    {
        return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id)
    {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
    }
}

Yaml

feign:
  hystrix:
    enabled: true

启动访问

启动 Eureka7001、PaymentHystrixMain8001、OrderHystrixMain80

先正常访问 http://127.0.0.1/consumer/payment/hystrix/ok/1

再关闭 8001再次访问

正常访问

关闭 8001 访问

服务熔断

机制描述

论文

熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。

当扇出链路的某个微服务出错不可用或者响应时间太长时, 会进行服务的降级, 进而熔断该节点微服务的调用, 快速返回错误的响应信息。

当检测到该节点微服务调用响应正常后,恢复调用链路

在 Spring Cloud 框架里, 熔断机制通过 Hystrix 实现。Hystrⅸ 会监控微服务间调用的状况,当失败的调用到定阈值, 缺省是 5秒内20次调用失败, 就会启动熔断机制。

熔断机制的注解是@HystrixCommand

生产者

Service

1000 ms「10 秒」 内 10 次请求有 60% 的失败率就会启动熔断器

HystrixCommandProperties 里有全部的配置

@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
})

熔断器的三个重要参数

  • 快照时间窗

    断路器确定是否打开需要统计一些请求和错误数据, 而统计的时间范围就是快照时间窗, 默认为最近的10秒

  • 请求总数阀值

    • 在快照时间窗内, 必须满足请求总数阀值才有资格熔断。

    • 默认为 20 , 意味着在 10 秒内, 如果该 Hystrix 命令的调用次数不足 20 次, 即使所有的请求都超时或其他原因失败, 断路器都不会打开。

  • 错误百分比阀值

    • 当请求总数在快照时间窗内超过了阀值, 比如发生了 30 次调用, 如果在这 30 次调用中, 有 15 次发生了超时异常, 也就是超过 50% 错误百分比, 在默认设定 50% 值情况下, 这时候就会将断路器打开。
@Service
public class PaymentService {
    /**
     * 正常访问,肯定OK
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  paymentInfo_OK,id:  " + id + "\t" + "O(∩_∩)O哈哈~";
    }

    /**
     * 暂停三秒钟
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
    })
    public String paymentInfo_TimeOut(Integer id) {
        //int age = 10/0;
        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗时(秒): 3";
    }

    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错,请稍后再试,id:  " + id + "\t" + "o(╥﹏╥)o";
    }

    //=======================================服务熔断=======================================
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " + id;
    }

}
Controller
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentService.paymentInfo_OK(id);
        log.info("*****result: " + result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("*****result: " + result);
        return result;
    }

    //========================服务熔断========================
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: " + result);
        return result;
    }
}
启动访问

启动 Eureka、PaymentHystrixMain8001

访问 http://127.0.0.1:8001/payment/circuit/{id}

前几次输入 id 都为负数,再改为正数,此时会发现,即使输入了正数,还是会调用失败。

过了窗口期后,调用成功。

熔断类型

  • 熔断打开
    • 请求不再进行调用当前服务,内部设置时钟一般为 MTTR (平均故障处理时间),当打开时长达到所设时钟则进入熔断状态。
  • 熔断关闭
    • 熔断关闭不会对服务进行熔断。
  • 熔断半开
    • 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。

熔断器开启条件

  1. 当满足一定阀值的时候(默认10 秒内超过 20 个请求次数)。
  2. 当失败率达到一定的时候(默认10 秒内超过 50% 请求失败)。
  3. 到达以上阀值,断路器将会开启。
  4. 当开启的时候,所有请求都不会进行转发。
  5. 一段时间之后(默认是 5 秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启,重复 4 和 5。

熔断器开启后

  1. 再有请求调用的时候, 将不会调用主逻辑, 而是直接调用降级 fallback。通过断路器,实 现了自动地发现错误并将降级逻辑切换为主逻辑, 減少响应延迟的效果。

  2. 原来的主逻辑要如何恢复呢?
    对于这一问题, Hystrix 也为我们实现了自动恢复功能。

    当断路器打开, 对主逻辑进行熔断之后, Hystrix 会启动—个休眠时间窗, 在这个时间窗内, 降级逻辑是临时的成为主逻辑。

    当休眠时间窗到期, 断路器将进入半开状态, 释放一次请求到原来的主逻辑上, 如果此次请求正常返回, 那么断路器将继续闭合, 主逻辑恢复。

    如果这次请求依然有问题, 断路器继续进入打开状态, 休眠时间窗重新计时。

服务限流

讲解放在 Alibaba

服务监控

除了隔离依赖服务的调用以外, Hystrⅸ 还提供了准实时的调用监控( Hystrixκ Dashboard), Hystrⅸ 会持续地记录所有通过 Hystrⅸ 发起的请求的执行信息, 并以统计报表和图形的形式展示给用户, 包括每秒执行多少请求多少成功, 多少失败等。

Netflix 通过 hystrix-metrics-event-stream 项目实现了对以上指标的监控。

Spring Cloud 也提供了 Hystrix Dashboard 的整合, 对监控内容转化成可视化界面。

Pom

被监控微服务也都需要引入

//监控信息完善
<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Yaml

server:
  port: 9001

Main

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

启动访问

启动该微服务

访问 http://localhost:9001/hystrix

监控其他微服务

  1. 被监控微服务 pom 中一定要引入 spring-boot-starter-actuator

  2. 被监控微服务 Main 方法中需要引入以下配置,否则会报 Unable to connect to Command Metric Stream

    @SpringBootApplication
    @EnableEurekaClient
    @EnableCircuitBreaker
    public class PaymentHystrixMain8001
    {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8001.class, args);
        }
    
    //##############################################################################################
        /**
         *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
         *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
         *只要在自己的项目里配置上下面的servlet就可以了
         */
        @Bean
        public ServletRegistrationBean getServlet() {
            HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
            registrationBean.setLoadOnStartup(1);
            registrationBean.addUrlMappings("/hystrix.stream");
            registrationBean.setName("HystrixMetricsStreamServlet");
            return registrationBean;
        }
    //##############################################################################################
    }
    
  3. 启动 Eureka、监控微服务和被监控微服务

    输入 http://localhost:8001/hystrix.stream 和其他相关信息

  4. 点击按钮进入监控页面(默认是 Loading),开始访问被监控微服务后,页面会显示监控数据‘

  5. 如何看监控页面

    • 7 色,7 种颜色一一对应

    • 一圈

      实心圆:共有两种含义。

      它通过颜色的变化代表了实例的健康程度, 它的健康度从绿色 < 黄色 < 橙色 < 红色递减
      该实心圆除了颜色的变化之外, 它的大小也会根据实例的请求流量发生变化, 流量越大该实心圆就越大。所以通过该实心圆的展示, 就可以在大量的实例中快速的发现故障实例和高压力实例。

    • 一线

      曲线:用来记录 2 分钟内 流量的相对变化, 可以通过它来观察到流量的上升和下降趋势

    • 具体更详细内容可自行百度

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值