SpringCloud 学习笔记-Hystrix(六)
SpringCloud 学习笔记, 第六章 Hystrix
# 雪崩场景模拟
用户使用客户端, 调用暴露在外的 Server A
服务. 而 Server A
又调用了 Server B
, Server B
中又需要调用 Server C
, Server D
- 在时间点(A)
Server D
挂掉 - 时间点(B) 由于
Server B
在调用Server D
时, 因为Server D TimeOut
, 导致Server B
挂掉 - 同理: 时间点(C) 由于
Server A
在调用Server B
时, 因为Server B TimeOut
, 导致Server A
挂掉 - 即使
Server C
可以对外正常提供服务, 整个服务链, 也是挂掉了.
# Hystrix 介绍
在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。
# 熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制. 当扇出链路的某个Server
不可用或者响应时间太长时, 会进行服务的降级, 进而熔断该服务节点的调用, 快速返回”错误”的响应信息.
# 熔断来源
我们家用电闸上都有保险丝模块, 当电压出现短路问题时, 自动跳闸, 此刻电路主动断开, 我们的电器就会收到保护. 否则, 不能断开的话, 后果不堪设想.
# 微服务的熔断
在分布式系统中, 我们往往需要依赖下游服务, 不管是内部系统还是第三方服务. 如果下游出现问题, 我们还是盲目地去请求, 即使失败了多次, 还是傻傻的去请求, 去等待. 这样, 会增加了整个链路的响应时间, 可能会造成上层服务的Timeout
# 熔断的作用
熔断器, 可以防止应用程序不断地尝试可能超时和失败的服务, 能达到应用程序执行而不必等待下游服务修正错误服务.
熔断器, 是能让应用程序自我诊断下游系统的错误是否已修正. 如果没有, 不放量去请求, 如果请求成功了, 慢慢的增加请求, 再次尝试调用.
看下面的代码:
package com.striveonger.demo.client.hello.web.config;
import com.netflix.hystrix.*;
/**
* @description:
* @author: Mr.Lee
* @date: 2021-05-12 14:09
*/
public class CustomCircuitBreakerCommand extends HystrixCommand<String> {
public CustomCircuitBreakerCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CommandKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPoolKey"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(true) // 默认是true, 用于控制是否启用服务降级
.withCircuitBreakerForceOpen(false) // 默认是false, 强制打开断路器, 拒绝所有请求
.withCircuitBreakerForceClosed(false) // 默认是false, 强制关闭断路器, 接收所有请求
.withExecutionTimeoutInMilliseconds(2000) // run 方法的执行超时时间
.withMetricsRollingStatisticalWindowInMilliseconds(10000) // 设置断路器窗口检查时长为10s
.withCircuitBreakerRequestVolumeThreshold(5) // 1. 设置断路器10秒内的流量阀值(10s内调用, 超过5次)
.withCircuitBreakerErrorThresholdPercentage(0) // 2. 错误百分比超过0% (同时超过1, 2条件的阀值时, 熔断器打开)
.withCircuitBreakerSleepWindowInMilliseconds(1000) // 隔1s之后, 熔断器会尝试半开(关闭), 重新放进来请求
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10) // 配置队列大小
.withCoreSize(3) // 配置线程池里的线程数
)
);
}
@Override
protected String run() throws Exception {
// 模拟业务耗时
Thread.sleep(100);
return "Hello...";
}
@Override
protected String getFallback() {
/*
* 执行 Fallback 的四种情况:
* 1. run 方法 throw Exception
* 2. 请求超时
* 3. 超出线程数
* 4. 断路开关打开 isCircuitBreakerOpen() = true
*/
return "Fallback...";
}
}
class TestHystrixCommand {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 25; i++) {
Thread.sleep(100);
HystrixCommand<String> command = new CustomCircuitBreakerCommand();
String result = command.execute();
System.out.println(String.format("call times: %02d; result: %s; isCircuitBreakerOpen: %s", i + 1, result, command.isCircuitBreakerOpen()));
}
/*
* 执行结果:
*
* call times: 01; result: Hello...; isCircuitBreakerOpen: false
* call times: 02; result: Hello...; isCircuitBreakerOpen: false
* call times: 03; result: Hello...; isCircuitBreakerOpen: false
* call times: 04; result: Hello...; isCircuitBreakerOpen: false
* call times: 05; result: Hello...; isCircuitBreakerOpen: false
* call times: 06; result: Fallback...; isCircuitBreakerOpen: true // 超过 流量阀值: 5(执行时间, 0.5s)
* call times: 07; result: Fallback...; isCircuitBreakerOpen: true
* call times: 08; result: Fallback...; isCircuitBreakerOpen: true
* call times: 09; result: Fallback...; isCircuitBreakerOpen: true
* call times: 10; result: Fallback...; isCircuitBreakerOpen: true
* call times: 11; result: Fallback...; isCircuitBreakerOpen: true
* call times: 12; result: Fallback...; isCircuitBreakerOpen: true
* call times: 13; result: Fallback...; isCircuitBreakerOpen: true
* call times: 14; result: Fallback...; isCircuitBreakerOpen: true
* call times: 15; result: Fallback...; isCircuitBreakerOpen: true
* call times: 16; result: Hello...; isCircuitBreakerOpen: false // 熔断器半开状态, 重试请求(成功, 重置流量阀值)
* call times: 17; result: Hello...; isCircuitBreakerOpen: false
* call times: 18; result: Hello...; isCircuitBreakerOpen: false
* call times: 19; result: Hello...; isCircuitBreakerOpen: false
* call times: 20; result: Hello...; isCircuitBreakerOpen: false
* call times: 21; result: Hello...; isCircuitBreakerOpen: true // 再次超过 流量阀值: 5(执行时间, 2.1s)
* call times: 22; result: Fallback...; isCircuitBreakerOpen: true
* call times: 23; result: Fallback...; isCircuitBreakerOpen: true
* call times: 24; result: Fallback...; isCircuitBreakerOpen: true
* call times: 25; result: Fallback...; isCircuitBreakerOpen: true
*/
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
上面的Demo
中, 熔断规则:
- 单位时间(
10s
)内的请求超过流量阀值(5
) - 失败百分比大于阀值(
0%
, 有错就会熔断)
其中, 较为重要的 熔断开关: isCircuitBreakerOpen
, 下面还会用到哦~
# 降级
# 降级的本质
降级就是为了解决资源不足和访问量增加的矛盾.
# 降级牺牲的是什么
从强一致性 变成 最终一致性
大多数的系统是不需要强一致性的. 强一致性就要求多种资源的占用, 减少强一致性就能释放更多资源. 这也是我们一般利用消息中间件来削峰填谷.
# 隔离
# 线程池隔离
# 信号量隔离
# 请求缓存
# 请求合并
参考文章:
- https://www.cnblogs.com/yufeng218/p/11042555.html
- https://cloud.spring.io/spring-cloud-netflix/multi/multi__circuit_breaker_hystrix_clients.html