如何使用Hystrix实现服务熔断?
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/129/ )。
概述
降级就是说在调用某个方法发生超时、异常等错误时,改为调用备用方法,并使用备用方法的结果来替代原方法的操作。例如原方法是请求Redis中的数据,但因为Redis宕机,导致请求发生异常,则此时可以调用备用方法通过查询数据库给出结果。
而熔断则在此基础上更进一步,即发现该方法频繁降级时直接停止对该方法的一切调用,直到该方法的情况好转。
环境配置
通过Hystrix就可以方便地实现这一操作。接下来,我们给出简单的示例。
首先,准备SpringBoot项目,然后在其中引入适用于SpringBoot的hystrix包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
注意,如果这时候启动提示如下的错误,则基本是SpringBoot版本与hystrix版本不匹配导致的。因为hystrix是基于SpringCloud的,而SpringCloud和SpringBoot存在版本对应关系。如果对应不一致,则可能导致出现下面的错误。
java.lang.NoSuchMethodError: 'void org.springframework.boot.builder.SpringApplicationBuilder.<init>(java.lang.Object[])'
这里我们给出一个可用的对应版本,即SpringBoot的2.3.5.RELEASE和spring-cloud-starter-netflix-hystrix的2.2.7.RELEASE。它们这一对是肯定没有问题的。
然后,我们需要在SpringBoot的主类上增加@EnableHystrix注解,以启用Hystrix。如下所示。
@SpringBootApplication
@EnableHystrix
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
环境配置就是这么简单,接下来我们就可以写业务代码了。
功能实现
例如我们编写如下的方法,也就是原方法。在原方法中,我们故意让它一定概率抛出异常、一定概率发生延迟、一定概率成功。
@HystrixCommand(
commandKey = "CircuitBreakerService_queryUserNameById",
fallbackMethod = "fallBackForSayHi",
commandProperties = {
// 开启熔断器(可以省略,因为默认就是开启的)
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "true"),
// 超时判定时间设置为30ms
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "30"),
// 时间窗内的最小采样值为5
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "5"),
// 错误超过30%时即引发熔断
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "30"),
// 熔断后恢复时间为500ms
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "500")
})
String queryUserNameById(int i) throws InterruptedException {
System.out.print("正在查询用户名,用户编号为" + i);
int randomValue = new Random().nextInt(100);
if (randomValue <= 30) {
// 约30%概率会触发异常
System.out.println(" : 查询发生异常 ");
throw new RuntimeException();
} else if (randomValue <= 60) {
// 约30%概率会触发超时
System.out.println(" : 查询发生延迟 ");
Thread.sleep(50);
return "编号" + i +"的用户名查询超时";
} else {
// 约40%概率会正常执行
System.out.println(" : 查询成功 ");
return "易哥 " + i;
}
}
我们在原方法上增加@HystrixCommand注解。其中比较关键的是fallbackMethod参数,我们指向了该原方法对应的备用方法,即当原方法发生错误时要调用的方法。备用方法如下所示。
String queryDefaultUserName(int i) {
System.out.println("生成默认用户名,用户编号为" + i);
return "平台用户" + i;
}
注意,这里的备用方法要和原方法有相同的入参、返回值。这一点也好理解,毕竟备用方法是要在某些时候替换原方法的,入参、返回值不一样那就没法替换了。
另外,我们还在@HystrixCommand注解中更改了熔断器的设置,让熔断器生效的最小请求数变为5,只要这些请求中错误超过30%即发生熔断,熔断后,在500ms后再尝试重新调用。
我们对上述原方法以100ms为间隔进行100次连续调用,可以在控制台上看到如下所示的结果片段(因异常和延迟为随机发生,故每次调用的结果不一定完全一致):
正在查询用户名,用户编号为69 : 查询成功
正在查询用户名,用户编号为70 : 查询发生延迟
生成默认用户名,用户编号为70
正在查询用户名,用户编号为71 : 查询发生异常
生成默认用户名,用户编号为71
生成默认用户名,用户编号为72
生成默认用户名,用户编号为73
生成默认用户名,用户编号为74
生成默认用户名,用户编号为75
生成默认用户名,用户编号为76
正在查询用户名,用户编号为77 : 查询发生延迟
生成默认用户名,用户编号为77
生成默认用户名,用户编号为78
生成默认用户名,用户编号为79
生成默认用户名,用户编号为80
生成默认用户名,用户编号为81
正在查询用户名,用户编号为82 : 查询成功
正在查询用户名,用户编号为83 : 查询发生延迟
生成默认用户名,用户编号为83
通过上述片段可以看出,用户编号72至76对应的查询并没有触发原方法,而是直接直行了备用方法。这就说明在此期间,熔断器是熔断的。
直到用户编号77的查询才再次触发了原方法,此时距熔断发生已经过去了500ms,说明熔断器正在尝试停止熔断。而此次原方法的执行仍然发生了错误,导致熔断继续。直到后面原方法执行成功了用户编号为82的查询操作,熔断才被暂时停止。
在最终的返回值中,我们可以看到如下的结果。表明无论是原方法执行错误还是熔断器熔断,备用方法的执行结果都会替代原方法的执行结果。
易哥 69
平台用户70
平台用户71
平台用户72
平台用户73
平台用户74
平台用户75
平台用户76
平台用户77
平台用户78
平台用户79
平台用户80
平台用户81
易哥 82
平台用户83
通过该示例,我们就演示了Hystrix的熔断功能。
参数详解
以上实现的是最简单的熔断示例,也仅仅使用了最简单的参数,而其他参数都是用的默认值。但是这个作为示例是可以的,真正使用时,大家还是要对Hystrix的各个参数有详细的了解,至少也要知道大体的含义和默认值。否则,后续使用中,可能会遇到不少百思不得其解的地方。
总之,Hystrix要比这个强大的多。大家还是要好好学一下。
下面是我对@HystrixCommand参数的介绍,主要是摘抄参考了《高性能架构之道(第二版)》这本书。上面的示例也是参考的书里的。下面的参数我这里列的不是很全,如果要细致学习的话,十分建议大家去看下这本书。
- groupKey 组名。通过为命令指定一个组名,我们就可以将多个指令分到同一个组中。这样,我们可以使用配置文件为该组设置参数,而不用在每个指令中一一设定参数。通过组名,我们也可以对指标汇总分类。默认为该注解所处类的类名。
- commandKey 指令名。用来唯一指定一个指令。这样,我们可以使用配置文件为某个指令设置参数,也可以在指标展示时知道指标具体来自哪个指令。默认为该注解所在的方法的方法名。如果程序中注解所在方法的名字不是唯一的,那强烈建议通过commandKey指定一个唯一的名字,以防止多个指令间配置信息的互相干扰。
- fallbackMethod 备用方法名。用来指定该方法的备用方法,当该方法发生错误或超时时,将执行该备用方法。注意,备用方法必须和该方法有相同的入参类型和返回值类型。
- commandProperties 命令参数。在这里可以为该指令设置多个命令参数。具体命令参数的介绍详见书中的命令参数小节。
- ignoreExceptions 要忽略的异常。通常,当原方法执行抛出异常时,Hystrix会自动帮我们调用备用方法。通过该参数指定一些异常,就可以让Hystrix遇到这些异常时,将它们抛出而不触发备用方法。
- raiseHystrixExceptions 异常包装设置。该配置只有两个选项,要么为空,要么填写RUNTIME_EXCEPTION。如果填写RUNTIME_EXCEPTION,那原方法执行中抛出的所有未被忽略(参见ignoreExceptions配置)的异常会被包装进HystrixRuntimeException中抛出。因为有些场景下,外部调用方需要捕获的异常的类型是相同的,以便于处理。默认值为空,这种情况下原方法的异常会被正常抛出而不被包装。
- defaultFallback 默认备用方法。它比备用方法(参见fallbackMethod配置)的优先级更低,因此只在没有配置备用方法的时候生效。默认备用方法不允许有入参,且返回值类型必须要和原方法一致。
其中熔断器相关的参数主要有:
- circuitBreaker.enabled 是否启用熔断器。默认值true。
- circuitBreaker.requestVolumeThreshold 熔断器滚动窗中使得熔断器生效的最小请求数。如果小于这个数,即使请求全部失效,熔断器也不会熔断。默认值20。
- circuitBreaker.errorThresholdPercentage 触发熔断器熔断的错误百分比。默认值50。
- circuitBreaker.sleepWindowInMilliseconds 熔断器的熔断时长。熔断器熔断后,将熔断该时间后再尝试重新调用。默认值5000。
- circuitBreaker.forceOpen 强制开启熔断,将拒绝所有请求。默认值false。 circuitBreaker.forceClosed 强制关闭熔断,将允许所有请求。默认值false。
好了,以上就是Hystrix实现熔断的简单示例。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。