3.hystrix

阅读 154

2022-01-06

第三章 Hystrix

第一节 引言

复杂分布式体系结构中的应用程序有许多依赖项,每个依赖项在某些时候都不可避免地会失败。如果主机应用程序没有与这些外部故障隔离,那么它有可能被他们拖垮。

当一切正常时,请求交互如下:

当其中有一个系统有延迟时,它可能阻塞整个用户请求:

在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(意味着后续再有请求将无法立即提供服务)

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

如何避免服务故障引发的雪崩效应呢?可以使用Spring Cloud Hystrix 来解决。

第二节 Hystrix

1. Hystrix 介绍

Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

2. Hystrix 设计原则

  • 防止任何单个依赖项耗尽所有容器(如Tomcat)用户线程。
  • 甩掉包袱,快速失败而不是排队。
  • 在任何可行的地方提供回退,以保护用户不受失败的影响。
  • 使用隔离技术(如隔离板、泳道和断路器模式)来限制任何一个依赖项的影响。
  • 通过近实时的度量、监视和警报来优化发现时间。
  • 通过配置的低延迟传播来优化恢复时间。
  • 支持对Hystrix的大多数方面的动态属性更改,允许使用低延迟反馈循环进行实时操作修改。
  • 避免在整个依赖客户端执行中出现故障,而不仅仅是在网络流量中。

3. Hystrix 主要功能

  • 隔离(线程池隔离和信号量隔离)

    限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

  • 优雅的降级机制

    超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

  • 融断

    当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。

  • 缓存

    提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)

4. Hystrix 项目搭建

4.1 创建子工程 goods-service03

<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>spring-cloud-demo</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0</version>
    </parent>
    <artifactId>goods-service03</artifactId>
    <version>1.0</version>

    <name>goods-service03</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--eureka client的jar包 ,这个包就包含了Ribbon相关的依赖在内-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <!--spring web的jar包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--redis-->
        <!--mybatis、mybatis-plus、jpa-->
        <!--spring security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--spring boot 四大神器之一 服务监控信息的jar包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署相关jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
</project>

4.2 编写启动类

package com.qf.spring.cloud.goodsservice;


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

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //启用服务熔断和降级
public class GoodsService03 {

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

4.4 编写控制器类

package com.qf.spring.cloud.goodsservice.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.qf.spring.cloud.goodsservice.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @GetMapping
    public String buyGoods(){
        //买商品的时候需要知道是谁买的?
        //要获取用户信息
        //用户信息在user-service这个服务上
        //如何从user-service这个服务上拿到用户信息
        //当前服务goods-service只是知道有user-service这样的一个服务
        //但是却不知道user-service服务在哪个位置(ip、端口)
        //可以使用ribbon来实现调用,而ribbon的使用通常都是在业务层
        return goodsService.buyGoods();
    }

    @GetMapping("/cancelOrder")
    @HystrixCommand(fallbackMethod = "cancelOrderFallback")
    public String cancelOrder(@RequestParam(name = "name") String name){
        if("".equals(name)) throw new RuntimeException("name为空");
        return goodsService.cancelOrder();
    }

    //该方法是一个降级方法
    //该方法的返回值类型必须与cancelOrder一致
    public String cancelOrderFallback(@RequestParam(name = "name") String name){
        return "name属性值为空,触发了降级";
    }
}

4.5 yml 配置

server:
  port: 1202 # 这里配置端口 1200~1299 这个段内就是配置Goods Service的端口
spring:
  application:
    name: goods-service #这个就是服务的名称
eureka:
  instance: #Eureka 实例
    hostname: localhost #Eureka实例所在的计算机IP地址
    instance-id: goods-service1202 #自定义微服务名称,在Eureka状态栏显示的名称
    prefer-ip-address: true #访问路径可以显示IP地址
  client: # 客户端配置
    service-url:
      #注册的Eureka Server地址,多个地址之间使用逗号分割开
      defaultZone: http://com.qf.eureka01:1001/eureka/,http://com.qf.eureka02:1002/eureka/,http://com.qf.eureka03:1003/eureka/

启动服务,进行测试

思考: 如果在调用服务的时候出现了异常,怎么办?

Hystrix 支持在FeignClient的调用的时候统一降级处理方式

4. 6 Feign 客户端降级类编写

package com.qf.spring.cloud.goodsservice.service.fallback;

import com.qf.spring.cloud.goodsservice.service.GoodsService;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class GoodsServiceFallback implements FallbackFactory<GoodsService> {

    @Override
    public GoodsService create(Throwable throwable) {
        return new GoodsService() {
            @Override
            public String buyGoods() {
                return "调用buyGoods方法失败";
            }

            @Override
            public String cancelOrder() {
                return "调用cancelOrder方法失败";
            }

            @Override
            public String testAdd() {
                return "调用testAdd方法失败";
            }
        };
    }
}


package com.qf.spring.cloud.goodsservice.service;


import com.qf.spring.cloud.TestRule;
import com.qf.spring.cloud.goodsservice.service.fallback.GoodsServiceFallback;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

//@FeignClient表示需要构建一个Feign客户端,该客户端需要与服务user-service交互
//交互时,使用的负载均衡规则是TestRule
//name指定的是微服务名称
//configuration指定的是负载均衡的规则
//path指定的是URL拼接路径
@FeignClient(name = "user-service", configuration = TestRule.class, path = "/user", fallbackFactory = GoodsServiceFallback.class)
public interface GoodsService {

    @GetMapping //这个请求就会去匹配 user-service服务中的 /user 请求处理
    String buyGoods();

    @GetMapping("/cancel") //这个请求就会去匹配 user-service服务中的 /user/cancel 请求处理
    String cancelOrder();

    @PostMapping
    String testAdd();
}

4.7 yml配置

server:
  port: 1202 # 这里配置端口 1200~1299 这个段内就是配置Goods Service的端口
spring:
  application:
    name: goods-service #这个就是服务的名称

feign:
  hystrix:
    enabled: true #启用feign客户端熔断


eureka:
  instance: #Eureka 实例
    hostname: localhost #Eureka实例所在的计算机IP地址
    instance-id: goods-service1202 #自定义微服务名称,在Eureka状态栏显示的名称
    prefer-ip-address: true #访问路径可以显示IP地址
  client: # 客户端配置
    service-url:
      #注册的Eureka Server地址,多个地址之间使用逗号分割开
      defaultZone: http://com.qf.eureka01:1001/eureka/,http://com.qf.eureka02:1002/eureka/,http://com.qf.eureka03:1003/eureka/

注释掉控制器中的 @HystrixCommand(fallbackMethod = "cancelOrderFallback")

启动服务器,测试

5. Hystrix Dashboard 项目搭建

5.1 创建子工程 service-hystrix-dashboard

<?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>spring-cloud-demo</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service-hystrix-dashboard</artifactId>

    <name>service-hystrix-dashboard</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--eureka client的jar包 ,这个包就包含了Ribbon相关的依赖在内-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        </dependency>
        <!--spring web的jar包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--redis-->
        <!--mybatis、mybatis-plus、jpa-->
        <!--spring security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--spring boot 四大神器之一 服务监控信息的jar包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署相关jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
</project>

5.2 编写启动类

package com.qf.spring.cloud.hystrix.dashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard //启用Hystrix仪表盘,也就是查看监控信息的页面
public class ServiceDashboard {

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

5.3 yml配置

server:
  port: 1300

启动服务,测试,访问地址 http://localhost:1300/hystrix

5.4 监控任一服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9SGLe59-1641447221913)(imgs\hystrix4.png)]

5.5 监控仪表盘

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjijF5sa-1641447221914)(imgs\hystrix5.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIU6cCch-1641447221914)(imgs\hystrix6.png)]

实心圆:本身颜色表示该微服务的健康程度,其健康程度

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


#### 5.3 yml配置

```yaml
server:
  port: 1300

启动服务,测试,访问地址 http://localhost:1300/hystrix

5.4 监控任一服务

[外链图片转存中…(img-u9SGLe59-1641447221913)]

5.5 监控仪表盘

[外链图片转存中…(img-CjijF5sa-1641447221914)]

[外链图片转存中…(img-OIU6cCch-1641447221914)]

实心圆:本身颜色表示该微服务的健康程度,其健康程度

从绿色<黄色<橙色<红色依次递减。本身大小表示该微服务的请求流量。请求流量越大,实心圆也越大。

精彩评论(0)

0 0 举报