背景是这样,小年的项目在跑定时任务时,发现抛出大量的异常:
从异常信息来看,应该像是 Hystrix 开启了断路器,触发了熔断限流。
查阅了一下相关资料,又翻阅了源码,得出的结论与小年开始的猜测是基本一致。而代码中没有 Fallback 处理的方法,所以程序就直接把异常抛出来。
Hystrix 有一个断路器的概念,当断路器开启时,也就是触发熔断机制阻断请求。
有两个关键参数,判断当前当前断路器是否开启:
# 断路器最小请求数(默认值20)
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
# 错误阈值百分比(默认值50)
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
hystrix.command.default.circuitBreaker.requestVolumeThreshold
换句话说,只有当在一个时间窗口内的请求数量达到或超过 circuitBreakerRequestVolumeThreshold 时,Hystrix 才会开始考虑是否需要打开断路器。如果请求数量没有达到这个阈值,Hystrix 会继续执行正常的请求处理流程,而不会进行断路器状态的切换。
这个参数的作用是为了避免在请求数量过少的情况下就打开断路器,以免对系统的正常运行产生不必要的影响。只有当请求数量达到一定的阈值时,才会开始进行断路器的状态切换。
hystrix.command.default.circuitBreaker.errorThresholdPercentage
当在一段时间内请求的错误百分比超过该阈值时,断路器将打开,停止向该服务发送请求,并快速失败返回错误响应,而不是继续尝试请求。
例如,如果将 errorThresholdPercentage 设置为 50,表示当一段时间内超过 50% 的请求发生错误时,断路器将打开。这可以防止故障的服务继续接收请求,从而避免对整个系统的进一步影响。
简单说,只有当前请求量大于 requestVolumeThreshold,并且失败率大于 errorThresholdPercentage,断路器才会开启。
源码:HystrixCircuitBreaker
@Override
public boolean isOpen() {
if (circuitOpen.get()) {
return true;
}
HealthCounts health = metrics.getHealthCounts();
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
return false;
}
if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
} else {
if (circuitOpen.compareAndSet(false, true)) {
circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
return true;
}
}
}
当然,如果你的代码有实现 Fallback 处理的方法,最终会回调你的 fackback 方法。
源码AbstractCommand.Class
private Observable<R> handleShortCircuitViaFallback() {
// record that we are returning a short-circuited fallback
eventNotifier.markEvent(HystrixEventType.SHORT_CIRCUITED, commandKey);
// short-circuit and go directly to fallback (or throw an exception if no fallback implemented)
Exception shortCircuitException = new RuntimeException("Hystrix circuit short-circuited and is OPEN");
executionResult = executionResult.setExecutionException(shortCircuitException);
try {
return getFallbackOrThrowException(this, HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT,
"short-circuited", shortCircuitException);
} catch (Exception e) {
return Observable.error(e);
}
}
回到问题本身,真正出发断路器开启,应该是跟 errorThresholdPercentage 有关,也就是说接口的错误率高了导致的。
小年再翻一下日志发现,其实接口最开始就抛出了 TimeoutException
查了一下下游接口的耗时,差不多 6-8 s,我的天,不 Timeout 才怪...
根本问题是找到了,接口超时响应导致错误率升高,触发 Hystrix 的熔断了断路器。
既然知道是接口超时,那就好办,只需调整 Hystrix 的超时时间应该就可以。
Hystrix超时配置
1、统一配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
这里是对所有的 Feign Client 做统一超时设置,超过6s就会熔断。
PS:如果是通过 spring-cloud-feign 接入的话,还需要加上配置
feign.hystrix.enabled=true
2、接口级别配置
假如有一个 Feign Client 定义如下:
@FeignClient(value = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/get")
Result<UserDTO> getUser(@RequestParam("id") Long id);
@PostMapping("/user/add")
Result<UserDTO> addUser(@RequestBody User user);
}
想要对 /user/get
方法配置超时时间,可以设置成:
hystrix.command.UserClient#getUser(Long).execution.isolation.thread.timeoutInMilliseconds=6000
中间的 UserClient#getUser(Long)
是针对接口方法级别的限流配置规则。
如果接口方法的参数是对象,怎么处理?
很简单,直接填写对象名称
hystrix.command.UserClient#addUser(User).execution.isolation.thread.timeoutInMilliseconds=6000
3、服务级别配置
如果是要基于 Feign Client 级别,统一设置 Client 下超时时间。网上的资料说如果是使用 **Zuul **可以直接通过 service-id 配置。
其他的话小年是没有找到的直接通过配置的方式,不过可以通过代码形式进行配置
@Configuration
public class Config {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder().setterFactory((target, method) -> {
String groupKey = target.name();
String commandKey = Feign.configKey(target.type(), method);
return HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
// 默认的接口方式
//.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
// service-id方式
//.andCommandKey(HystrixCommandKey.Factory.asKey(groupKey))
// Feign Client Name方式
.andCommandKey(HystrixCommandKey.Factory.asKey(target.type().getSimpleName()));
});
}
}
配置文件直接配置 Feign Client
hystrix.command.UserClient.execution.isolation.thread.timeoutInMilliseconds=6000
不过这种方式有个弊端就是,只能选用一种方式。就是说使用 Feign Client 级别的配置后,你的粒度就不能再具体到Client里面的接口了,只能选择其中一种方式。
当然,仅仅配置 hystrix 的超时时间是不够的。一般我们都是通过 spring-cloud-starter-openfeign
组件引入,而组建中的 OpenFeign
、Ribbon
也有各自的超时时间配置。
Hold on,可能有些人这里对这几个概念不是理解很清楚,小年这里就贴上 ChatGPT 的回答,让大家再温习一下:
OpenFeign底层可以选择使用不同的HTTP组件来实现HTTP请求的发送和响应的处理。默认情况下,OpenFeign 使用的是 Spring 的
RestTemplate
来处理HTTP请求,但也可以配置使用其他的 HTTP 客户端,比如 Apache HttpClient 或者 OkHttp 等。通过配置不同的 HTTP 客户端,可以根据实际需求来选择最适合的实现方式,以满足性能、功能和扩展性等方面的需求。这三个都是 Netflix 公司开发的用于构建分布式系统的开源工具,它们通常与 Spring Cloud 一起使用。
- Ribbon:Ribbon 是一个负载均衡器,它可以帮助客户端应用程序在多个实例的服务之间进行负载均衡。它通过轮询、随机选择、加权随机等算法将请求分发到不同的服务实例上。
- Hystrix:Hystrix 是一个容错和延迟容忍库,它提供了断路器、线程隔离、请求缓存、请求合并以及降级等功能,可以帮助构建弹性和高可用的分布式系统。它可以防止由于底层服务的故障或延迟导致的级联失败,并提供了回退机制,允许应用程序在服务不可用时提供默认响应或执行备用逻辑。
- OpenFeign:OpenFeign 是一个声明式的HTTP客户端,它简化了通过 REST API 与其他微服务进行通信的过程。它允许开发者通过定义接口并添加注解的方式来描述服务之间的调用关系,而不需要编写具体的 HTTP 请求代码。OpenFeign 底层集成了 Ribbon 和 Hystrix,可以通过配置实现负载均衡和容错功能。
联系和区别:
- Ribbon 和 Hystrix 是基础设施组件,用于处理服务间通信的负载均衡和容错问题。
- OpenFeign 则是在 Ribbon 和 Hystrix 的基础上构建的,它提供了一种更加简洁、声明式的方式来调用其他服务的 API,同时集成了 Ribbon 和 Hystrix 的功能。
- Ribbon 主要负责负载均衡,Hystrix主要负责容错和延迟容忍,而 OpenFeign 则是一个更高层次的抽象,提供了更加方便的服务调用方式。
OpenFeign超时时间配置
#建立连接的超时时间(默认10s)
feign.client.config.default.connect-timeout=1000
#读取数据的超时时间(默认60s)
feign.client.config.default.read-timeout=6000
Ribbon超时时间配置
#建立连接的超时时间(默认1s)
ribbon.ConnectTimeout=1000
#读取数据的超时时间(默认60s)
ribbon.ReadTimeout=60000
按照优先级: Hystrix > Ribbon > OpenFeign
所以一般来说,Ribbon 的超时时间要小于 Hystrix,并且 Ribbon 会有重试机制,还需要考虑重试次数。否者还没等到 Feign、Ribbon 超时,Hystrix 就熔断,重试也就无效了。
今天的分享就到这里了~
每一步都是进步!
我是宅小年,下期我们再见!
关注公众号「宅小年」,个人博客 📖 edisonz.cn,阅读更多分享文章