SpringCloud之Hystrix 服务熔断降级

本文介绍Hystrix在微服务架构中的应用,包括服务熔断、降级和限流等核心功能,以及如何实现这些功能的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.分布式系统面临的问题:

在微服务架构里,一个系统会有很多的服务。如下图:
在这里插入图片描述
上图请求需要调用 A,H,I, P 四个服务,如果一切顺利没有什么问题,倘若其中某一个服务,机房着火,不幸挂掉了会怎么样?是不是每次调用这个服务的时候,都会卡住几秒钟,然后抛出—个超时异常。如果这是在高并发的场景下,大量请求涌过来,导致请求都卡在这里,没有其他线程去处理正常的服务,就会导致的恐怖的服务雪崩。

1.1:服务雪崩:

多个微服务之前调用的时候,假设服务A调用服务B和服务C,服务B和服务C又调用其他的微服务,这就是所谓的扇出,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的服务雪崩。这个情况我们如何处理呢?下面我们掌声有请我们的一号男嘉宾–Hystrix 豪猪先生!
在这里插入图片描述

2. Hystrix是什么?

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

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

3.Hystrix能干什么?

  • 服务熔断
  • 服务降级
  • 接近实时的监控
  • 限流、隔离等

一号男嘉宾真是多才多艺,一表人才呀,活生生的高富帅呀,这不赶紧爆灯?
对不起,Hystrix 官宣停更!
臧克家:“有的人活着,它已经死了”
Eureka,Hystrix :“滚…”
在这里插入图片描述
贴心不,感动不,我就知道你们看不懂英文,我也看不懂!它停更以后,推荐使用Resilience4j,但是国内还是推荐使用Sentinel,后续会更新,大家多多关注。

4.Hystrix核心概念

4.1服务降级:

“服务器忙,请稍后再试!”,不让客户端等待并立刻返回一个友好提示(fallback)

哪些情况会发出降级?

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

4.2服务熔断:

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

4.3服务限流:

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

5.Hystrix实现服务降级

5.1 服务端实现服务降级

①:在父工程下,新建Module : cloud-provider-hystrix-payment8001

②:引入相关依赖:

 <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>
        <!--引入自己定义的api通用包,可以使用Payment支付Entity-->
        <dependency>
            <groupId>com.mk</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <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>

③: application.yml 配置文件

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

④:Service层

@Service
public class PaymentService {

    public String paymentInfo_ok(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok,id:"+id+"\t"+"访问顺利,O(∩_∩)O哈哈~";
    }

    public String paymentInfo_timeOut(Integer id){
        try {  // 超时异常
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
    }

}

⑤:控制层:

@RestController
public class PaymentController {

     @Autowired
     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);
        return result;
    }
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_timeOut(@PathVariable("id") Integer id)
    {
        String result = paymentService.paymentInfo_timeOut(id);
        return result;
    }

}

⑥:主启动类

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

启动cloud-eureka-server7001,cloud-provider-hystrix-payment8001访问测试:
在这里插入图片描述
可以发现ok的方法访问的话是立刻加载完成,超时那个方法要等待3秒钟,现在貌似没什么大问题?!
Jemete压测:
在这里插入图片描述
在这里插入图片描述
两万的并发量再次查看结果,发现ok那个方法,也被拖垮了,开始了爱的魔力转圈圈…好家伙,终于找到理由换电脑了
在这里插入图片描述
自己玩都那么费劲了,如果80再调用这个服务,嘶~
在这里插入图片描述
如何解决尼?为了大家能直观的看到,上面写了那么多,如感觉耽误你时间了,请见谅!
豪猪哥,醒一醒,醒醒,该你表演了!

对8001做如下修改:

启动类加@EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix服务熔断降级
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
}

PaymentService 修改后如下:

@Service
public class PaymentService {

    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 = "2000")
    })
    public String paymentInfo_timeOut(Integer id){
        try {  // 超时异常
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
    }
    //出现异常,超时 则进入这个方法
    public String paymentInfo_timeOutHandler(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"id:"+id+"\t"+"当前服务器繁忙,请稍后再试!o(╥﹏╥)o";
    }

}

@HystrixCommand(fallbackMethod = “paymentInfo_timeOutHandler”,commandProperties = {
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”,value = “2000”)
})
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了或有异常就进入fallbackMethod ,做服务降级

在这里插入图片描述
启动测试:
峰值两秒,程序延迟3秒,测试结果如下
在这里插入图片描述

5.2 消费端实现服务降级

①:在父工程下,新建Module : cloud-consumer-feign-hystrix-order80

②:引入相关依赖:

<dependencies>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</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.mk</groupId>
        <artifactId>cloud-api-common</artifactId>
        <version>${project.version}</version>
    </dependency>
    <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>

③:application.yml 配置文件

server:
  port: 80

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

④: 主启动类

@SpringBootApplication
@EnableFeignClients //开启feign进行远程调用
@EnableHystrix //开启hystrix服务熔断和降级
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}")
    String paymentInfo_ok(@PathVariable("id") Integer id);

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

⑥:控制器

@RestController
public class OrderHystrixController {

    @Autowired
    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 = {
            @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";
    }
}

启动测试:
80等待峰值为1.5秒,程序要运行3秒,结果如下
在这里插入图片描述

5.3 全局服务降级

通过前面的示例可以发现,每一个业务方法,都要对应一个处理方法,这样的代码显得很臃肿,是不是可以设置全局的处理方法,特殊的再自己定制自己的特殊化处理比较好。

修改PaymentService

@Service
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class PaymentService {

    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 = "2000")
    })*/
    @HystrixCommand
    public String paymentInfo_timeOut(Integer id){
        /*try {  // 超时异常
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        int i =10/0;
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_timeOut,id:"+id+"\t"+"访问耗时3秒";
    }
    //出现异常,超时 则进入这个方法
    public String paymentInfo_timeOutHandler(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"id:"+id+"\t"+"当前服务器繁忙,请稍后再试!o(╥﹏╥)o";
    }

    public String payment_Global_FallbackMethod() {
        return "全局设置,服务器繁忙";
    }

}

启动测试如下:
在这里插入图片描述
现在还有个问题就是服务降级代码和业务逻辑代码写在一起,看着是不是很难受,接下来实现解耦

为PaymentHystrixService增加实现类:

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_ok(Integer id) {
        return "服务器出现异常,paymentInfo_ok请稍后再试";
    }

    @Override
    public String paymentInfo_timeOut(Integer id) {
        return "服务器出现异常,paymentInfo_timeOut请稍后再试";
    }
}

yml增加配置

server:
  port: 80

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

feign:
  hystrix:
    enabled: true

PaymentHystrixService修改如下
在这里插入图片描述
启动测试:
在这里插入图片描述

6.Hystrix实现服务熔断

6.1:熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。 当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand

在这里插入图片描述

6.2:熔断测试案例

修改cloud-provider-hystrix-payment8001

①: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") Long id){
        if(id < 0){
            throw  new RuntimeException("************id,不能为负数");
        }
        String uuid = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+"\t" + "调用成功,流水号===" + uuid;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Long id){
        return "id不能为负数,请稍后重试!o(╥﹏╥)o id:"+id;
    }

②:控制器新增

@GetMapping("/payment/circuit/{id}")
    public String  payemntCircuitBreaker(@PathVariable("id") Long id){
        String result = paymentService.paymentCircuitBreaker(id);
        return result;
    }

启动测试:
在这里插入图片描述
进而实现:服务的降级-》熔断-》恢复调用链路

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

Hystrix 整个工作流程图如下:

在这里插入图片描述

7.hystix图形化监控

7.1 概述

Hystix提供了准实时的调用监控,Hystix会持续的记录所有通过Hystix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功失败等。

7.2 案例

①在父工程下,新建Module : cloud-consumer-hystrix-dashboard9001

②:引入相关依赖:

<dependencies>
        <!--hystrix-->
        <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-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>

③:application.yml

server:
  port: 9001

④:主启动类

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

启动访问:http://localhost:9001/hystrix

在这里插入图片描述
用到了上面的8001作为测试,完活
在这里插入图片描述
好家伙,上来就出错!
修改8001的主启动类

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix服务熔断降级
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
    /**
     *  此配置是为了服务监控而配置,与服务容错本身无关
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

再次请求访问
在这里插入图片描述

写作不易,加个关注呗

求关注、求点赞!
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值