SpringCloud:服务熔断/降级-Hystrix断路器(2)

服务降级 / 服务熔断  / 服务限流

接上一篇:https://www.tinstu.com/2245.html

1.服务降级

不管是在消费者,还是提供者,都可以进行服务降级,使用过@HystrixCommand注解指定降级后的方法
一般服务降级 是放在 客户端

1.1服务降级-8001生产者服务端

cloud-provider-hystrix-payment8001

8001设置自身调用超时时间的峰值,峰值内可以正常运行。超过了就需要有兜底的方法,做服务降级fallback。向调用方法返回一个符合预期的,可以处理的备选响应(FallBack)

1.1.1业务类(Service)方法上添加@HystrixCommand

service 添加

    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            //规定这个线程的超时时间是3s,3s后就由fallbackMethod指定的方法帮我“兜底”(服务降级)
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })

    public String paymentInfo_Timeout(Integer id) {
        //模拟拥堵的情况
        int timeNumber = 5;  //模拟超时,上面规定的3000毫秒,超过就是超时了
        // int age= 10/0 ;  //模拟程序异常
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        return "线程池:" + Thread.currentThread().getName() + "--payment_Timeout,  id:  " + id;
    }
    public String paymentInfo_TimeoutHandler(){
       return "线程池:" + Thread.currentThread().getName() + "链接超时或程序异常,请稍后再试!!";
    }

一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand 标注好的fallbackMethod 调用类中的指定方法,当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler。

1.1.2主启动类激活

主启动类添加新注解@EnableCircuitBreaker

1.1.3测试

1.2服务降级-80消费者服务端(客户端)

cloud-consumer-feign-hystrix-order80

1.2.1修改yaml

feign:
  hystrix:
    enabled: true

1.2.2主启动类:@EnableHystrix

1.2.3业务类——Controller

Controller

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "ptoFall",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500") //1.5秒得不到响应就执行fall方法
    })
    public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
        //int a=10/0; //模拟程序出错
        return paymentHystrixService.paymentInfo_Timeout(id);
    }
    public String ptoFall(@PathVariable("id") Integer id){
        return "我是消费者80,服务端繁忙或程序出错,请稍后再试";
    }

1.2.4测试

因为80设置的1500得不到响应就执行fallbackmethod,而8001设置的3000ms 再进行返回,

所以,访问:http://127.0.0.1/consumer/payment/hystrix/timeout/1

1.3进一步优化

1.3.1 问题

目前存在的问题——代码臃肿、耦合度高

  • 我们现在服务降级的方法和业务处理的方法混杂在了一块,耦合度很高。
  • 如果每个接口,每个方法都需要一个“兜底”方法,那么就会造成代码臃肿

1.3.2 全局服务降级 DefaultProperties

所以我们需要一个全局的服务降级:global fallback;需要特殊照顾的方法,我们再进行精确的配置服务降级

1:N 除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = "")  统一跳转到统一处理结果页面
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。

-------------------------------------------

下面以消费者服务端80为例:cloud-consumer-feign-hystrix-order80

  • 在业务类Controller上加@DefaultProperties(defaultFallback = "method_name")注解,并指明defaultFallback
  • 在需要服务降级的方法上标注@HystrixCommand 注解
    • 如果@HystrixCommand里没有指明fallbackMethod,就默认使用@DefaultProperties(defaultFallback = "method_name")中指明的降级服务

1.3.3通配服务降级 FeignFallback

在80微服务中,它没有注册进eureka注册中心成为eureka客户端。

而是通过openFeign,成为一个Feign客户端,通过Feign来进行微服务的调用和负载均衡。

直接定义Service层的接口通过@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") 这个注解找CLOUD-PROVIDER-HYSTRIX-PAYMENT这个微服务中的方法进行调用。

解耦案例——服务降级:客户端调用服务端,遇到服务端宕机或关闭等极端情况

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

PaymentFallbackService

@Component  //不要忘记这个注解
public class PaymentFallbackService implements PaymentHystrixService{

    @Override
    public String paymentInfo_Ok(Integer id) {
        return "------PaymentFallbackService-paymentInfo_Ok, fallback";
    }

    @Override
    public String paymentInfo_Timeout(Integer id) {
        return "------PaymentFallbackService-paymentInfo_Timeout, fallback";
    }
}

 

修改PaymentHystrixService接口,指定其服务异常处理类:

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

确认yml配置文件中开启了hystrix

# 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
feign:
  hystrix:
    enabled: true  #在Feign中开启Hystrix

目的是为了实现以下效果:

 

  • 正常运行的话就找指定的微服务中的方法执行;
  • 异常的话就找PaymentFallbackService,由它来统一进行服务降级的处理

这样的话客户端的OrderHystirxController中就不需要再进行降级处理,降级处理只针对要调用的微服务,因此降低了服务降级和客户端的耦合度。

2.服务熔断

2.1熔断机制概述

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

大神论文:https://martinfowler.com/bliki/CircuitBreaker.html

半开状态: 比如说一个微服能承受100的并发量,某一时刻有500人同时访问。该服务就会崩掉,熔断,直接当掉。 外部此时不能访问了,过了一会,发现没有那么高的并发量了。感觉并发量在我的承受之内了(100)。 比如1s/72次,那么就试着这放开这些并发请求。 放着放着发现能够适应当前的并发量了,我再把闸道合上。 放着放着的状态就是半开状态,然后再把断路器关闭变成关闭的状态。

2.1.1对8001服务端service进行改造,开启熔断机制

paymentCircuitBreak

    @HystrixCommand(fallbackMethod = "rongduan",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") //失败率达到多少后跳闸
            //总的意思就是在n毫秒内的时间窗口期内,m次请求中有p%的请求失败了,那么断路器启动
    })
    public String paymentCircuitBreak(@PathVariable("id") Integer id){
       if(id < 0 ){
           throw new RuntimeException("****id不能为负数");
       }
       String a = IdUtil.simpleUUID();
       return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+a;
    }
    public String rongduan(@PathVariable("id") Integer id){
       return "id 不能为负数,请稍后重试!!";
    }

 

2.1.2修改模块cloud-provider-hystrix-payment8001的controller层

paymentCircuitBreaker

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

 

2.1.3测试

正常测试,启动7001,8001

测试熔断机制

在时间窗口期10s内,异常访问60%以上,发生断路,即正常也无法访问!

过一会刷新恢复正常!!!

2.2Hystrix服务熔断总结

2.2.1熔断类型

  • 熔断打开:请求不再进行调用当前服务,再有请求调用时将不会调用主逻辑,而是直接调用降级fallback。实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果。内部设置时钟一般为MTTR(Mean time to repair,平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态。
  • 熔断关闭:熔断关闭不会对服务进行熔断,服务正常调用
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

2.2.2断路器在什么情况下开始起作用

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

  • 1:快照时间窗(滚动窗口:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
    • 对应红框中的metrics.rollingStats.timeInMilliseconds
  • 2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
    • 对应红框中的circuitBreaker.requestVolumeThreshold
  • 3:窗口睡眠时间:剩下一个表示窗口睡眠时间,即断路器触发多少秒(默认5s)后尝试恢复,进入半开状态。
    • 对应红框中的circuitBreaker.sleepWindowInMilliseconds
  • 4:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
    • 对应红框中的circuitBreaker.errorThresholdPercentage

2.2.3断路器开启或关闭条件

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

2.2.4 断路器打开之后

1: 再有请求调用的时候,还会调用主逻辑吗?
将不会调用主逻辑,而是直接调用降级的fallback方法,通过断路器,实现了自动的发现错误并将降级逻辑升级为主逻辑,减少响应延迟的效果。

2:原来的主逻辑要如何恢复?

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

2.2.5所有的配置

hystrix所有配置

//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
        groupKey = "strGroupCommand",
        commandKey = "strCommand",
        threadPoolKey = "strThreadPool",

        commandProperties = {
                // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
                @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
                // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
                @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 配置命令执行的超时时间
                @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
                // 是否启用超时时间
                @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                // 执行超时的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
                // 执行被取消的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
                // 允许回调方法执行的最大并发数
                @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 服务降级是否启用,是否执行回调函数
                @HystrixProperty(name = "fallback.enabled", value = "true"),
                // 是否启用断路器
                @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
                // 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
                // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
                // circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
                // 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
                // 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
                // 如果成功就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
                // 断路器强制打开
                @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
                // 断路器强制关闭
                @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
                // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
                @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
                // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
                // 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
                // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
                @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
                // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
                @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
                // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
                @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
                // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
                @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
                // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
                // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
                // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
                @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
                // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
                @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
                // 是否开启请求缓存
                @HystrixProperty(name = "requestCache.enabled", value = "true"),
                // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
                @HystrixProperty(name = "requestLog.enabled", value = "true"),
        },
        threadPoolProperties = {
                // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
                @HystrixProperty(name = "coreSize", value = "10"),
                // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
                // 否则将使用 LinkedBlockingQueue 实现的队列。
                @HystrixProperty(name = "maxQueueSize", value = "-1"),
                // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
                // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
                // 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
                @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
        }
)
public String strConsumer() {
    return "hello 2020";
}
public String str_fallbackMethod()
{
    return "*****fall back str_fallbackMethod";
}

服务限流

在spring cloud alibaba Sentinel时再说!

阅读剩余
THE END