快速入门Spring Cloud Hystrix(服务降级、服务熔断、服务监控)


前言

在微服务架构中,一个系统往往是由多个服务组成,这些服务之间相互依赖。
假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.


提示:以下是本篇文章正文内容,下面案例可供参考

一、服务雪崩

1.服务雪崩概述

在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。
雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。      

2. 造成服务雪崩的原因

 - 服务提供者不可用
     a)硬件故障:硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问
     b)程序Bug:
     c) 缓存击穿:缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
     d)用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
 - 重试加大流量
     a)用户重试:在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单
     b)代码逻辑重试: 服务调用端的会存在大量服务异常后的重试逻辑
 - 服务调用者不可用
     a)同步等待造成的资源耗尽:当服务调用者使用同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

3. 如何防止雪崩

使用Spring Cloud Hystrix进行服务熔断、降级。
然而Hystrix已经停更,进入了维护模式,Hystrix官方推荐的替代产品:Resilience4J 
但我们需要知道Hystrix是干嘛,怎么用的,它的思想

在这里插入图片描述

二、Spring Cloud Hystrix

1.什么是Spring Cloud Hystrix(豪猪哥)

Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,
它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。         
Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。

Hystrix 的使用文档:https://github.com/Netflix/Hystrix/wiki/How-To-Use

  • 服务熔断

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

  • 服务降级

服务降级是从系统整体考虑,当某个服务熔断之后,服务器不再被调⽤时,客户端可以为发送的请求准备⼀个本地的fallback回调,返回⼀个与方法返回值类型相同的缺省值,这样做,虽然服务水平下降,但整体仍然可用,比直接熔断要好。

  • 服务限流

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

2.搭建测试环境

(1)创建cloud-provider-hystrix-payment8003支付服务

改pom
在原有支付服务的依赖下,添加hystrix依赖

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

添加yml

server:
  port: 8003
#服务名称
spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

启动类

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

业务类

@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()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
    }

}

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;
    }
}
(2)创建cloud-consumer-feign-hystrix-order订单服务

改pom

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

添加yml

server:
  port: 80
eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: false
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

启动类

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

业务类

@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
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;
    }
}
(3)jmeter压力测试

压测40000并发,访问支付模块
在这里插入图片描述
在这里插入图片描述
此时80调用8003,发现订单模块访问变慢。
原因:8003同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
解决方案:使用服务降级

  • 超时导致服务器变慢(转圈) - 超时不再等待
  • 出错(宕机或程序运行出错) - 出错要有兜底

3.服务降级实例

为了测试比较清晰,因此故意制造两种异常

  • 制造超时异常
  • int age = 10/0,计算异常
(1)支付服务做服务降级

更改支付服务业务类
在这里插入图片描述
主启动类添加@EnableCircuitBreaker注解来激活Hystrix的功能

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker   //此处添加
public class PaymentHystrixMain8003 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(PaymentHystrixMain8003.class,args);
    }
}

修改代码造成计算异常
在这里插入图片描述

测试:两种异常都是跳转到了兜底的方法上
在这里插入图片描述

(2)@EnableHystrix 注解

@EnableHystrix注解它继承了@EnableCircuitBreaker,并对它进行了在封装。
这两个注解都是激活hystrix的功能,

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
    
    
}
(3)全局服务降级DefaultProperties

每个方法上配置一个服务降级方法。太麻烦,只需要对核心业务有专属
其他普通的可以进行全局降级

@Service
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class PaymentService {
    
    
   
    @HystrixCommand
    public String paymentInfo_TimeOut(Integer id)
    {
    
        int age=10/0;
        return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
    }
    public String payment_Global_FallbackMethod(){
    
    
        return "全局异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
    }

}

在这里插入图片描述

(4)通配服务降级FeignFallback

降级方法与业务方法写在了一块,耦合性太高

修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可
创建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出现异常";
    }
}

让PayService的实现类生效:

@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);
}

配置文件中开启hystrix

feign:
  hystrix:
    enabled: true

测试:支付服务报错,首先会运行支付服务中该方法的兜底方法
在这里插入图片描述
当我们关掉支付服务后,重新发起请求
在这里插入图片描述
可以看到,并没有报500错误,而是降级访问实现类的同名方法

这样,即使服务器挂了,用户要不要一直等待,或者报错

4.服务熔断实例

(1)服务熔断原理剖析

服务熔断:就类似于保险丝
熔断这个概念由martin fowler 提出的,在他的博客中对熔断的原理进行了概述
在这里插入图片描述
简单来说,断路器有三种状态熔断打开熔断关闭熔断半开

正常调用时,断路器是关闭的。
当出现高并发等情况,导致某个服务瘫痪,此时断路器打开,服务发生熔断,会进行服务的降级,快速返回错误的信息。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果能够正常运行,则恢复正常链路,此时断路器关闭

(2)服务熔断实例

修改cloud-hystrix-pay8003模块

  • 修改业务类
@HystrixCommand(fallbackMethod = "payment_Hander",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 paymentInfo_TimeOut(Integer id)
    {
    
       if(id<0){
    
    
         throw new RuntimeException("id不能为负数");
    }
        //try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
    }
    public  String payment_Hander(Integer id){
    
    

        return "线程池:  "+Thread.currentThread().getName()+"  8003系统繁忙或者运行报错,请稍后再试,id:  "+id;
    }
  • 测试
    正确 - http://localhost:8001/payment/circuit/1
    错误 - http://localhost:8001/payment/circuit/-1
    多次错误,再来次正确,但错误得显示
    在这里插入图片描述
    HystrixCommandProperties 这个类规定了HystrixCommand注解下的HystrixProperty属性里的值。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

5.Hystrix工作流程

官网的工作流程:
在这里插入图片描述
步骤说明:

  1. 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。

  2. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。

  3. 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。

  4. 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。

  5. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
    HystrixCommand.run():返回一个单一的结果,或者抛出异常。
    HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。

  6. Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。

  7. 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。

  8. 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。

6.服务监控

(1)Hystrix图形化Dashboard搭建

创建模块:cloud-consumer-hystrix-dashboard-9001
改pom:

<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>

添加yml

server:
  port: 9001

启动类 @EnableHystrixDashboard注解

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

}

测试:浏览器输入:http://localhost:9001/hystrix
在这里插入图片描述
出现这个则配置成功

(2)图形化Dashboard监控实战

对监控的服务主启动类添加以下内容

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix  //此处添加
public class PaymentHystrixMain8003 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(PaymentHystrixMain8003.class, args);
    }

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

    }
}

监控测试:
启动注册中心、9003、9001服务
填写监控地址 http://localhost:8003/hystrix.stream 到 http://localhost:9001/hystrix页面的输入框
在这里插入图片描述
监控说明:
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45637894/article/details/126421001
今日推荐