Spring Cloud 核心组件:Hystrix

分布式系统通常会存在单个的微服务故障问题,而复杂业务中往往涉及层层调用,当其中一层或者几层服务发生故障,最终可能会导致整个系统故障,即产生服务雪崩效应。

Hystrix 的作用是针对微服务故障发生时进行服务降级、服务熔断。


为什么需要 Hystrix

因为分布式系统中微服务的调用链路可能很长,为了防止某些服务出错而导致整体系统崩溃。一般需要对待调用的微服务方法进监测措施,一般措施包括 “服务降级” 和 “服务熔断”。

  • 服务降级 & 服务熔断

共同特性:

  1. 目的一致。都是从系统可用性可靠性着想,为防止系统的整体缓慢甚至崩溃而采用的措施
  2. 最终表现类似。对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用
  3. 粒度一般都是服务级别(Service 层)。当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改)
  4. 自治性要求很高。熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段

区别:

  1. 触发原因不太一样。服务熔断一般是某个服务故障引起,通常表现为是由于某个服务失败次数或者比例达到阈值而触发的服务熔断;而服务降级一般是从整体负荷考虑,往往是因为某个服务因为系统整体负荷偏高,导致调用超时而触发服务降级
  2. 管理目标的层次不太一样。熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外层服务开始)

服务降级

在讲解如何使用 Hystrix 实现“服务降级”之前,需要先导入 Hystrix 依赖并在启动类开启 Hystrix。

导入 Hystrix 依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

在启动类中启用 Hystrix

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

根据使用场景的不同,Hystrix 提供了多种配置“服务降级”的方法:

  1. 直接配置降级方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserServiceImpl implements UserService {

@Override
@HystrixCommand(fallbackMethod = "getUserInfoFallback", commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "3000")
})
public Object getUserInfo(Integer uid) {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {

}
return "UserServiceImpl.getUserInfo";
}

public Object getUserInfoFallback(Integer uid) {
return "UserServiceImpl.getUserInfoFallback";
}

}

我们可以直接在方法上使用 @HystrixCommand 注解配置降级方案,当配置了服务降级的方法发生超时或者抛出异常,会自动执行所配置的降级方案中的 fallbackMethod 。

使用这种配置方法的好处是能够精确为每个方法配置降级方案,缺点是会导致大量的降级方法出现和过长重复的 @HystrixCommand 出现。


  1. 配置默认降级方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DefaultProperties(defaultFallback = "userServiceFallback", commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "3000")
})
@Service
public class UserServiceImpl implements UserService {

@Override
@HystrixCommand
public Object getUserInfo(Integer uid) {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {

}
return "UserServiceImpl.getUserInfo";
}

// 注意这里取消了入参,可指定了返回值为 String,String 能够转换为 Object
public String userServiceFallback() {
return "UserServiceImpl.userServiceFallback";
}

}

在类上使用 @DefaultProperties 注解,并通过 defaultFallback 属性指定默认的降级方案,然后在需要使用默认降级方案的方法中添加 @HystrixCommand 注解。

注意使用 @DefaultProperties 注解所指定的 fallback 方法不能有入参(哪怕被降级的方法本身有参数),同时要求降级方法的返回值能够转换为被降级方法的返回值。

这种配置方法减少了大量降级方法的出现和过多的 @HystrixCommand 声明,缺点是将降级方案和我们本身的业务耦合在一起。


  1. 针对 Feign 的服务接口配置降级方案:

需要先在 application.yml 开启配置

1
2
3
feign:
hystrix:
enabled: true

再针对 Feign 接口创建一个实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// UserProviderFeign.java
@Component
@FeignClient(value = "cloud-user-service", fallback = UserProviderFeignFallback.class)
public interface UserProviderFeign {

@GetMapping("/user_info")
String getUserInfo(@RequestParam(name = "uid") Integer uid);

}

// UserProviderFeignFallback.java
@Component
public class UserProviderFeignFallback implements UserProviderFeign {

@Override
public String getUserInfo(Integer uid) {
return "UserProviderFeignFallback.getUserInfo";
}

}

当调用 Feign 服务接口失败(如提供该服务的节点宕机),将会执行服务接口实现类中对应降级方法。


服务熔断

“服务熔断”是应对雪崩效应的另一种微服务链路保护机制,和遭遇到超时异常和错误异常才会触发的“服务降级”不同。“服务降级”每次都会尝试去走正常的逻辑,而“服务熔断”在失败次数达到阈值(或者一定比例)的时候,就会打开熔断器开关,之后的一短时间内,都会直接走“备用方案”,直到到达一定时间,会将尝试部分请求走回正常的逻辑,如果调用成功(或者一定比例),则关闭熔断器,否则继续打开熔断器,并进入下一个计时周期。

熔断器有以下三种状态:

  1. Closed :关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制
  2. Open :打开状态,此时对下游的调用都直接使用“备用方案”,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态
  3. Half-Open :半熔断状态,允许定量的服务请求,如果调用都成功(或一定比例)则认为故障已经得到修复,关闭熔断器,否则重新回到熔断器打开状态
熔断器的三种状态

Hystrix 实现服务熔断采用的仍然是 @HystrixCommand 注解。但通过配置不同的 @HystrixProperty 可以实现服务熔断的配置:

1
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
@Service
public class UserServiceImpl implements UserService {

@Override
@HystrixCommand(fallbackMethod = "userServiceFallback", commandProperties = {
// 开启熔断机制
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "true"),
// 设置统计计算阈值(按每 10 次请求做一次统计)
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"),
// 设置错误百分比(每 10 次请求为一次统计,错误比例达到 60% ,即错误次数达到 6 次,开启熔断器)
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "60"),
// 设置窗口时间(熔断器开启多久后,切换为 Half-Open 状态,统计成功次数,尝试关闭熔断器)
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "1000")
})
public Object getUserInfo(Integer uid) {
if (uid <= 0) {
throw new RuntimeException();
}
return "UserServiceImpl.getUserInfo";
}

public Object userServiceFallback(Integer uid) {
return "UserServiceImpl.userServiceFallback uid param";
}

}

当服务调用的入参为连续负数,将会触发熔断器打开,在之后的窗口时间内,将会直接执行 fallback 方法(即使这时候的入参为合法值)。只有当时间窗口倒计时结束,才会将熔断器的开关转为 Half-Open 状态,统计成功次数,由此来决定熔断器是继续打开还是关闭。

Hystrix 的服务熔断实现原理是对将待保护的函数调用包裹在断路器对象中,让断路器对象负责调用并来监控失败。当失败次数达到特定的阈值时,断路器打开,后续对此断路器对象的访问将直接调用设置的 fallback 方法,不会调用受保护的函数。

熔断器的实现原理

相关配置

除了上述说到几个 @HystrixProperty 以外,还有哪些配置项可供配置。

翻看 Hystrix 中的 HystrixPropertiesManager 源码可以查看所有的配置项:

1
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
public final class HystrixPropertiesManager {

/** 控制 HystrixCommand.run() 如何执行 */

/**
* 隔离策略
* 默认值为 THREAD,有 THREAD 和 SEMAPHORE
* THREAD: 它在单独的线程上执行,并发请求受线程池中的线程数量的限制
* SEMAPHORE: 它在调用线程上执行,并发请求受到信号量计数的限制
*/
public static final String EXECUTION_ISOLATION_STRATEGY = "execution.isolation.strategy";

/**
* 超时时间
* 默认值为 1000
* 在 THREAD 模式下,达到超时时间,可以中断
* 在 SEMAPHORE 模式下,会等待执行完成后,再去判断是否超时
*/
public static final String EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS = "execution.isolation.thread.timeoutInMilliseconds";

/**
* 是否开启 HystrixCommand.run() 的超时
* 默认值:true
*/
public static final String EXECUTION_TIMEOUT_ENABLED = "execution.timeout.enabled";

/**
* HystrixCommand.run() 执行超时是否中断
* 默认值:true,THREAD 模式有效
*/
public static final String EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT = "execution.isolation.thread.interruptOnTimeout";

/**
* 允许 HystrixCommand.run() 方法的最大请求数
* 默认值:10,SEMAPHORE 模式有效
*/
public static final String EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "execution.isolation.semaphore.maxConcurrentRequests";



/** 控制 HystrixCommand.getFallback() 如何执行 */

/**
* 设置从调用线程允许 HystrixCommand.getFallback() 方法的最大请求数。
* 默认值:10,SEMAPHORE 模式有效
*/
public static final String FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "fallback.isolation.semaphore.maxConcurrentRequests";

/**
* 确定在发生失败或拒绝时是否尝试调用 HystrixCommand.getFallback()
* 默认值:true,SEMAPHORE 模式有效
*/
public static final String FALLBACK_ENABLED = "fallback.enabled";



/** 控制 HystrixCircuitBreaker 断路器的行为 */

/**
* 是否开启断路器
* 默认值:true
*/
public static final String CIRCUIT_BREAKER_ENABLED = "circuitBreaker.enabled";

/**
* 熔断器触发的最少请求次数
* 默认值:20
*/
public static final String CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD = "circuitBreaker.requestVolumeThreshold";

/**
* 熔断器打开的窗口时间,即熔断器打开后多少毫秒后切换为 Half-Open 状态,通常为故障修复的平均时间
* 默认值:5000
*/
public static final String CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS = "circuitBreaker.sleepWindowInMilliseconds";

/**
* 熔断器打开的失败率阈值
* 默认值:50
*/
public static final String CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE = "circuitBreaker.errorThresholdPercentage";

/**
* 强制断路器进入打开状态,将拒绝所有的请求
* 默认值:false
*/
public static final String CIRCUIT_BREAKER_FORCE_OPEN = "circuitBreaker.forceOpen";

/**
* 强制断路器进入关闭状态,将允许所有的请求
* 默认值:false
*/
public static final String CIRCUIT_BREAKER_FORCE_CLOSED = "circuitBreaker.forceClosed";



/** HystrixCommand 和 HystrixObservableCommand 执行捕获指标 */

/**
* 是否开启应以百分位数跟踪和计算执行延迟
* 如果设定为 false,则所有摘要统计信息(平均值,百分位数)都将返回 -1
* 默认值:true
*/
public static final String METRICS_ROLLING_PERCENTILE_ENABLED = "metrics.rollingPercentile.enabled";

/**
* 此属性设置统计滚动窗口的持续时间
* 对于断路器的使用和发布 Hystrix 保持多长时间的指标
* 默认值:10000
*/
public static final String METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS = "metrics.rollingPercentile.timeInMilliseconds";

/**
* 设置 rollingPercentile 窗口将划分的桶的数量
* 如果禁用它们,则所有摘要统计信息(平均值,百分位数)都将返回 -1
* 默认值:6
*/
public static final String METRICS_ROLLING_PERCENTILE_NUM_BUCKETS = "metrics.rollingPercentile.numBuckets";

/**
* 设置每个存储桶保留的最大执行次数
* 默认值:100
*/
public static final String METRICS_ROLLING_PERCENTILE_BUCKET_SIZE = "metrics.rollingPercentile.bucketSize";

/**
* 属性设置在允许计算成功和错误百分比并影响断路器状态的健康快照之间等待的时间
* 默认值:500
*/
public static final String METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS = "metrics.healthSnapshot.intervalInMilliseconds";



/** HystrixCommand 使用的 HystrixRequestContext 功能 */

/**
* 是否应与 HystrixRequestCache 一起使用
* 默认值:true
*/
public static final String REQUEST_CACHE_ENABLED = "requestCache.enabled";

/**
* HystrixCommand 执行和事件是否应记录到 HystrixRequestLog
* 默认值:true
*/
public static final String REQUEST_LOG_ENABLED = "requestLog.enabled";



/** 线程池的行为 */

/**
* 请求等待队列
* 默认值:-1,无界队列
* 如果使用正数,队列将从 SynchronizeQueue 改为 LinkedBlockingQueue
*/
public static final String MAX_QUEUE_SIZE = "maxQueueSize";

/**
* 线程池核心数
* 默认值:10
*/
public static final String CORE_SIZE = "coreSize";

/**
* 设置保持活动时间(分钟)
* 默认值:1
*/
public static final String KEEP_ALIVE_TIME_MINUTES = "keepAliveTimeMinutes";

/**
* 设置队列大小拒绝阈值
* 即使未达到 maxQueueSize 也将发生拒绝的人为最大队列大小
* 默认值:5
*/
public static final String QUEUE_SIZE_REJECTION_THRESHOLD = "queueSizeRejectionThreshold";

/**
* 设置滚动统计窗口划分的桶数
* 默认值:10
*/
public static final String METRICS_ROLLING_STATS_NUM_BUCKETS = "metrics.rollingStats.numBuckets";

/**
* 为线程池保留多长时间
* 默认值:10000
*/
public static final String METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS = "metrics.rollingStats.timeInMilliseconds";



/** 控制 HystrixCollapser 行为 */

/**
* 设置在触发批处理执行之前批处理中允许的最大请求数
* 默认值:Integer.MAX_VALUE
*/
public static final String MAX_REQUESTS_IN_BATCH = "maxRequestsInBatch";

/**
* 设置创建批处理后触发其执行的毫秒数
* 默认值:10
*/
public static final String TIMER_DELAY_IN_MILLISECONDS = "timerDelayInMilliseconds";

}

默认的配置项设置在抽象类 HystrixCommandProperties

1
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
public abstract class HystrixCommandProperties {
private static final Logger logger = LoggerFactory.getLogger(HystrixCommandProperties.class);
static final Integer default_metricsRollingStatisticalWindow = 10000;
private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;
private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;
private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;
private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;
private static final Boolean default_circuitBreakerForceOpen = false;
static final Boolean default_circuitBreakerForceClosed = false;
private static final Integer default_executionTimeoutInMilliseconds = 1000;
private static final Boolean default_executionTimeoutEnabled = true;
private static final HystrixCommandProperties.ExecutionIsolationStrategy default_executionIsolationStrategy;
private static final Boolean default_executionIsolationThreadInterruptOnTimeout;
private static final Boolean default_executionIsolationThreadInterruptOnFutureCancel;
private static final Boolean default_metricsRollingPercentileEnabled;
private static final Boolean default_requestCacheEnabled;
private static final Integer default_fallbackIsolationSemaphoreMaxConcurrentRequests;
private static final Boolean default_fallbackEnabled;
private static final Integer default_executionIsolationSemaphoreMaxConcurrentRequests;
private static final Boolean default_requestLogEnabled;
private static final Boolean default_circuitBreakerEnabled;
private static final Integer default_metricsRollingPercentileWindow;
private static final Integer default_metricsRollingPercentileWindowBuckets;
private static final Integer default_metricsRollingPercentileBucketSize;
private static final Integer default_metricsHealthSnapshotIntervalInMilliseconds;
}


参考文献

CircuitBreaker

Hystrix Configuration

Java 对象详解 更值得其他语言开发者看的《阿里Java开发手册》

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×