SpringCloud实战---第十四篇-Ⅰ:Hystrix概念及快速上手

举报
老司机张师傅 发表于 2022/07/26 23:23:58 2022/07/26
【摘要】 系列文章目录SpringCloud快速入门到精通各组件原理专栏传送门@TOC 前言OpenFeign也太好用了吧!!!说起来容易做起来难,一步一步都干完!!!学习一定要自己动手搞一搞,不能只眼会。学习笔记是跟着尚硅谷的视频学的:传送门 一、关于Hystrix–豪猪哥关于服务降级、服务熔断、服务限流、服务隔离等等一系列优秀的设计理念,出道即巅峰,虽然现在已经停止维护,现在的相关框架都是对豪猪...

系列文章目录

SpringCloud快速入门到精通各组件原理
专栏传送门


@TOC


前言

OpenFeign也太好用了吧!!!
在这里插入图片描述

说起来容易做起来难,一步一步都干完!!!
学习一定要自己动手搞一搞,不能只眼会。
学习笔记是跟着尚硅谷的视频学的:传送门


一、关于Hystrix–豪猪哥

  • 关于服务降级、服务熔断、服务限流、服务隔离等等一系列优秀的设计理念,出道即巅峰,虽然现在已经停止维护,现在的相关框架都是对豪猪哥的抄作业,所以仍然具有很高的学习价值。
  • 微服务解决了模块解耦,但是会出现链路变长(A调用B,B调用C···),这样当B或C有一个出现问题时,A都会失败,然后A、B、C的频繁失败会占用越来越多的资源,最终导致雪崩,系统崩溃。为了解决这一问题,服务降级和服务熔断产生。
  • Hystrix主要用于保护其他服务,当链路上某一服务出现问题时,即使进行阻断或延迟或放行,不要让他影响其他服务。
  • 官方说明:在github上面搜索,查看开源社区。
  • 服务降级fallBack:当服务出现不可用,不要直接抛出错误给调用方,而是返回一个符合调用方预期格式的信息。
  • 服务熔断break:相当于是保险丝,当出现服务处理崩溃的边缘时,直接拒绝访问,然后调用服务降级方法返回符合预期格式的信息。
  • 服务限流flowlimit:当流量达到某一值时,为了防止服务崩溃,进行服务器限流,进来的服务排队进行或直接拒绝提示服务器忙。
  • 豪猪哥通过一系列操作主要目的是出现故障时保护服务调用方的线程不被长时间占用。

二、先构建好基础工程(一篇一篇看过来的不用重新构建)

构建基础父工程:构建基础父工程
Rest风格微服务:Rest风格微服务
传统分布式方法:传统分布式方法
改造工程,抽取公共模块:改造工程,抽取公共模块
使用Eureka:使用Eureka
Eureka集群: Eureka集群

想偷懒的请下载;gitee上我上传的代码懒人传送门

基础工程构建完成的目录结构:
在这里插入图片描述
启动所有模块,访问

localhost:7001

显示如下,代表基础工程没问题
在这里插入图片描述

话不多说,立马开干

三、工程改造

1. 为了学习方便,我们先把7001改成单节点,将7001作为注册中心

修改7001的配置文件

defaultZone: http://eureka7001.com:7001/eureka/

在这里插入图片描述

2. 构建带有Hystrix熔断的8001

在这里插入图片描述
模块名

cloud-provider-hystrix-payment8001

包名

com.atguigu.springcloud

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-hystrix-payment8001</artifactId>


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

</project>

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,http://eureka7002.com:7002/eureka
      defaultZone: http://eureka7001.com:7001/eureka

启动类PaymentHystrixMain8001

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 11:42
 * @Version: 1.0
 * @Description:
 */
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {

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

}

3. 编写业务类

我们编写一个一定正确的接口,再编写一个有可能出错的接口,用于学习测试。
PaymentService:

package com.atguigu.springcloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 11:46
 * @Version: 1.0
 * @Description:
 */
@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){
        int timeNumber =3;
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O  耗时"+timeNumber+"秒钟";
    }
}

PaymentController,调用service,模拟一个正常的,一个出现超时异常的:

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 11:57
 * @Version: 1.0
 * @Description:
 */
@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;
    }
}

4. 启动测试

启动7001和8001,访问接口,测试编写的model是否正常。

http://localhost:8001/payment/hystrix/ok/1
http://localhost:8001/payment/hystrix/timeout/1

在这里插入图片描述
量接口都正常,证明我们的model是没有问题的。

四、演示高并发场景下对服务的影响

我们编写好的两个接口在非高并发情况下访问正常没有问题,但是设想一下,高并发情况下,那个sleep3秒的接口肯定会报资源不足异常(线程资源得不到释放),因为servlet是多线程访问的,每个线程占三秒,高并发场景下很快程序就会崩溃。
接下来我们测试一下。

1. 安装压力测试工具Jmeter

Jmeter安装教程

2. 添加测试线程

添加一个线程组
在这里插入图片描述
按照如下配置,表示1秒内向接口发送200*100个请求
在这里插入图片描述
crtl+s,保存一下

3. 添加测试HTTP请求

在这里插入图片描述
填写信息,将两万个请求打到/payment/hystrix/timeout/{id}接口上,保存
在这里插入图片描述

4. 启动

在这里插入图片描述

5. 测试接口

看后台,发现发送的两万个请求还在处理中(因为一个要3秒)

在这里插入图片描述
我们再访问我们刚才很快能响应的那个接口

http://localhost:8001/payment/hystrix/ok/1

我们会发现,有时这个接口也会转圈,说明timeout那个接口的资源占用产生了影响
当然,虽然现在转圈还是很快,但是再真正的高并发场景下可不止两万这么简单哦!
在这里插入图片描述

五、演示雪崩

看热闹不嫌事大,我们新建一个80端口的服务调用方,来调用hystrix-8001模块的接口,然后测试下8001高并发场景下,对80产生的雪崩效应,然后我们再使用hystrix进行服务降级处理,来解决雪崩问题。

1. 新建微服务调用方hystrix-80

在这里插入图片描述
模块名:

cloud-consumer-feign-hystrix-order80

pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

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


</project>

application.yml

server:
  port: 80

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

基础包:

com.atguigu.springcloud

启动类OrderHystrixMain80

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 16:11
 * @Version: 1.0
 * @Description:
 */
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {

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

}

Feign接口PaymentHystrixService

package com.atguigu.springcloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 16:13
 * @Version: 1.0
 * @Description:
 */
@FeignClient(value = "cloud-provider-hystrix-payment")
@Component
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);

}

测试接口OrderHystrixController

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/27 16:15
 * @Version: 1.0
 * @Description:
 */
@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }

}

2. 启动服务调用方hystrix-80,测试模块是否正常

启动7001,hystrix-8001,hystrix-80模块,进行自测访问
首先是8001接口自测
ok的接口,很快就能出结果

http://localhost:8001/payment/hystrix/ok/1

timeout的接口,大约三秒钟左右出结果

http://localhost:8001/payment/hystrix/timeout/1

然后是80服务调用方测试
ok的接口,很快能访问

http://localhost/consumer/payment/hystrix/ok/1

timeout的接口,出现超时异常(Feign调用默认超时时间1秒)

http://localhost/consumer/payment/hystrix/timeout/1

在这里插入图片描述
注意, 这时只有服务调用方的timeout接口调用时会出问题,ok接口是没有问题的,接下来我们把两万个请求打到8001的timeout接口上,再测试一下

3. 测试服务雪崩

启动Jmeter
在这里插入图片描述
在这里插入图片描述
访问刚才80的ok接口

http://localhost/consumer/payment/hystrix/ok/1

会发现要么很慢,要么直接超时
在这里插入图片描述
注意,我们压力测试时打的时timeout接口,然后timeout接口处理不过来请求了,服务提供方8001资源不足,影响了我们调用的ok接口,ok接口也不能用了,服务出现了整体的不可用。

六、Hystrix闪亮登场

1. 8001服务方的服务降级

当服务方8001自己请求处理不过来,出现超时时,应该有适当的容错降级,不要把错误暴漏给调用方,而是返回一个调用方可以处理的消息。
改造8001工程的Service,使用@HystrixCommand注解,声明出出现异常时调用哪个方法
在这里插入图片描述

// 方法出异常时会找fallbackMethod配置的方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") // 表示这个线程的超时时间时3秒钟,下面我们线程睡了5秒,他一定会超时
})
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"+"O(∩_∩)O  耗时"+timeNumber+"秒钟";
}
public String paymentInfo_TimeOutHandler(Integer id){
    int timeNumber =3;
    try {
        TimeUnit.SECONDS.sleep(timeNumber);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "线程池"+Thread.currentThread().getName()+" paymentInfo_TimeOutHandler,id:"+id+"\t"+"/(ㄒoㄒ)/~~处理不过来了";
}

给主启动类添加激活信息

@EnableCircuitBreaker // 开启回注

在这里插入图片描述

2. 测试8001超时降级

启动7001和8001服务,访问

http://localhost:8001/payment/hystrix/timeout/1

等了三秒左右,页面上就会返回我们降级方法的返回值
在这里插入图片描述
并且我们发现了打印的线程名是一个Hystrix的线程,这说明降级不会影响我们的请求,而是一个新的线程,做到了线程隔离。

3. 80服务调用方的降级

80服务调用方再调用接口不通时,也需要有自我保护而不是应该报错,通常我们在业务逻辑处理时都是把服务降级做在调用方,以实现一种自我保护。
修改80的application.yml

feign:
  hystrix:
    enabled: true

在这里插入图片描述

主启动类上声明激活Hystrix

@EnableHystrix

在这里插入图片描述
把8001的 timeout改为正常的,不会在8001上降级
在这里插入图片描述
修改80的Controller,添加上降级的回调方法

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// 方法出异常时会找fallbackMethod配置的方法
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutFallbackMethod",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500") // 表示这个线程的超时时间时3秒钟,下面我们线程睡了5秒,他一定会超时
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
    return paymentHystrixService.paymentInfo_TimeOut(id);
}

public String paymentInfoTimeOutFallbackMethod(Integer id){
    return "80端:8001支付服务繁忙,请10秒后重试,如未解决请联系开发人员排查自身异常。。。";
}

在这里插入图片描述

4. 启动测试

启动7001、hystrix-8001、hystrix-80
访问

http://localhost/consumer/payment/hystrix/timeout/1

在这里插入图片描述

服务已经进行降级,这样就实现了及时的阻断服务异常,不让异常传递


总结

  • hystrix通过服务降级来实现的解决异常传递问题。
  • 服务降级可以这么理解,类似于淘宝,淘宝的支付宝功能出现了问题,支付功能不能用了,如果我们没有错误降级,在淘宝上调用支付时每次都会报出异常,然后大量的用户使用淘宝调用支付,就会导致资源占用,淘宝直接崩溃,使用了降级后,我们再调用时就直接返回一个友好的提示(系统繁忙,请稍后重试),至少能够保障系统的正常运行。
  • hystrix的服务降级可以使用再服务调用方和服务提供方,通常服务调用方需要做降级处理。
  • hystrix的降级是有一个回调方法,当出现异常时调用配置的回调方法返回预先设置好的符合接口格式的信息,防止异常传递。
  • @EnableCircuitBreaker加在主启动类上,用于开启回注
  • 调用方启用hystrix需要在配置文件开启,在主启动类上添加@EnableHystrix启用
  • @HystrixCommand注解加在方法上,配置的是降级时调用哪个方法及降级触发的条件
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。