分布式系统通常会存在单个的微服务故障问题,而复杂业务中往往涉及层层调用,当其中一层或者几层服务发生故障,最终可能会导致整个系统故障,即产生服务雪崩效应。
Hystrix 的作用是针对微服务故障发生时进行服务降级、服务熔断。
为什么需要 Hystrix
因为分布式系统中微服务的调用链路可能很长,为了防止某些服务出错而导致整体系统崩溃。一般需要对待调用的微服务方法进监测措施,一般措施包括 “服务降级” 和 “服务熔断”。
共同特性:
- 目的一致。都是从系统可用性可靠性着想,为防止系统的整体缓慢甚至崩溃而采用的措施
- 最终表现类似。对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用
- 粒度一般都是服务级别(Service 层)。当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改)
- 自治性要求很高。熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段
区别:
- 触发原因不太一样。服务熔断一般是某个服务故障引起,通常表现为是由于某个服务失败次数或者比例达到阈值而触发的服务熔断;而服务降级一般是从整体负荷考虑,往往是因为某个服务因为系统整体负荷偏高,导致调用超时而触发服务降级
- 管理目标的层次不太一样。熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外层服务开始)
服务降级
在讲解如何使用 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 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 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"; }
public String userServiceFallback() { return "UserServiceImpl.userServiceFallback"; }
}
|
在类上使用 @DefaultProperties
注解,并通过 defaultFallback 属性指定默认的降级方案,然后在需要使用默认降级方案的方法中添加 @HystrixCommand
注解。
注意使用 @DefaultProperties
注解所指定的 fallback 方法不能有入参(哪怕被降级的方法本身有参数),同时要求降级方法的返回值能够转换为被降级方法的返回值。
这种配置方法减少了大量降级方法的出现和过多的 @HystrixCommand
声明,缺点是将降级方案和我们本身的业务耦合在一起。
- 针对 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
| @Component @FeignClient(value = "cloud-user-service", fallback = UserProviderFeignFallback.class) public interface UserProviderFeign { @GetMapping("/user_info") String getUserInfo(@RequestParam(name = "uid") Integer uid);
}
@Component public class UserProviderFeignFallback implements UserProviderFeign { @Override public String getUserInfo(Integer uid) { return "UserProviderFeignFallback.getUserInfo"; }
}
|
当调用 Feign 服务接口失败(如提供该服务的节点宕机),将会执行服务接口实现类中对应降级方法。
服务熔断
“服务熔断”是应对雪崩效应的另一种微服务链路保护机制,和遭遇到超时异常和错误异常才会触发的“服务降级”不同。“服务降级”每次都会尝试去走正常的逻辑,而“服务熔断”在失败次数达到阈值(或者一定比例)的时候,就会打开熔断器开关,之后的一短时间内,都会直接走“备用方案”,直到到达一定时间,会将尝试部分请求走回正常的逻辑,如果调用成功(或者一定比例),则关闭熔断器,否则继续打开熔断器,并进入下一个计时周期。
熔断器有以下三种状态:
Closed
:关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制
Open
:打开状态,此时对下游的调用都直接使用“备用方案”,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态
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"), @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"), @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "60"), @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 {
public static final String EXECUTION_ISOLATION_STRATEGY = "execution.isolation.strategy";
public static final String EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS = "execution.isolation.thread.timeoutInMilliseconds";
public static final String EXECUTION_TIMEOUT_ENABLED = "execution.timeout.enabled";
public static final String EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT = "execution.isolation.thread.interruptOnTimeout";
public static final String EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "execution.isolation.semaphore.maxConcurrentRequests";
public static final String FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS = "fallback.isolation.semaphore.maxConcurrentRequests";
public static final String FALLBACK_ENABLED = "fallback.enabled";
public static final String CIRCUIT_BREAKER_ENABLED = "circuitBreaker.enabled";
public static final String CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD = "circuitBreaker.requestVolumeThreshold";
public static final String CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS = "circuitBreaker.sleepWindowInMilliseconds";
public static final String CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE = "circuitBreaker.errorThresholdPercentage";
public static final String CIRCUIT_BREAKER_FORCE_OPEN = "circuitBreaker.forceOpen";
public static final String CIRCUIT_BREAKER_FORCE_CLOSED = "circuitBreaker.forceClosed";
public static final String METRICS_ROLLING_PERCENTILE_ENABLED = "metrics.rollingPercentile.enabled";
public static final String METRICS_ROLLING_PERCENTILE_TIME_IN_MILLISECONDS = "metrics.rollingPercentile.timeInMilliseconds";
public static final String METRICS_ROLLING_PERCENTILE_NUM_BUCKETS = "metrics.rollingPercentile.numBuckets";
public static final String METRICS_ROLLING_PERCENTILE_BUCKET_SIZE = "metrics.rollingPercentile.bucketSize";
public static final String METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS = "metrics.healthSnapshot.intervalInMilliseconds";
public static final String REQUEST_CACHE_ENABLED = "requestCache.enabled";
public static final String REQUEST_LOG_ENABLED = "requestLog.enabled";
public static final String MAX_QUEUE_SIZE = "maxQueueSize";
public static final String CORE_SIZE = "coreSize";
public static final String KEEP_ALIVE_TIME_MINUTES = "keepAliveTimeMinutes";
public static final String QUEUE_SIZE_REJECTION_THRESHOLD = "queueSizeRejectionThreshold";
public static final String METRICS_ROLLING_STATS_NUM_BUCKETS = "metrics.rollingStats.numBuckets";
public static final String METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS = "metrics.rollingStats.timeInMilliseconds";
public static final String MAX_REQUESTS_IN_BATCH = "maxRequestsInBatch";
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
评论