SpringCloud(四)Hystrix服务熔断、降级、流监控

1. Hystrix:服务熔断

分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败!

服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A掉调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一个后端依赖可能会导致所有服务器上的资源都在几秒钟饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

需要弃车保帅

什么是Hystrix

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的对调用失败,比如超时,异常等,Hystrix能够保证一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

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

能干什么

  • 服务降级
  • 服务熔断
  • 服务限流
  • 接近实时的监控
  • ……

什么是服务熔断

熔断机制是对应雪崩效应的一种微服务链路保护机制

当扇出链路的某个微服务不可用或者响应时间太长,会进行服务降级,==进而熔断该节点微服务的调用,快速返回错误的响应信息==。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是:HystrixCommand。

  • 创建一个module:springcloud-provider-dept-hystrix-8001
  • 添加pom.xml依赖
<dependencies>
    <!--Hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--添加监控信息-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--Eureka-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--需要拿到实体类,要配置api-module-->
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>springcloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
    <!--logback-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
    </dependency>
    <!--springboot启动器-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
    </dependency>
    <!--web启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--jetty,等价tomcat-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    <!--热部署工具-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>
  • DeptController
@RestController
public class DeptController {

    /**
     * 注入服务
     */
    @Autowired
    private DeptService deptService;

    /**
     * 通过部门id查询部门
     * @param id 部门id
     * @return 部门信息
     * @description id查询结果为null时,触发熔断,调用备选方法
     */
    @GetMapping("/dept/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrixGetDept")
    public Dept getDept(@PathVariable("id") Long id) {
        Dept dept = deptService.queryDeptById(id);
        if (dept == null) {
            throw new RuntimeException("查询信息为空");
        }
        return dept;
    }

    /**
     * 备选方法
     * 当服务发生熔断时调用该方法
     */
    public Dept hystrixGetDept(@PathVariable("id") Long id) {
        return new Dept()
                .setDeptNo(id)
                .setDeptName("not have DeptName,this is @Hystrix")
                .setDbSource("no this database in MySql");
    }
}
  • application.yml
server:
  port: 8001

# mybatis配置
mybatis:
  type-aliases-package: com.example.springcloud.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

# spring配置
spring:
  application:
    # 三个服务名称一致
    name: springcloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: root

# Eureka的配置,服务注册到哪里
eureka:
  client:
    service-url:
      # 服务发布地址
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
  instance:
    # 修改Eureka的默认描述信息
    instance-id: springcloud-provider-dept-hystrix-8001

总结:为了避免因为级联调用导致的服务雪崩,从而出现的熔断机制。

controller的方法上需要添加注解:@HystrixCommand(fallbackMethod = "hystrixGetDept") (hystrixGetDept:备选方法名)

当发现该方法出现异常,触发熔断,调用备选方法。

服务熔断:服务端~ 某个服务超时或异常,引起熔断~,保险丝~

2. Hystrix:服务降级

  • 服务降级的原因,当前有三个服务,A是主要服务,B是中等服务,C是次级服务,某个时刻由于访问A服务的请求特别多,为了保证A服务不发生故障,需要暂时关闭冷门服务C来给A服务提供更多的硬件资源,例如:双十一当天关闭退款功能。
  • 在module:springcloud-api中的service文件夹中新建DeptClientServiceFallbackFactory.java
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {

    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept queryDeptById(Long id) {
                return new Dept()
                        .setDeptNo(id)
                        .setDeptName("not have info,this server is closed")
                        .setDbSource("not have database");
            }

            @Override
            public boolean addDepartment(Dept dept) {
                return false;
            }

            @Override
            public List<Dept> queryAllDept() {
                return null;
            }
        };
    }
}
  • 在module:springcloud-consumer-dept-feignapplication.yml文件中添加
# 开启降级
feign:
  hystrix:
    enabled: true
{
    deptNo: 1,
    deptName: "not have info,this server is closed",
    dbSource: "not have database"
}
  • 此时说明服务降级

当主要服务需要更多的硬件资源时,次要服务需要被暂时关闭,同时为了给用户更好的体验,需要设置提示信息

服务降级:客户端~ 从整体网站请求负载考虑~ ,当某个服务熔断或者关闭之后,服务将不再被调用~

​ 此时在客户端,可以准备一个FallbackFactory,返回一个默认值(缺省值),整体的服务水平下降,但是能用,比直接挂掉好~

3. Hystrix:Dashboard流监控

  • 创建一个module:springcloud-consumer-hystrix-dashboard

  • 添加pom.xml依赖

<dependencies>
    <!--Dashboard,Hystrix监控页面-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--Hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--Ribbon-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--Eureka-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--实体类-->
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>springcloud-api</artifactId>
        <version>1.0-SNAPSHOT</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-devtools</artifactId>
    </dependency>
</dependencies>
  • 配置application.yml
# 监控页面端口
server:
  port: 9001
  • 修改module:springcloud-provider-dept-hystrix-8001的主启动类
@SpringBootApplication // SpringBoot启动器
@EnableEurekaClient    // Eureka客户端
@EnableDiscoveryClient // 让被服务发现
@EnableCircuitBreaker  // 开启熔断
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class,args);
    }

    /**
     * 增加一个Servlet
     * hystrix流监控
     * @return 添加对象
     */
    @Bean
    public ServletRegistrationBean register() {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        return registrationBean;
    }
}
  • 依次启动9001、7001、hystrix-8001
  1. 访问http://localhost:7001
    • 确保8001服务已注册
  2. 访问http://localhost:8001/dept/get/1
    • 确保有数据
  3. 访问http://localhost:9001/hystrix
    1. 第一个编辑框输入:http://localhost:8001/actuator/hystrix.stream
    2. 延时:2000
    3. title:demo
    4. 点击Monitor Stream进入
  4. 新开一个页面多次访问:http://localhost:8001/dept/get/1
    1. 图中有个绿色的圈,访问次数越多,圈越大
    2. 以下为对应的参数解释
	成功数 | 超时数 	   | 最近10S错误百分比

	熔断数 | 线程池拒绝数 | Host: 服务请求频率
									  								
错误请求数 | 失败/异常数  |  Cluster:Circuit Closed 断路状态