1

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

2.Hystrix能发挥的作用

3.服务的异常现象

4.Hystrix的服务降级

5.Hystrix的服务熔断

6.Hystrix的服务监控hystrixDashboard

1.分布式系统面临的严重问题
复杂的分布式系统中微服务的依赖关系往往非常复杂,很可能会出现调用失败的情况
image.png
假设如图所示,一个请求需要调用到A,H,I,P四个服务,如果请求数不多,并发量不大的情况下,这个分布式系统不会出现什么问题,但是一旦请求数增加到一定的阈值,某些服务如果出现了超时,发生什么?

服务的雪崩:
如果多个服务在调用的时候,在调用链上的一个微服务出现相应时间过长或者报错导致的长期不可用,那么调用这些服务的服务调用者会积压越来越多的请求占用越来越多的资源,从而引起崩溃,这就是所谓的“雪崩效应”。

对于单一一个服务来说,被调用方发生了异常阻塞,那个调用方的资源很可能在几秒钟内饱和。比失败更糟糕的是,被调用方发生异常后,还可能会使队列,线程池等资源越来越紧张,导致整个系统发生联级故障。这些现象都证明了,要对服务的异常和延迟进行隔离和管理,即便单个依赖的失败,也不能让其它服务出现异常,防止出现联级效应。

此时就要用我们的Hystrix
Hystrix是一个处理分布式服务的延迟和容错的开源库,在分布式系统中,许多依赖不可避免地会调用失败,Hystrix能够保证在一个依赖出问题的情况下不会导致整体服务失败,避免联级故障,提高分布式系统的可用性

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

很可惜Hystrix也已经停止更新了,不过我们可以为后续学习SpringCloud Alibaba
Sentinel打下基础。

image.png

2.Hystrix能发挥的作用

一般的来说Hystrix能发挥三个作用:
2.1)服务降级
降级就是在被调用服务出问题的时候,不让客户端等待并返回一个友好的提示。
当发生以下这种情况的时候,可以触发降级
2.1.1)程序运行异常,报错
2.1.2)超时
2.1.3)服务熔断发生服务降级
2.1.4)线程池/信号量打满发生服务降级

2.2)服务熔断
服务到达最大的访问量后,或者服务调用失败(降级)超过一定的次数,直接拒绝访问,然后调用服务降级的方法并返回友好提示。

2.3)服务限流
按照服务器能够消化的能力,进行排队消费请求,就像队列一样。

3.服务的异常现象

我们新建一个延时接口,然后对这个接口进行压力测试

    @GetMapping(value = "/payment/get/delay/{id}")
    public String getPaymentDelayById(@PathVariable("id") Long id)
    {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "调用成功,服务端口为"+serverPort;
    }

image.png

我们对这个延时接口进行压力测试,同时调用这个服务的其它接口,发现没有延时的接口也被影响了,也一直转圈圈。

image.png

为什么会一直在转圈圈?

因为tomcat的默认的工作线程数被打满了,没有多余的线程来分解和处理,此时再调用另外的接口,导致整个服务都出现问题。

正因为会出现上面这种,一个接口拖垮整个服务的现象,我们才会有降级/熔断/限流等技术诞生。

我们要解决的问题如下:
1)服务提供者超时了,调用者不能一直卡死等待,必须有服务降级
2)服务提供者down机了,调用者不能一直卡死等待,比如有服务降级
3)服务提供者正常,调用者出现异常,也要自己进行降级

4.Hystrix的服务降级

接下来,我们就要使用Hystrix进行服务降级了,我们可以在服务的调用者加上服务降级的注解。

在服务提供者中加入这个依赖,因为springCloud 在2021.0.1已经移除了除了eureka的所有依赖,所以我们这里对版本进行降级
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>service-provider-8501</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-provider-8501</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

主启动类:

@EnableHystrix
@GetMapping("/payment/get/delay/{id}")
    //回调方法
    @HystrixCommand(fallbackMethod = "delayTimeOurFallBack",commandProperties = {
            //超时时间
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
    })
    public String delayCreate(@PathVariable("id") Long id) {
        return providerFeignService.getPaymentDelayById(id);
    }

    public String delayTimeOurFallBack(@PathVariable("id") Long id)
    {
        return "服务端消费系统繁忙!请稍后再试o(╥﹏╥)o";
    }

此时再调用服务,超时的话就会降级:

image.png

此时我们配置完了服务降级,但是每个业务类就会对应一个兜底的方法会使代码膨胀,所以我们对代码进行改造

我们要在feign中开启hystrix,然后继承feign接口,继承feign接口的实现类就要实现所有feign接口的方法这些方法全是失败回调方法,就可以对失败回调进行统一管理了。

yml:

feign:
  hystrix:
    enabled: true

我们删除所有的HystrixCommand注解,然后新建一个类,实现feign接口

@Component
public class ProviderFeignServiceImpl implements ProviderFeignService{
    @Override
    public String getPaymentById(Long id) {
        return "服务端消费系统繁忙!请稍后再试o(╥﹏╥)o";
    }

    @Override
    public String getPaymentDelayById(Long id) {
        return "服务端消费系统繁忙!请稍后再试o(╥﹏╥)o";
    }
}
@Component
@FeignClient(value="SERVICE-PROVIDER" ,fallback = ProviderFeignServiceImpl.class)
public interface ProviderFeignService {

    @GetMapping(value = "/payment/get/{id}")
    String getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/get/delay/{id}")
    String getPaymentDelayById(@PathVariable("id") Long id);

}

再次调用:

image.png

这样我们就实现了代码的隔离,把所有降级返回的方法都合到一个地方去使用。

5.Hystrix的服务熔断

什么是服务熔断?

熔断是应对服务雪崩效应的一种微服务链路保护机制。当调用链上的某个微服务不可用或者响应时间太长的时候,直接断该节点服务的调用,快速返回错误的响应信息(服务降级是等待调用超时,服务熔断是直接不调用这个服务,熔断该服务,直接返回结果)。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在spring cloud框架里,熔断机制通过Hystrix实现hystrix会监控微服务间的调用情况,当失败的调用到一定的阈值,缺省是5秒内20次调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand。

    @GetMapping("/payment/get/delay/{id}")
    @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"),//有60%的请求失败,就熔断
    })
    public String delayCreate(@PathVariable("id") Long id) {
        return providerFeignService.getPaymentDelayById(id);
    }

熔断的流程:
我们先开看一下大神的结论:

image.png

总的来说说就是:
1)熔断打开请求不再调用该微服务,等到熔断打开的时长到达所设的时间就会进入半熔断状态
2)熔断关闭:不会对服务进行熔断,请求能直接调用服务
3)熔断半开部分请求能根据规则调用当前服务,如果请求成功且符合规则,则认为当前服务恢复正常,关闭熔断如果调用失败依然打开熔断

6.Hystrix的服务监控hystrixDashboard

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控图形界面(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,成功的条数,失败的条数等等。

我们新建一个项目:

image.png

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>consumer-hystrix-dashboard9001</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consumer-hystrix-dashboard9001</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <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.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

主启动:

@SpringBootApplication
@EnableEurekaClient
@EnableHystrixDashboard
public class DemoApplication {

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

}

yml:

server:
  port: 9001

eureka:
  instance:
    hostname: consumer-hystrix-dashboard9001 #eureka服务端的实例名称
    prefer-ip-address: true #访问路径可以显示IP地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}-consumer-hystrix-dashboard9001 #访问路径的名称格式
  client:
    register-with-eureka: true     #false表示不向注册中心注册自己。
    fetch-registry: true     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #集群指向其它eureka
      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
  application:
    name: consumer-hystrix-dashboard9001
logging:
  level:
  # feign日志以什么级别监控哪个接口
    com.example.demo.feign.*: debug
feign:
  hystrix:
    enabled: true

被监控的服务全都得加上这个依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

被依赖的服务的主启动:

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

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

我们启动监控面板服务:

http://localhost:9001/hystrix

image.png

输入监控的服务端口:http://localhost:8402/hystrix...

成功:
image.png
失败,导致熔断打开:
image.png

对图片的参数进行标注:
image.png

image.png


苏凌峰
73 声望38 粉丝

你的迷惑在于想得太多而书读的太少。