导读:Hystrix的资源隔离策略有两种,分别为:线程池和信号量。说到资源隔离,那我们在实战中需要注意哪些点呢?
一、背景
对于Hystrix熔断器的隔离策略分别为:线程池和信号量,前面一篇已经做了详细说明 《微服务架构 | Hystrix的资源隔离策略该如何选择?》
具体使用哪种策略,需根据业务场景综合评估。一般情况下,推荐使用线程池隔离。
尽但是实战中对于Hystrix熔断器需要还需要注意哪些点呢?
二、Hystrix 实战经验分享
在线程池隔离策略下,线程池大小及超时时间的设置至关重要,直接影响着系统服务的响应能力。如线程池大小若设置的太大会造成资源浪费及线程切换等开销;若设置的太小又支撑不了用户请求,造成请求排队。而超时时间设置的太长会出现部分长耗时请求阻塞线程,造成其它正常请求排队等待;若设置的太短又会造成太多正常请求被熔断。
建议在理解下图先阅读《微服务架构 | Hystrix的资源隔离策略该如何选择?》
对此Hystrix官方给的建议如图:
即转换为以下计算公式:
-
线程池大小 = 服务TP99响应时长(单位秒) * 每秒请求量 + 冗余缓冲值
-
超时时间(单位毫秒) = 1000(毫秒) / 每秒请求量
例如某服务TP99情况下每秒钟会接收30个请求,然后每个请求的响应时长是200ms,按如上公式计算可得:
线程池大小 = 0.2 * 30 + 4(冗余缓冲值)= 10,超时时间 = 300ms
▐ 注解叠加
在实际开发中可能会遇到某外部调用方法有Hystrix注解与其它注解一起使用的情况,例如查询方法加上缓存注解。此时需特别注意注解间的执行顺序,避免出现非预期的结果:
-
缓存注解未生效此时Hystrix注解切面的执行是在最外层,由于Hystrix内部执行是通过ProceedingJoinPoint.getTarget()获取目标对象,使用反射调用的方式直接执行到目标对象方法上,从而造成中间其它注解逻辑丢失。可通过指定注解执行顺序@Order解决保证Hystrix注解执行在最里层。
-
因缓存异常造成该查询方法被熔断如果Hystrix注解切面的执行是在最外层,此时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远程调用的异常处理
对远程服务的直接调用进行try-catch会把异常直接“吞掉”,会直接造成Hystrix获取不到网络异常等服务不可用异常。建议在catch日志记录处理后将异常再throw出来。
▐ fallback方法
Hystrix在依赖服务调用时通过增加fallback方法返回默认值的方式来支持服务优雅降级。但fallback的使用也有很多需要注意的地方,大致总结如下:
-
fallback 方法访问级别、参数等要与对应依赖服务一致对于需要获取触发fallback的异常实例,可以通过fallback方法增加Throwable类型参数(加到最后一个参数)即可。
-
fallback 方法中执行的逻辑尽量轻量,如用本地缓存或静态默认值,避免远程调用
-
如果fallback方法里有远程调用,建议也使用Hystrix包装起来,且保证与主命令线程池的隔离
-
对于写操作的远程调用不建议使用fallback降级写服务的调用失败可以直接抛出给方法调用侧进行业务判断。
▐ groupKey、commandKey、threadPoolKey
在使用Hystrix开发中肯定都见过这三个key,但很多人并不理解这三个key的意义以及对Hystrix的作用,尤其是threadPooKey,故在此总结下:
-
groupKey通过group key可以对命令方法进行分组,便于Hystrix数据统计、告警及dashboad展示。一般会根据远程服务的业务类型进行区分,如账户服务定义一个group key,订单服务定义另一个group key。默认值是@HystrixCommand注解标注的方法所在的类名。
-
commandKey具体命令方法的标识名称,常用于对该命令进行动态参数设置。默认值是@HystrixCommand注解标注的方法名。
-
threadPoolKey用于标识命令所归属的线程池,具有相同threadPoolKey的命令使用同一个线程池。
若该key不指定,默认值就是groupKey,即@HystrixCommand注解标注的方法所在的类名。
在实际项目中,我们会建议尽量通过threadPoolKey来指定线程池, 而不是通过groupKey的默认方式划分, 因为会存在某个命令需要跟同组其他命令进行线程隔离的场景,以避免互相影响。
针对服务端改如何配置熔断器参数包括服务注册客户端配置、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实战过程中使用进行总结分享,有关于隔离策略、线程池设置、参数优先级等知识点讲解,也有关于注解叠加、异常处理、参数动态配置等具体问题解决方案,希望对大家有所帮助。
专注于高质量 技术文章原创分享与交流,拒绝水文、软文。