springcloud之Hystrix

springcloud学习笔记,第六章,Hystrix。hystrix已经停止了更新,目前处于维护阶段了。

hystrix:豪猪

简介:

扇出:就是微服务之间的调用就像一把扇子一样,关联性太强了。

先来了解一下什么是服务雪崩:

Hystrix是什么?

Hystrix的几个重要概念:

  1. 服务降级fallback

    • 比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案
  2. 服务熔断break

    • 当某个服务出现问题,卡死了,不能让用户一直等待,需要关闭所有对此服务的访问,然后调用服务降级。
  3. 服务限流flowlimit

    • 限流,比如秒杀场景,不能访问用户瞬间都访问服务器,限制一次只可以有多少请求

Hystrix实现服务降级实例:

项目结构:

说明:生产者与消费者都注册进eureka,消费者的调用使用OpenFeign。所以这些maven依赖不要忘记导入。

创建不带降级模式的生成者模块:

  1. 创建模块cloud-provider-hystrix-payment8001

  2. pom文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <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>
    <!--web-->
    <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>
    <!--hutool插件-->
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.0.7</version>
    </dependency>
    </dependencies>
  3. yml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8001

    eureka:
    client:
    register-with-eureka: true #false表示不向注册中心注册自己
    fetch-registry: true #false表示自己端就是注册中心
    service-url:
    defaultZone: http://localhost:7001/eureka/

    spring:
    application:
    name: cloud-provider-hystrix-payment
  4. 主启动类

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaClient
    public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
    }
  5. service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    public class PaymentService {
    public String paymentInfo_ok(Integer id){
    return "线程池:"+Thread.currentThread().getName()+",paymentInfo_ok,id:"+id+"\t"+"aaron";
    }


    public String paymentInfo_TimeOut(Integer id){
    int timeNumber = 5;
    try{
    TimeUnit.SECONDS.sleep(timeNumber);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    return "线程池:"+Thread.currentThread().getName()+",paymentInfo_Timeout,id:"+id+"\t"+"aaron";
    }

    }
  6. controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @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;
    }

    }
  7. 启动eureka和payment:

  8. 高并发测试,先不加入hystrix支持,上面只是引入了一个pom包。

    • 使用jmeter压力测试工具,模拟20000个线程访问http://localhost:8001/payment/hystrix/timeout/31,最后我们发现http://localhost:8001/payment/hystrix/ok/31路径也被拖慢了(之前都是很快的响应的)。这是因为tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

创建不带降级模式的消费者模块:

  • 使用feign实现服务调用paymentHytrix8001。
  1. 创建模块cloud-consumer-feign-hystrix-order80

  2. pom文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <!--openFeign-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--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>
    <!--web-->
    <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>
  3. yml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    server:
    port: 80

    eureka:
    client:
    register-with-eureka: false #表示不向注册中心注册自己,openFeign要求的
    service-url:
    defaultZone: http://localhost:7001/eureka/
  4. 主启动类

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableFeignClients
    public class OrderHystrixMain80 {
    public static void main(String[] args) {
    SpringApplication.run(OrderHystrixMain80.class, args);
    }
    }
  5. service,需要远程调用接口,这是openFeign里面规定的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @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);
    }
  6. controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RestController
    @Slf4j
    public class OrderHystrixController {
    @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;
    }
    }
  7. 测试

    但是http://localhost/consumer/payment/hystrix/timeout/31链接访问不成功!这是因为我们在消费者一侧写的timeOut线程休眠时间是5秒,而之前我们学过OpenFeign的默认时间是1秒:

  8. 以上都先不加入hystrix支持,这是可以看到服务错误服务处理不了高并发却不降级,这都是我们需要解决的问题。

这个时候使用jmeter进行压力测试,和服务端payment压力测试时出现的情况差不多。

生产和消费模块配置服务降级:

故障导致原因,所以我们需要有个东西来解决这些:

为了解决这些需求:

  • 降级配置Hystrix:@HystrixCommand

修改pay8001模块,进行服务降级

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

  • 业务类service:

    需要在超时方法上加一个注解,并写一个降级后的处理方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
    commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value="3000")
    })
    public String paymentInfo_TimeOut(Integer id){
    int timeNumber = 5;
    try{
    TimeUnit.SECONDS.sleep(timeNumber);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    return "线程池:"+Thread.currentThread().getName()+",paymentInfo_Timeout,id:"+id+"\t"+"aaron";
    }

    // 处理超时访问,服务降级的方法
    public String paymentInfo_TimeOutHandler(Integer id){
    return "调用支付接口服务异常,当前的线程池名字是:"+Thread.currentThread().getName();
    }

  • 主启动类激活,添加新注解@EnableCircuitBreaker

  • 启动测试:

    访问:http://localhost:8001/payment/hystrix/timeout/1,由于服务降级设置的是3秒,请求了5秒,所以出现了服务降级。而且用的是单独的一个线程池。

    超时服务和运行报错,都可以进行服务降级。

修改order80模块,进行服务降级

  • 参照pay8001进行降级。

  • 题外话,切记

    我们对于配置通过热部署方式对Java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

  • YML

    1
    2
    3
    feign:
    hystrix:
    enabled: true # 开启hystrix服务降级
  • 主启动类

    @EnableHystrix

  • 业务类controller,同理,加上注解和降级后处理方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutFallbackHandler",commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value="1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
    String result = paymentHystrixService.paymentInfo_TimeOut(id);
    return result;
    }

    public String paymentInfo_TimeOutFallbackHandler(@PathVariable("id") Integer id){
    return "调用支付接口服务异常,当前的线程池名字是:"+Thread.currentThread().getName();
    }
  1. 测试,consumer中的timeOut超时时间设置的是1.5秒,而provider中的处理时间是5秒,它的超时时间设置的是3秒。

重构以上代码:

上面出现的问题:

  1. 降级方法与业务方法写在了一块,耦合度高
  2. 每个业务方法都写了一个降级方法,重复代码多

1、解决重复代码的问题:

  • 配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,再单独创建方法
  1. 创建一个全局方法

    1
    2
    3
    public String payment_Global_FallbackMethod(){
    return "Global异常处理信息,请稍后再试~";
    }

    这里我们是在order80模块上做演示:

  2. 使用注解指定其为全局降级方法(默认降级方法)

    1
    @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
  3. 业务方法使用默认降级方法

  4. 测试:

2、解决代码耦合度的问题:

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

未来我们要面对的异常:

  • 运行异常,超时,宕机。

修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可。

  1. 修改cloud-consumer-feign-hystrix-order80模块。

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

  3. PaymentFallbackService类实现PaymentHystrixService接口

​ 处理思路就是正常那么久走正常的接口,出现错误需要降级久走,该fallback实现类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "------PaymentFallbackService fall back back-paymentInfo_OK.........";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "------PaymentFallbackService fall back back-paymentInfo_TimeOut.........";
}
}
  1. 修改配置文件yml,记得开启hystrix:

  2. PaymentHystrixService接口,添加降级处理的类的注解:

  3. 启动测试:

Hystrix实现服务熔断:

理论:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。服务降级——》进而熔断——》恢复调用链路。

熔断机制:

修改前面的pay模块实现服务熔断:

IdUtil是Hutool包下的类,这个Hutool就是整合了所有的常用方法,比如UUID,反射,IO流等工具方法什么的都整合了。记得导入依赖。

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.7</version>
</dependency>
  1. 修改cloud-provider-hystrix-payment8001

  2. PaymentService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @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 serialNumber = IdUtil.simpleUUID();
    return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id不能为负数,请稍后再试,"+"\t"+"id:"+id;
    }

    这里的这么多属性表示的意思是:在10秒的窗口期中,处理10次请求,如果有6次失败就服务熔断,也就是跳闸。

  3. PaymentController

    1
    2
    3
    4
    5
    6
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("*******result:"+result);
    return result;
    }
  4. 测试:

    • 配置熔断之后,先一直刷新-1,使错误率达到100%,开启断路器。之后再试1,不能马上反应过来,等达到60%之后,才会恢复正常。这就是熔断机制:

熔断的类型:

断路器在什么情况下起作用:

断路器开启或者关闭的条件:

断路器打开之后:

Hystrix所有可配置的属性:

Hystrix工作流程:

Hystrix服务监控,图形化DashBoard:

搭建监控:

  1. 新建cloud-consumer-hystrix-dashboard9001

  2. POM

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
  3. YML

    1
    2
    server:
    port: 9001
  4. 主启动类HystrixDashboardMain9001+新注解@EnableHystrixDashboard

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
    SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
    }

  5. 所有的Provider微服务类(8001/8002/8003…)都需要监控依赖配置,这个依赖必须要。

  6. 启动cloud-consumer-hystrix-dashboard9001该微服务后将监控微服务8001:

  7. 此时仅仅是可以访问HystrixDashboard,并不代表已经监控了8001,8002。。。如果要监控,还需要配置:(8001为例)

如何使用监控浏览器:

  1. 修改cloud-provider-hystrix-payment8001

    • 注意新版本需要在主启动类中去增加监控路径,否则404报错。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 此服务是为了服务监控配置的,与服务容错本身无关。
    * 这个是springcloud新版本里确定加的。
    * @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;
    }

  2. 监控测试

    1. 启动1个eureka或者3个eureka集群即可

    2. 观察监控窗口

      • 新开窗口访问:localhost:8001/payment/circuit/1,localhost:8001/payment/circuit/-1

    3. 怎么来看这一张图:

      七色、一圈、一线。这个我也没懂。

ps:写于2020.9.27号,写了一天,出去吃饭了。值得纪念!

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信