Hystrix服务降级

发布于 2022-04-19  5.04k 次阅读


一,Hystrix的概述

一,服务降级的背景

分布式系统面临的问题:复杂分布式体系结构中的应用程序依赖众多,每一个依赖在某些时候将不可避免地失败,那么在一系列服务调用链中一个服务失败可能就意味着整个系统崩溃

服务雪崩:多个微服务相互调用,假如微服务1调用微服务2和微服务3,微服务3又调用微服务4,微服务4又调用其他微服务,这就是的所谓的"扇出"任何扇出的链路上某个微服务调用响应时间过长或者不可用,对于微服务1的调用就会占用更多的系统资源,进而引起系统崩溃,所谓"雪崩效应"

  对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更严 重的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或者系统

  通常当发生一个模块下的某个实例失败后,这个时候这个模块依然还会接收流量,然后这个问题的模块还调用其他模块,这样就会发生级联故障(雪崩)

二,Hystrix的简介

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

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

三,服务降级,熔断,限流的概念

服务降级:服务出错或者网络故障时不进行长时间等待和直接报异常,而是调用兜底方法返回友好提示(FallBack)

哪些情况会触发降级:

  1. 程序运行异常
  2. 网络超时
  3. 服务熔断触发服务降级
  4. 线程池/信号量打满也会服务降级

服务降级主要解决的两大问题:

  1. 超时导致程序变慢,还有可能导致超时不再等待的错误
  2. 出错或者宕机(程序运行报错),需要兜底方法

服务熔断:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回提示

熔断本质上也属于服务降级,只不过是更严重的服务降级,需要某个服务快导致系统级错误的时候才会触发熔断

熔断的过程:服务降级  --> 熔断  -->  返回提示和兜底方法 -->恢复调用链路

服务限流:秒杀高并发等操作,严禁一次过多的访问,对访问流量进行限制(削峰)

二,Hystrix的使用

一,不使用Hystrix的情况下进行服务调用并压测

在不使用Hystrix的情况下进行服务调用(服务提供者和服务调用者),进行压测,看是否在不进行服务降级的情况下服务是否被拖慢,甚至超时宕机

一,服务提供方

模块名称:cloud-provider-hystrix-payment

① 父依赖省略

② maven依赖

<dependency>
       <groupId>com.cloud.commons</groupId>
       <artifactId>cloud-api-commons</artifactId>
   </dependency>

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

   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-actuator</artifactId>
   </dependency>

   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-devtools</artifactId>
   </dependency>

   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <scope>provided</scope>
   </dependency>

   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>

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

③ application配置

server:
  port: 8086
spring:
  application:
    name: cloud-provider-hystrix-payment
eureka:
  client:
    fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
    register-with-eureka: true #是否将自己注册进Eureka Server
    service-url:
      defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/

④ main启动类

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

⑤ service

@Service
public class pymentservice {
        
    //正常方法
    public String paymentinfo_Ok(String id){
        return "线程"+Thread.currentThread().getName()+"paymentinfo_Ok:id"+id;
    }
    
    //延时方法
    public String paymentinfo_TimeOut(String id){

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "线程"+Thread.currentThread().getName()+"paymentinfo_TimeOut:id"+id;
   }}

⑥ controller

@RestController
@Slf4j
public class paymentcontroll {

    @Resource
    pymentservice pymentservice;

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

    @RequestMapping("/hystrix/ok/{id}")
    public String ok(@PathVariable("id") String id){
        String s = pymentservice.paymentinfo_Ok(id);
        log.info(s);
        return s;
    }

    @RequestMapping("/hystrix/timeout/{id}")
    public String timeout(@PathVariable("id") String id){
        String s = pymentservice.paymentinfo_TimeOut(id);
        log.info(s);
        return s;
    }
}

二,服务消费方

模块名称:cloud-consumer-hystrix-order① maven依赖(和服务端提供端一样,加一个OpenFeign服务调用)

② application配置

server:
  port: 8087
eureka:
  client:
    fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
    register-with-eureka: true #是否将自己注册进Eureka Server
    service-url:
      defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/
spring:
  application:
    name: cloud-consumer-hystrix-order
feign: #openfeign开启hystrix服务降级
  hystrix:
    enabled: true
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000

③ main启动类

@SpringBootApplication
@EnableFeignClients
public class HystrixOrderMain {

    public static void main(String[] args) {
        SpringApplication.run(HystrixOrderMain.class,args);
    }}

④ service

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface feignserver {

    @RequestMapping("/hystrix/ok/{id}")
    public String ok(@PathVariable("id") String id);

    @RequestMapping("/hystrix/timeout/{id}")
    public String timeout(@PathVariable("id") String id);
}

⑤ controller

@RestController
public class hystrixorder {

    @Resource
    feignserver feignserver;

    @RequestMapping("/hystrixorder/ok/{id}")
    public String ok(@PathVariable("id") String id){

        return feignserver.ok(id);
    }

    @RequestMapping("/hystrixorder/timeout/{id}")
    public String timeout(@PathVariable("id") String id){

        return feignserver.timeout(id);
    }
}

三,压测

启动压测访问后,非延时请求出现延迟

二,使用Hystrix进行服务降级

一,提供方进行Hystrix服务降级

HyStrix服务降级的步骤:

  • 启动类@EnableCircuitBreaker启动hystrix
  • 在调用的service上标注@HystrixCommand进行服务降级

在原基础上进行改造

① main方法启动Hystrix

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PymentHystrixMain {

    public static void main(String[] args) {

        SpringApplication.run(PymentHystrixMain.class,args);
    }
}

② 服务降级

 @HystrixCommand(fallbackMethod = "paymentinfo_Hander",
                    commandProperties = {
                                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3 000")
                    })
    public String paymentinfo_TimeOut(String id){
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {

            e.printStackTrace();
        }
        return "线程"+Thread.currentThread().getName()+"paymentinfo_TimeOut:id"+id;
    }
//fallback兜底方法,当服务出现超时,错误,宕机fallbackmethod调用此方法
    public String paymentinfo_Hander(String id){

        return "线程:"+Thread.currentThread().getName()+id+" --> 服务超时或者错误!请稍后在试!";


    }}

@HystrixCommand的参数:

  • fallbackMethod:当服务出现超时,错误,宕机问题,调用什么方法进行返回
  • commandProperties:参数设置,在里面有很多可设置的参数

二,消费方进行Hystrix服务降级

因为服务消费方使用OpenFeign进行服务调用,所以使用Hystrix有些区别

步骤:

  • application配置文件开启hystrix
  • main标注@EnableHystrix
  • 方法使用@HystrixCommand进行服务降级

① 在配置文件中开启Hystrix

feign:
  hystrix:
    enabled: true

② mian方法

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class HystrixOrderMain {

    public static void main(String[] args) {
        SpringApplication.run(HystrixOrderMain.class,args);
    }
}

③ 服务降级

@RequestMapping("/hystrixorder/timeout/{id}")
@HystrixCommand(fallbackMethod = "hystrix_fallback",
        commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
        })
public String timeout(@PathVariable("id") String id){


    return feignserver.timeout(id);
}


public String hystrix_fallback(@PathVariable("id") String id){


    return "服务出现超时或者错误,请稍后再试!";
}

注:Hystrix服务提供方和消费方都能设置,再开发中一般设置在消费方

测试:

三,FallBackMethod方法解耦

在Hystrix通过@HystrixCommand设置时,需要指定fallback方法,假如需要服务降级的方法一多,就需要重复设置,很麻烦,而且fallbackmethod是耦合的,在代码逻辑上不重用

一,@DefaultProperties全局服务降级

@DefaultProperties通用方法降级标注在类上,指定通用的服务fallbackmethod方法,在所有方法上生效不需要通过@HystrixCommand单独指定

@RestController
@DefaultProperties(defaultFallback = "hystrix_fallback")
public class hystrixorder {

    @Resource
    feignserver feignserver;


    @RequestMapping("/hystrixorder/timeout/{id}")
    @HystrixCommand
    public String timeout(@PathVariable("id") String id){


        return feignserver.timeout(id);
    }

    public String hystrix_fallback(@PathVariable("id") String id){

        return "服务出现超时或者错误,请稍后再试!";
    }
}

二,Hystrix通用服务降级

通用服务降级一般设置在服务消费端,和OpenFeign整合使用

步骤:

  • 设置类实现OpenFeign接口并重写其中方法
  • 在OpenFeign接口的@FeignClient设置fallback属性

① openfeign接口

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = feignserviceimpl.class)
public interface feignserver {


    @RequestMapping("/hystrix/ok/{id}")
    public String ok(@PathVariable("id") String id);


    @RequestMapping("/hystrix/timeout/{id}")
    public String timeout(@PathVariable("id") String id);
}

② 实现并重写openfeign接口

@Component
public class feignserviceimpl implements feignserver {//feignserver为自定义接口
    @Override
    public String ok(String id) {
        return "服务出现超时或者错误,请稍后再试!!";
    }

    @Override
    public String timeout(String id) {
        return "服务出现超时或者错误,请稍后再试!!";
    }
}

③ controller

@RequestMapping("/hystrixorder/timeout/{id}")
@HystrixCommand
public String timeout(@PathVariable("id") String id){

    return feignserver.timeout(id);
}

测试:

四,Hystrix服务熔断

熔断概述:熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间超时。会进行服务降级,进而熔断该节点微服务的调用,快速返回错误的响应信息,当检测该节点微服务调用响应正常后,再恢复链路

在Spring Cloud框架里,熔断机制通过Hystrix实现,Hystrix会监控微服务间的调用状况,当失败的调用到一定域值,缺省5内20次调用失败,就会触发熔断机制,熔断机制也使用@HystrixCommand进行设置

熔断的三种状态:

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

熔断器设置的重要参数:快照时间窗,请求总数阈值,错误百分比阈值

  • 快速时间窗口期:熔断器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗口,默认为10秒
  • 请求总数阈值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开
  • 错误百分比阈值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开

这些参数在Hystrix的设置:

  • circuitBreaker.enabled:是否打开熔断器
  • circuitBreaker.requestVolumeThreshold:请求总次数(请求总数阈值)
  • circuitBreaker.sleepWindowInMilliseconds:窗口时间(快速时间窗口期)
  • circuitBreaker.errorThresholdPercentage:失败率(错误百分比阈值)

演示:

@RequestMapping("/hystrix/breaker/{id}")
   @HystrixCommand(fallbackMethod ="fashingCiruitBreaker",
                   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 paymentCiruitBreaker(@PathVariable("id") Integer id){

       if(id<0){
           throw new RuntimeException("id不能为负数,/(ㄒoㄒ)/~~");
       }
       String s = IdUtil.simpleUUID();

       return "订单流水号:"+s;
   }


   public String fashingCiruitBreaker(@PathVariable("id") Integer id){

       return "熔断!!!";
   }

五,HyStrix图形化Dashboard的搭建使用

  除了隔离依赖服务的调用以外, Hystrix还提供了准实时的调用监控(Hystrix Dashboard). Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。 Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面

一,Dashboard的搭建

①新建一个模块(作为Dashboard的监控端)

② maven依赖

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

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

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

③ application配置

server:
  port: 9096

spring:
  application:
    name: cloud-hystrix-dashboard

④ main方法

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

访问:

二,Dashboard监控服务端

Dashboard监控服务提供和消费端需要对服务提供或消费端进行配置

配置:在main方法中加入一个bean

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PymentHystrixMain {
    public static void main(String[] args) {
        SpringApplication.run(PymentHystrixMain.class,args);
    }
    /*此配置是为了服务监控而配置,与服务容错本身无关,
     springcloud升级后的坑servletRegistrationBean因为springboot 的默认S径不是"/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;
    }
}

监控:

图例说明:


路漫漫其修远兮,吾将上下而求索