8 Hystrix:Spring Cloud服务熔断与降级组件
8.1 分布式系统面临的问题
复杂分布式体系结构中的应用程序往往由多个服务组成,这些服务之间相互依赖,依赖关系错综复杂,每个依赖关系在某些时候将不可避免的失败!
若一个分布式系统存在多个服务,服务间依赖关系如下的结构,
通常情况下,一个用户请求往往需要多个服务配合才能完成。如图 1 所示,在所有服务都处于可用状态时,请求 a 需要调用 S1、S3、S5、S7 四个服务才能完成,请求 b 需要调用 S4、S5 、S72个服务才能完成,请求 s 需要调用服务S3、S5、S7 、S8 四个服务才能完成。
当服务 s5发生故障或网络延迟时,会出现以下情况:
即使其他所有服务都可用,由于服务s5的不可用,那么用户请求 a、b、c 都会处于阻塞状态,等待服务s5 的响应。在高并发的场景下,会导致整个服务器的线程资源在短时间内迅速消耗殆尽。
所有依赖于服务s5的其他服务,例如服务 S3、S4、S6、S7 以及s5 也都会处于线程阻塞状态,等待服务 s5 的响应,导致这些服务的不可用。
所有依赖服务S3、S7 的服务,例如服务 s1 和服务 s8 也会处于线程阻塞状态,以等待S3、S7的响应,导致 s1、s8 也不可用。
- 服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源并且故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,所谓的“雪崩效应”。为了防止此类事件的发生,微服务架构引入了“熔断器”的一系列服务容错和保护机制。
- 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能影响整个应用程序或系统。
8.2 Hystrix介绍
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
- Spring Cloud Hystrix 是一款优秀的服务容错与保护组件,也是 Spring Cloud 中最重要的组件之一。
- Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
- “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类以熔断保险丝),向调用方返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务降级:在某个服务奔溃后调用备选的服务,若不用Hystrix实现自己可以使用异步加载,异常来实现
在微服务系统中,Hystrix 能够帮助我们实现以下目标:
- 保护线程资源:防止单个服务的故障耗尽系统中的所有线程资源。
- 为通过第三方客户端库访问的依赖项(通常通过网络)提供延迟和故障保护和控制。
- 快速失败、恢复机制:当某个服务发生了故障,不让服务调用方一直等待,而是直接返回请求失败。
- 提供降级(FallBack)方案:在请求失败后,提供一个设计好的降级方案,通常是一个兜底方法,当请求失败后即调用该方法。
- 防止故障扩散:使用熔断机制,停止复杂分布式系统中的级联故障,防止故障扩散到其他服务。
- 监控功能:提供熔断器故障监控组件 Hystrix Dashboard,实现近实时监控、警报和操作控制,随时监控熔断器的状态。
- 服务限流
8.3 Hystrix 服务降级
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
服务降级的使用场景有以下 2 种:
- 服务端服务降级:在服务器压力剧增时,根据实际业务情况及流量,对一些不重要、不紧急的服务进行有策略地不处理或简单处理,从而释放服务器资源以保证核心服务正常运作。
- 客户端服务降级:当服务端某些服务不可用时,为了避免长时间等待造成服务卡顿或雪崩效应,而主动执行客户端备用的降级逻辑立刻返回一个友好的提示,以保障主体业务不受影响。
我们可以通过重写 HystrixCommand 的 getFallBack() 方法或 HystrixObservableCommand 的 resumeWithFallback() 方法,使服务支持服务降级。
这个降级方法可以是返回一个友好提示,也可以是执行一个和原方法同样的逻辑方法,由于导致故障的原因不同,也许第二次执行 可以通过。这两种方案视具体情况而定。
Hystrix 会在以下场景下进行服务降级处理:
- 程序运行出现异常时,自定义抛出异常
- 服务超时
- 熔断器处于打开状态
- 线程池资源耗尽
接着我们测试下 Hystrix 服务端服务降级和客户端服务降级
8.4 服务端服务降级
- 在基础工程 spring-cloud-microservice 下创建一个名为 microservice-cloud-provider-dept-hystrix-8004 的服务提供者,并在其 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>spring-cloud-microservice</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>microservice-cloud-provider-dept-hystrix-8004</artifactId>
<dependencies>
<!--引入公共子模块-->
<dependency>
<groupId>org.example</groupId>
<artifactId>microservice-cloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!--Spring Boot Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--devtools 开发工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--整合 mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--spring-boot test 测试只能在test包中测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--junit 测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- 修改后立即生效,热部署 这个热部署重启得更快 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
<!--日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--引入 Eureka Client 的依赖,将服务注册到 Eureka Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- Spring Boot 监控模块,完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--hystrix 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 在类路径(即 /resources 目录)下添加一个配置文件 application.yml,配置内容如下。
server:
port: 8004
spring:
application:
name: microServiceCloudProviderDeptHystrix #微服务名称,对外暴漏的微服务名称,十分重要
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springcloud_db2?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimeZone=UTC
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认是不注入这些的,需要自己绑定
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Priority
#则导入log4j 依赖就行
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#整合mybatis
mybatis:
#type-aliases-package: com.zk.springcloud.entity
#mapper-locations: classpath:mybatis/mapper/*.xml
#config-location: classpath:mybatis/mybatis-config.xml
configuration:
map-underscore-to-camel-case: true #默认开启驼峰命名法,可以不用设置该属性
#eureka配置,Spring cloud 自定义服务名称和 ip 地址
eureka:
client:
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ #这个地址是 7001注册中心在 application.yml 中暴露出来额注册地址 (单机版)
defaultZone: http://eurekaserver7001.com:7001/eureka/,http://eurekaserver7002.com:7002/eureka/,http://eurekaserver7003.com:7003/eureka/ #将服务注册到 Eureka Server 集群
instance:
instance-id: microservice-cloud-provider-dept-hystrix-8004 #自定义服务名称描述信息
prefer-ip-address: true #显示访问路径的 ip 地址
#zookeeper需要配置那些服务service被扫描,eureka则是将整个服务包注册进去了
# spring cloud 使用 Spring Boot actuator 监控完善信息
# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 heath 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: health,info #应包含的端点 ID 或 '' 全部,* 在yaml 文件属于关键字,所以需要加双引号
#include的值也可以改成*,但建议还是最小暴露原则,用啥开启啥
info:
env:
enabled: true #启用配置里的info开头的变量
info: #配置一些服务描述信息,监控信息页面显示,,可以判断服务是否正常
app.name: microservice-cloud-provider-dept-hystrix-8004
company.name: cloud.zk.com
auth: zk
email: 123@qq.com
build.aetifactId: @project.artifactId@
build.version: @project.version@
- 在 com.example.service 包下创建一个名为 DeptService 的接口,代码如下。
package com.example.service;
public interface DeptService {
// hystrix 熔断器示例 ok
String dept_200(Integer id);
//hystrix 熔断器超时案例
String dept_timeout_500(Integer id);
}
- 实现类 DeptServiceImpl,代码如下
package com.example.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author CNCLUKZK
* @create 2022/9/20-13:26
*/
@Service("deptService")
public class DeptServiceImpl implements DeptService{
@Override
public String dept_200(Integer id) {
return "服务端当前线程:"+Thread.currentThread().getName()+"请求处理成功200。ID+"+id;
}
//一旦该方法失败并抛出了异常信息后,会自动调用 @HystrixCommand 注解标注的 fallbackMethod 指定的方法
@HystrixCommand(fallbackMethod = "dept_timeout_handler",
规定 5 秒钟以内就不报错,正常运行,超过 5 秒就报错,调用指定的方法
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")})
@Override
public String dept_timeout_500(Integer id) {
try {
//Thread.sleep(6000);
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服务端当前线程:"+Thread.currentThread().getName()+"请求超时500。ID+"+id;
}
// 当服务出现故障后,调用该方法给出友好提示
public String dept_timeout_handler(Integer id){
return "服务端因当前请求超时,服务降级,返回提示信息!当前线程:"+Thread.currentThread().getName()+"请求超时500。ID+"+id;
}
}
dept_timeout_500() 方法上使用 @HystrixCommand 注解,该注解说明如下:
- 参数 fallbackMethod 属性用于指定降级方法。
- 参数commandProperties指定命令属性,具体可配置项可见HystrixCommandProperties类
- execution.isolation.thread.timeoutInMilliseconds 用于设置自身调用超时时间的峰值,峰值内可以正常运行,否则执行降级方法
在 com.example.controller包下创建一个名为 DeptController的控制类提供服务,代码如下
package com.example.controller;
import com.example.service.DeptService;
import com.zk.springcloud.entity.Dept;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author CNCLUKZK
* @create 2022/9/7-1:43
*/
@RestController //提供restful服务,@RestController需要spring-boot-starter-web
@Slf4j
@RequestMapping("/dept")
public class DeptController {
@Value("${server.port}")
private String serverPort;
@Autowired
Environment environment;
@Autowired
private DeptService deptService;
//获取一些配置的信息,得到具体的微假务
@GetMapping("/getInfo/Hystrix/200/{id}")
public String dept_200(@PathVariable("id") Integer id) {
String info = deptService.dept_200(id);
log.info("request result"+info+"port:"+serverPort);
return "request result"+info+"port:"+serverPort;
}
//超时测试,该服务的响应时间为 3 秒
@GetMapping("/testTimeOut/Hystrix/{id}")
public String testTimeOut(@PathVariable("id") Integer id){
String info = deptService.dept_timeout_500(id);
//当Application.yml中没有配置当前端口号,只能使用此方式获取端口
String serverPort = environment.getProperty("server.port");
log.info("request result"+info+"port:"+serverPort);
return "request result"+info+"port:"+serverPort;
}
}
- 在MicroserviceCloudProviderDeptHystrix8004Application主启动类上,使用@EnableHystrix或@EnableCircuitBreaker(3.0.1 release开始弃用) 注解开启熔断器功能,@EnableHystrix继承了@EnableCircuitBreaker。代码如下。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient //在服务启动后自动注册到eureka中
//@EnableCircuitBreaker //激活熔断器功能
@EnableHystrix //启用 Hystrix
public class MicroserviceCloudProviderDeptHystrix8004Application {
public static void main(String[] args) {
SpringApplication.run(MicroserviceCloudProviderDeptHystrix8004Application.class,args);
}
}
- 依次启动服务注册中心(Eureka Server)集群和 microservice-cloud-provider-dept-hystrix-8004,并使用浏览器访问“http://127.0.0.1:8004/dept/getInfo/Hystrix/200/1”或“http://eureka7001.com:8004/dept/getInfo/Hystrix/200/1,结果如下图。
- 访问http://127.0.0.1:8004/dept/testTimeOut/Hystrix/1,结果如下图。