1.背景
今天我们学习SpringCloud的Hystrix熔断器
我们今天继续使用之前eureka-server作为服务注册中心
使用Springboot和springcloud的版本如下
- springboot版本:2.3.5-release
- springcloud版本:Hoxton.SR9
2.Hystrix 是什么
在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
3.Hystrix为了什么
Hystrix被设计的目标是:
- 对通过第三方客户端库访问的依赖项(通常是通过网络)的延迟和故障进行保护和控制。
- 在复杂的分布式系统中阻止级联故障。
- 快速失败,快速恢复。
- 回退,尽可能优雅地降级。
- 启用近实时监控、警报和操作控制。
4..Hystrix有哪些功能
通过hystrix可以解决雪崩效应问题,它提供了资源隔离、降级机制、融断、缓存等功能。
- 资源隔离:包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
- 降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
- 融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
- 缓存:返回结果缓存,后续请求可以直接走缓存。
- 请求合并:可以实现将一段时间内的请求(一般是对同一个接口的请求)合并,然后只对服务提供者发送一次请求。
服务熔断和降级
-
服务熔断:一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施。简单的说服务熔断是条件,服务熔断是配置于服务端的防范机制
-
服务降级:简单的数据服务降级是服务熔断的解决问题的手段之一
资源隔离:
- 线程隔离
Hystrix在用户请求和服务之间加入了线程池。 Hystrix为每个依赖调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。线程数是可以被设定的。 原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃
- 信号量隔离
在该模式下,接收请求和执行下游依赖在同一个线程内完成,不存在线程上下文切换所带来的性能开销,所以大部分场景应该选择信号量模式,但是在下面这种情况下,信号量模式并非是一个好的选择
对比
| 对比 | 线程池隔离 | 信号量隔离 |
|---|---|---|
| 是否支持熔断 | 支持,当线程池到达MaxSize后,再请求会触发fallback接口进行熔断 | 支持,当信号量达到maxConcurrentRequest后,再请求会触发fallback |
| 是否支持超时 | 支持,可直接返回 | 不支持,如果阻塞,只能通过调用协议 |
| 隔离原理 | 每个服务单独用线程池 | 通过信号量的计数器 |
| 是否支持异步调用 | 可以是异步,也可以是同步。看调用的方法 | 同步调用,不支持异步 |
| 资源消耗 | 大,大量线程的上下文切换,容易造成机器负载高 | 小,只是个计数器 |
| 是否支持超时 | row 2 col 2 | row 1 col 2 |
5熔断器原理(CircuitBreaker)
熔断器的三种状态
- CLOSED:熔断器关闭状态,正常请求流程
- OPEN:熔断器开状态,请求直接熔断降级处理
- HALF-OPEN:熔断器半开状态,在一个熔断时间窗口结束后放一个请求
熔断器配置参数-重要
Circuit Breaker主要包括如下6个参数:
1、circuitBreaker.enabled
是否启用熔断器,默认是TRUE。 2 、circuitBreaker.forceOpen
熔断器强制打开,始终保持打开状态,不关注熔断开关的实际状态。默认值FLASE。 3、circuitBreaker.forceClosed 熔断器强制关闭,始终保持关闭状态,不关注熔断开关的实际状态。默认值FLASE。
4、circuitBreaker.errorThresholdPercentage 错误率,默认值50%,例如一段时间(10s)内有100个请求,其中有54个超时或者异常,那么这段时间内的错误率是54%,大于了默认值50%,这种情况下会触发熔断器打开。
5、circuitBreaker.requestVolumeThreshold
默认值20。含义是一段时间内至少有20个请求才进行errorThresholdPercentage计算。比如一段时间了有19个请求,且这些请求全部失败了,错误率是100%,但熔断器不会打开,总请求数不满足20。
6、circuitBreaker.sleepWindowInMilliseconds
半开状态试探睡眠时间,默认值5000ms。如:当熔断器开启5000ms之后,会尝试放过去一部分流量进行试探,确定依赖服务是否恢复
熔断器流程分析
熔断器工作的详细过程如下:
第一步,调用allowRequest()判断是否允许将请求提交到线程池
如果熔断器强制打开,circuitBreaker.forceOpen为true,不允许放行,返回。 如果熔断器强制关闭,circuitBreaker.forceClosed为true,允许放行。此外不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效而已。
第二步,调用isOpen()判断熔断器开关是否打开
如果熔断器开关打开,进入第三步,否则继续; 如果一个周期内总的请求数小于circuitBreaker.requestVolumeThreshold的值,允许请求放行,否则继续; 如果一个周期内错误率小于circuitBreaker.errorThresholdPercentage的值,允许请求放行。否则,打开熔断器开关,进入第三步。
第三步,调用allowSingleTest()判断是否允许单个请求通行,检查依赖服务是否恢复
如果熔断器打开,且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行。 此外,为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器。
6.项目搭建
6.1 消费者
添加依赖
<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-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</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-actuator</artifactId>
</dependency>
添加注解
@EnableCircuitBreaker
@EnableCircuitBreaker
增加配置类
@FeignClient(name ="ms-feign-producer",path = "/api/user",configuration = Config.class,fallback = UserServiceImpl.class)
public interface UserService {
@GetMapping("/{id}")
public String selectUser(@PathVariable("id") String id);
}
@Component
public class UserServiceImpl implements UserService{
@Override
public String selectUser(String id) {
return "我是熔断机制";
}
}
配置文件
spring:
application:
name: ms-feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
register-with-eureka: true
instance:
prefer-ip-address: true
#appname: ${spring.application.name}
instance-id: ${spring.cloud.client.ip-address}:${server.port}
hostname: ${spring.cloud.client.ip-address}
server:
port: 8081
hystrix:
command:
default:
circuitBreaker:
requestVolumeThreshold: 5 #设置时间窗口的请求量至少为5个
sleepWindowInMilliseconds: 5000
errorThresholdPercentage: 50
metrics:
rollingStats:
timeInMilliseconds: 5000 # 时间窗口
使用
@RequestMapping("/api/comsumer/user")
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/{id}")
public String selectUser(@PathVariable("id") String id){
return userService.selectUser(id);
}
}
6.2 生产者
添加依赖
<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-openfeign</artifactId>
</dependency>
修改配置文件
spring:
application:
name: ms-feign-producer
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
register-with-eureka: true
instance:
prefer-ip-address: true
#appname: ${spring.application.name}
instance-id: ${spring.cloud.client.ip-address}:${server.port}
hostname: ${spring.cloud.client.ip-address}
server:
port: 8082
提供服务
@RequestMapping("/api/user")
@RestController
public class UserController {
@Value("${server.port}")
Integer port;
@GetMapping("/{id}")
public String selectUser(@PathVariable("id") String id){
if ("1".equals(id)) {
int i = 1/0;
}
User user = new User();
user.setId(id);
user.setName("wangyunqi");
user.setPort(port);
return user.toString();
}
}
测试
- 1:当请求消费者的id?=1,在5秒内连续请求5次以上,然后在请求直接返回:"我是熔断机制" 通过端口认真熔断器: http://192.168.1.119:8081/actuator/health
"hystrix": {
"status": "CIRCUIT_OPEN",
"details": {
"openCircuitBreakers": [
"ms-feign-producer::UserService#selectUser(String)"
]
}
},
"ping": {
"status": "UP"
},
"refreshScope": {
"status": "UP"
}
- 2:等待sleepWindowInMilliseconds一个时间后,方位id=2,发现正常返回数据
User{id='2', name='111', age=0, port=8082}
通过端口认真熔断器: http://192.168.1.119:8081/actuator/health
"hystrix": {
"status": "UP"
},
"ping": {
"status": "UP"
},
"refreshScope": {
"status": "UP"
}
}
7.熔断器工作流程
流程说明:
- 1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中。
- 2:执行execute()/queue做同步或异步调用。
- 3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤。
- 4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤。
- 5:调用HystrixCommand的run方法。运行依赖逻辑
- 5a:依赖逻辑调用超时,进入步骤8。
- 6:判断逻辑是否调用成功
- 6a:返回成功调用结果
- 6b:调用出错,进入步骤8。
- 7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态。
- 8:getFallback()降级逻辑。
- 以下四种情况将触发getFallback调用:
- (1):run()方法抛出非HystrixBadRequestException异常
- (2):run()方法调用超时
- (3):熔断器开启拦截调用
- (4):线程池/队列/信号量是否跑满
- 8a:没有实现getFallback的Command将直接抛出异常
- 8b:fallback降级逻辑调用成功直接返回
- 8c:降级逻辑调用失败抛出异常
- 9:返回执行成功结果