微服务架构 | Hystrix 熔断降级需要注意哪些点?

2,218 阅读4分钟

导读: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进行抛出。

图片

/**
 * 服务注册客户端配置类
 */
@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实战过程中使用进行总结分享,有关于隔离策略、线程池设置、参数优先级等知识点讲解,也有关于注解叠加、异常处理、参数动态配置等具体问题解决方案,希望对大家有所帮助。

 专注于高质量 技术文章原创分享与交流,拒绝水文、软文。

首发地址:微服务架构 | Hystrix 熔断降级需要注意哪些点?