SpringCouldHystrix 实现服务降级和服务熔断

Posted 覃会程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCouldHystrix 实现服务降级和服务熔断相关的知识,希望对你有一定的参考价值。

Spring Could主要内容结构图:





Hystrix(断路器)

概述

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

图中的请求需要调用A, P, H,I四个服务,如果一切顺利则没有什么问题,关键是如果I服务超时可能会引起服务雪崩

服务雪崩

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

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

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

Hystrix是什么:

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

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

Hystrix的重要概念

服务降级(fallback)

系统不可用了,需要向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。(简单说,你需要给我一个兜底的解决方法)

哪些情况会出发降级:

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

服务熔断(break)

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

服务限流(flowlimit)

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

Hystrix 应用案例

Hystrix实现降级

服务提供者使用Hystrix降级

设置自身调用超时时间的峰值,峰值内可以正常运行,
超过了需要有兜底的方法处理,作服务降级fallback

HystrixCommand报异常后如何处理

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

业务类:


@Service
public class PaymentServiceImpl implements PaymentService {

    public String getPaymentInfo_OK(Integer id) {
        return Thread.currentThread().getName()+"----OK----"+id;
    }

    // 也可以通过继承HystrixCommand类实现这个配置
    @HystrixCommand(fallbackMethod = "getPaymentInfo_ErrorHandler",commandProperties = {
            // 指定超时时间,即超过3秒钟进行降级
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String getPaymentInfo_Error(Integer id) {
//        return Thread.currentThread().getName()+"----ERROR----"+id;
        System.out.println(Thread.currentThread().getName()+"-----------error----------"+id);
        Integer timeout = 5;
        try {
            TimeUnit.SECONDS.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Thread.currentThread().getName()+"-----getPaymentInfo_Error----ERROR----"+id;
    }


    public String getPaymentInfo_ErrorHandler(Integer id) {
        return Thread.currentThread().getName()+"-----------getPaymentInfo_ErrorHandler----ERROR----"+id;
    }

}

在启动类中添加注解@EnableCircuitBreaker //激活hystrix

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker  //激活hystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }

    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
     * 只要在自己的项目里配置下面的Servlet就可以了
     * @return
     */
    @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;
    }
}

上面故意制造两个异常:
1 int age = 10/0;计算异常
2 我们能接受3秒钟,它运行5秒钟,超时异常。

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler
所以不光在超时的时候能降级,在发生异常的时候也能降级。

调用方使用Hystrix降级

yml配置文件中配置,开启hystrix

feign:
  hystrix:
    enabled: true  #在feign中开启hystrix

在启动类中添加注解@EnableHystrix //激活Hystrix

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

controller

@RestController
@RequestMapping("order")
public class OrderController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("test")
    public String test() {
        return "test";
    }

    @GetMapping("info/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String res = paymentHystrixService.paymentInfo_OK(id);
        System.out.println(res);
        return res;
    };

    @GetMapping("info/error/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String paymentInfo_ERROR(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_ERROR(id);
    }

    public String paymentInfo_ERROR_Handler(@PathVariable("id") Integer id){
        return "80____paymentInfo_ERROR_Handler___异常返回";
    }
}

默认的FallBack

上面每个业务方法对应一个兜底的方法,容易造成代码膨胀
应该将统—兜底方案和自定义的分开

@RestController
@RequestMapping("consumer")
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")// hystrix 全局fallback方法
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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


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

    /**
     * 超时访问,设置自身调用超时的峰值,峰值内正常运行,超过了峰值需要服务降级 自动调用fallbackMethod 指定的方法
     * <br/>
     * 超时异常或者运行异常 都会进行服务降级
     *
     * @param id
     * @return
     */
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }

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


}

通配服务降级FeignFallback

上面将程序异常和业务逻辑混—起,即正常调用的代码和降级的代码放在一个类里,容易混乱

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

根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,
重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

@Component
// FeignFallback 客户端的服务降级 针对 CLOUD-PROVIDER-HYSTRIX-PAYMENT 该服务 提供了一个 对应的服务降级类
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackServiceImpl.class)
//@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfoOK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
@Component
public class PaymentFallbackServiceImpl implements PaymentHystrixService {

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

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

}

Hystrix实现服务熔断

服务的降级->进而熔断->恢复调用链路

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

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

服务提供者实现熔断:

@Service
public class PaymentServiceImpl implements PaymentService {

//---服务的熔断
    @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 simpleUUID = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+"\\t" + "成功调用,流水号是:" + simpleUUID;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id不能为负数,请稍后再试............"+id;
    }


}

上面的配置表示在10秒钟的窗口期内,请求次数超过10次,且失败率达到60%,则进行熔断。

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。

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

服务熔断后,再次输入正确的数据也不会马上得到恢复,需要等待一段时间(默认是5秒),且失败率下降到60%的时候,才会得到恢复。

流程如下:

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

熔断类型:

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

常见问题:

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

  2. 原来的主逻辑要如何恢复呢?
    对于这一问题,hystrix也为我们实现了自动恢复功能当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
    当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

以上是关于SpringCouldHystrix 实现服务降级和服务熔断的主要内容,如果未能解决你的问题,请参考以下文章

SpringCouldHystrix 实现服务降级和服务熔断

什么是服务降级?springCloud如何实现?

Hystrix实现服务隔离和降级

通过API网关实现微服务管控-限流,熔断和降级

限流降级方案

dubbo服务降级