导读:Hystrix的资源隔离策略有两种,分别为:线程池和信号量。说到资源隔离,那我们在实战中需要注意哪些点呢?
一、背景
对于Hystrix熔断器的隔离策略分别为:线程池和信号量,前面一篇已经做了详细说明 《微服务架构 | Hystrix的资源隔离策略该如何选择?》
具体使用哪种策略,需根据业务场景综合评估。一般情况下,推荐使用线程池隔离。
尽但是实战中对于Hystrix熔断器需要还需要注意哪些点呢?
二、Hystrix 实战经验分享
在线程池隔离策略下,线程池大小及超时时间的设置至关重要,直接影响着系统服务的响应能力。如线程池大小若设置的太大会造成资源浪费及线程切换等开销;若设置的太小又支撑不了用户请求,造成请求排队。而超时时间设置的太长会出现部分长耗时请求阻塞线程,造成其它正常请求排队等待;若设置的太短又会造成太多正常请求被熔断。
建议在理解下图先阅读《微服务架构 | Hystrix的资源隔离策略该如何选择?》
对此Hystrix官方给的建议如图:
即转换为以下计算公式:
例如某服务TP99情况下每秒钟会接收30个请求,然后每个请求的响应时长是200ms,按如上公式计算可得:
线程池大小 = 0.2 * 30 + 4(冗余缓冲值)= 10,超时时间 = 300ms
在实际开发中可能会遇到某外部调用方法有Hystrix注解与其它注解一起使用的情况,例如查询方法加上缓存注解。此时需特别注意注解间的执行顺序,避免出现非预期的结果:
程序在运行中接口请求的成功或者失败率来决定所依赖的命令是否打开。如果打开,针对该接口的后续请求会被拒绝。有此可见,对异常的控制是Hystrix运行效果起很大影响。
下面的案例分析下问题所在。
@HystrixCommand(fallbackMethod="executeScriptFallback")
public Response<Object> executeScript(FormulaDTO express){
if(!StringUtils.isEmpty(express.getScript())) {
throw new ParamsNotValidException("无效参数");
}
try {
return sysFormulaLocalApi.executeScript(express);
}catch (Exception e){
log.error("#executeScript 无效参数->{}",JsonUtil.toJsonString(express),e);
return Response.err(JsonUtil.toJsonString(express));
}
}
仔细阅读上面代码不难发现,有两个异常处理问题。
非法或者无效参数等系统调用异常失败不应该影响熔断,不应该计算在熔断判断逻辑范围内。对此可以将非法或者无效参数等的异常封装到熔断外层逻辑进行异常捕捉处理,或者封装HystrixBadRequestException
进行抛出。
因为在Hystrix内部逻辑中HystrixBadRequestException
异常已默认为不算作失败统计范围内。
对远程服务的直接调用进行try-catch会把异常直接“吞掉”,会直接造成Hystrix获取不到网络异常等服务不可用异常。建议在catch日志记录处理后将异常再throw出来。
Hystrix在依赖服务调用时通过增加fallback方法返回默认值的方式来支持服务优雅降级。但fallback的使用也有很多需要注意的地方,大致总结如下:
在使用Hystrix开发中肯定都见过这三个key,但很多人并不理解这三个key的意义以及对Hystrix的作用,尤其是threadPooKey,故在此总结下:
针对服务端改如何配置熔断器参数包括服务注册客户端配置、hystrix扩展配置,下面案例可参考
/**
* 服务注册客户端配置类
*/
@Configuration
@EnableFeignClients(basePackages = NamingConstant.BASE_PACKAGE)
@EnableCircuitBreaker
public class DiscoveryClientConfig {
@Autowired
private HystrixExtendConfig hystrixExtendConfig;
/**
* 重定义Hystrix的GroupKey
*/
@Bean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder()
.setterFactory(new HystrixSetterFactory(hystrixExtendConfig));
}
/**
* hystrix扩展配置
*/
@Configuration
@ConfigurationProperties(prefix = "kmss.hystrix")
@Data
public class HystrixExtendConfig {
private Map<String, String> threadpool = new ConcurrentHashMap<>();
private Map<String, String> command = new ConcurrentHashMap<>();
}
/**
* hystrix参数设置
*/
@Slf4j
public class HystrixSetterFactory implements SetterFactory {
private HystrixExtendConfig config;
public HystrixSetterFactory(HystrixExtendConfig config) {
super();
this.config = config;
}
@Override
public Setter create(Target<?> target, Method method) {
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory
.asKey(getGroupKey(target, method));
HystrixCommandKey commandKey = HystrixCommandKey.Factory
.asKey(Feign.configKey(target.type(), method));
HystrixCommandProperties.Setter properties = getCommandProperties(
target);
return Setter.withGroupKey(groupKey)
.andCommandKey(commandKey)
.andCommandPropertiesDefaults(properties);
}
}
Hystrix开发三个核心的key分别实现:计算groupKey
/** 计算groupKey */
private String getGroupKey(Target<?> target, Method method) {
String groupKey = getGroupKeyByTarget(target);
if (groupKey == null) {
// 根据target计算groupKey
groupKey = getDefaultGroupKey();
log.debug("url:{}, hystrix group(default):{}", target.url(),
groupKey);
} else {
log.debug("url:{}, hystrix group:{}", target.url(), groupKey);
}
return groupKey;
}
三、总结
本文主要对Hystrix实战过程中使用进行总结分享,有关于隔离策略、线程池设置、参数优先级等知识点讲解,也有关于注解叠加、异常处理、参数动态配置等具体问题解决方案,希望对大家有所帮助。