Hystrix-介绍与使用(下)

41 篇文章 5 订阅
39 篇文章 1 订阅

上篇中对hystrix的隔离和熔断作了基本的介绍,本文将说明Hystrix在项目中的实际使用。

在开发中很多时候需要调用第三方接口或依赖,而第三方接口或依赖的响应时间是无法被调用方控制的。如果调用方发出大量的请求而长时间无响应,很可能会导致大量线程资源在阻塞中,拖垮服务器的性能。因此在调用第三方接口或依赖时使用hystrix进行隔离和熔断是有必要的。

注解使用

最常用的方式就是通过Hystrix注解使用。需要新引入一个依赖包:

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>${hystrix.version}</version>
</dependency>

因为是注解形式,所以需要设置aspect:

<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>

注意使用这个aspect需要开启aspectJ的自动代理:

<aop:aspectj-autoproxy/>

Demo如下:

public class AnnotationHystrixInvoke {

    @Resource
    private TestService testService;

    @Test
    public void invoke() {
        for (int i = 0; i < 10; i++) {
            String result = testService.getInfo();
            System.out.println(result);
        }
    }
}

测试的service如下:

@Service
public class TestService {

    @HystrixCommand(groupKey = "AnnotationHystrixInvoke", commandKey = "getInfo", fallbackMethod = "getFallback",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
            }
    )
    public String getInfo() {
        System.out.println(Thread.currentThread().getName() + "开始执行getInfo");
               try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            System.out.println("捕获到异常" + e.getMessage());
        }
       System.out.println(Thread.currentThread().getName() + "仍在执行getInfo");
        return "hello";
    }

    public String getFallback() {
        System.out.println(Thread.currentThread().getName() + "开始执行getFallback");
        return "bye bye";
    }
}

本例中主业务逻辑是getInfo函数,该函数调用超时时间设置为1s,降级逻辑为getFallback()。主业务逻辑线程sleep了2s,到了超时时间后hystrix启动一个新的线程中断主业务逻辑线程,新线程执行降级逻辑,同时主业务逻辑线程被中断后抛出了中断异常,捕获异常处理后继续执行。执行结果如下:

hystrix-AnnotationHystrixInvoke-1开始执行getInfo
捕获到异常sleep interrupted
hystrix-AnnotationHystrixInvoke-1仍在执行getInfo
HystrixTimer-1开始执行getFallback
bye bye
hystrix-AnnotationHystrixInvoke-2开始执行getInfo
捕获到异常sleep interrupted
hystrix-AnnotationHystrixInvoke-2仍在执行getInfo
HystrixTimer-1开始执行getFallback
bye bye
hystrix-AnnotationHystrixInvoke-3开始执行getInfo
捕获到异常sleep interrupted
hystrix-AnnotationHystrixInvoke-3仍在执行getInfo
HystrixTimer-2开始执行getFallback
bye bye
...

类级别默认属性

在实际项目中,业务场景较多。而HystrixCommand的属性也很多。于是就产生了一个问题:我们是否需要为每一个需要使用hystrix方法都定义一遍属性?比如:一个类中有很多方法,不能在每个方法都配置一遍相同的属性,容易造成配置代码的冗余,所以Javanica提供了@DefaultProperties注解,解释如下:

@DefaultProperties是类(类型)级别的注释,允许默认命令属性,如groupKey,threadPoolKey,commandProperties,threadPoolProperties,ignoreExceptions和raiseHystrixExceptions。

使用此注解指定的属性,将在类中使用@HystrixCommand注解的方法中公用,除非某个方法明确使用相应的@HystrixCommand参数来指定这些属性:

@DefaultProperties(groupKey = "DefaultGroupKey")
class Service {
    @HystrixCommand // hystrix command group key is 'DefaultGroupKey'
    public Object commandInheritsDefaultProperties() {
        return null;
    }
    @HystrixCommand(groupKey = "SpecificGroupKey") // command overrides default group key
    public Object commandOverridesGroupKey() {
        return null;
    }
}

注意事项

Hystrix只能在外部方法使用,这是因为Hystrix的使用是通过AOP进行拦截调用的,而Spring AOP是无法拦截内部方法调用的(见参考资料1)。

结合配置中心使用

在某些业务场景下,确实有必要为每一个HystrixCommand设置不同的参数值,全部写在注解上面显得代码很臃肿,冗余。而且有时这些参数值可能还会动态改变。因此引入配置中心就是一个比较好的方案。Spring Cloud使用的配置中心是基于gitHub来做配置的,没有配置界面。可以使用携程的框架部门提供的开源配置中心-Apollo。安装部署网上有很多资料,也有官网文档(参考资料2),这里就不描述了。

将Apollo部署好之后,要想获取到动态的配置参数,还需要引入两个依赖包:

<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
    <version>0.7.4</version>
</dependency>
<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.8</version>
</dependency>

另外需要一个类来负责初始化配置参数和读取参数的动态更新:

@Configuration
@EnableApolloConfig
public class ApolloDynamicConfig implements WatchedConfigurationSource, InitializingBean {

   private static final String HYSTRIX = "hystrix";

   private List<WatchedUpdateListener> listeners = new CopyOnWriteArrayList<>();

   @ZeusConfigChangeListener({HYSTRIX})
   public void onChange(ConfigChangeEvent changeEvent) {
      fireEvent(changeEvent);
   }

   private void fireEvent(ConfigChangeEvent changeEvent) {
      WatchedUpdateResult result = getWatchedUpdateResult(changeEvent);
      listeners.forEach(listener -> listener.updateConfiguration(result));
      HystrixPropertiesFactory.reset();
   }

   private WatchedUpdateResult getWatchedUpdateResult(ConfigChangeEvent changeEvent) {
      Map<String, Object> added = new HashMap<>();
      Map<String, Object> changed = new HashMap<>();
      Map<String, Object> deleted = new HashMap<>();
      changeEvent.changedKeys().forEach(key -> {
         ConfigChange change = changeEvent.getChange(key);
         switch (change.getChangeType()) {
            case ADDED:
               added.put(key, change.getNewValue());
               break;
            case MODIFIED:
               changed.put(key, change.getNewValue());
               break;
            case DELETED:
               deleted.put(key, change.getNewValue());
               break;
         }
      });
      return WatchedUpdateResult.createIncremental(added, changed, deleted);
   }

   @Override
   public void addUpdateListener(WatchedUpdateListener watchedUpdateListener) {
      if (watchedUpdateListener != null) {
         listeners.add(watchedUpdateListener);
      }
   }

   @Override
   public void removeUpdateListener(WatchedUpdateListener watchedUpdateListener) {
      listeners.remove(watchedUpdateListener);
   }

   @Override
   public Map<String, Object> getCurrentData() throws Exception {
      Map<String, Object> configMap = new HashMap<>();
      Config config = ConfigService.getConfig(HYSTRIX);
      config.getPropertyNames().forEach(p -> configMap.put(p, config.getProperty(p, "")));
      return configMap;
   }

   @Override
   public void afterPropertiesSet() throws Exception {
      installDynamicConfiguration(); // 安装动态配置
   }

   private void installDynamicConfiguration() {
      System.setProperty(DynamicPropertyFactory.ENABLE_JMX, "true");
      ConfigurationManager.install(new DynamicWatchedConfiguration(this));
   }
}

然后在配置中心创建hystrix.properties文件,配置示例如下:

#hystrix configuration
# see https://github.com/Netflix/Hystrix/wiki/Configuration
#
# === === === == 默认Command === === === ==
# 调用隔离方式, 默认: 采用线程隔离, ExecutionIsolationStrategy.THREAD
# Possible Values THREAD, SEMAPHORE
hystrix.command.default.execution.isolation.strategy = THREAD
# 调用超时时间, 默认: 5 秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 5000
# 使用信号量隔离时, 命令调用最大的并发数
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests = 10
# 使用信号量隔离时, 命令fallback调用最大的并发数
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests = 10
#
# === === === == 线程池 === === === ==
# 配置线程池大小, 默认值10个
hystrix.threadpool.default.coreSize = 10
# 配置队列长度, 默认-1使用SynchronousQueue,其他值则使用LinkedBlockingQueue.不可动态修改.
hystrix.threadpool.default.maxQueueSize = -1
# 队列拒绝的阈值,可通过修改这个变量动态修改允许排队的长度. maxQueueSize=-1时不适用.
hystrix.threadpool.default.queueSizeRejectionThreshold = 5
# 线程生存时间, 默认1分钟
hystrix.threadpool.default.keepAliveTimeMinutes = 1
#
# === === === == 熔断器 === === === ==
# 熔断器在整个统计时间内是否开启的阀值, 默认20个请求
hystrix.command.default.circuitBreaker.requestVolumeThreshold = 8
# 熔断器默认工作时间, 默认: 5 秒
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds = 5
# 默认: 50%, 当出错率超过50% 后熔断器启动.
hystrix.command.default.circuitBreaker.errorThresholdPercentage = 50
# 是否强制开启熔断器阻断所有请求, 默认: false, 不开启
hystrix.command.default.circuitBreaker.forceOpen = false
# 是否允许熔断器忽略错误, 默认false, 不开启
hystrix.command.default.circuitBreaker.forceClosed = false
# 以上为默认配置,以下为具体配置
# === === === == 单个ThreadPool配置 === === ===
hystrix. exampleGroupKey.HystrixThreadPoolKey.coreSize = 20
#
# === === === == 单个command的配置 === === === ==
#hystrix.command.exampleCommandKey.execution.isolation.thread.timeoutInMilliseconds = 1000

将最后两行配置参数去掉注释,在HystrixCommand注解上指定groupKey=” exampleGroupKey”,commandKey=” exampleGroupKey”,其他不需要指定,则会自动使用配置的参数。

参考资料

  1. https://www.jianshu.com/p/6534945eb3b5
  2. https://github.com/ctripcorp/apollo/wiki
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值