Hystrix配置插件化源码解析

时间:2021-04-17 01:16:38


依赖如下

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.18</version>
</dependency>

什么是Hystrix

2018.11发布了最后一个版本,目前处理维护阶段,不再升级版本

  • 用途:
  • 停止级联故障。fallback和优雅的降级,Fail fast和快速恢复
  • 实时监控和配置实时变更
  • 资源隔离,部分不可用不会导致整体系统不可用
  • 场景:商品列表接口中,需要获取红包、价格、标签等数据。这时候可以给这个一个线程池。
    如果线程池打满,也不会影响当前服务的非商品列表接口
  • 使用的框架:hystrix主要使用Rxjava,上手可参考:https://www.jianshu.com/p/5e93c9101dc5

Hystrix配置外部化、动态化

Hystrix自己实现了一个配置接口HystrixProperty(没用netfix的通用Config:com.netfix.config.Property)

public interface HystrixProperty<T> {
    // get获取属性方法
    public T get();

    /**
     * 生产property的工厂
     * HystrixProperty由Factory直接生产,不需要经过第三方依赖。
     * - 其核心子接口为(下面会进行讲解):HystrixDynamicProperty
      */
    public static class Factory {
        public static <T> HystrixProperty<T> asProperty(final T value) {
          return new HystrixProperty<T>() {
            @Override
            public T get() { return value; }
          };
        }
        //..... 剩下其他各种获取属性asProperty方法
    }
}

HystrixDynamicProperty

Hystrix中主要通过HystrixDynamicProperty接口来实现动态化属性。其对应 HystrixDynamicProperties 的两个实现类:

  • Hystrix也是通过多种方式管理属性类,其中有一个Util类用来管理各个属性,不过目前只支持:String、Integer、Long、Boolean
public interface HystrixDynamicProperties {
  public HystrixDynamicProperty<String> getString(String name, String fallback);
  public HystrixDynamicProperty<Integer> getInteger(String name, Integer fallback);
  public HystrixDynamicProperty<Long> getLong(String name, Long fallback);
  public HystrixDynamicProperty<Boolean> getBoolean(String name, Boolean fallback);
  public static class Util {
    public static <T> HystrixDynamicProperty<T> getProperty(
            HystrixDynamicProperties properties, String name, T fallback, Class<T> type) {
      return (HystrixDynamicProperty<T>) doProperty(properties, name, fallback, type);
    }
    private static HystrixDynamicProperty<?> doProperty(
            HystrixDynamicProperties delegate,
            String name, Object fallback, Class<?> type) {// 仅支持四种类型:String、Integer、Long、Boolean 
      if(type == String.class) return delegate.getString(name, (String) fallback);
      else if (type == Integer.class) return delegate.getInteger(name, (Integer) fallback);
      else if (type == Long.class) return delegate.getLong(name, (Long) fallback);
      else if (type == Boolean.class) return delegate.getBoolean(name, (Boolean) fallback);
      throw new IllegalStateException();
    }
  }
}

上面HystrixDynamicProperties中delegate.getXxxx()的具体实现为如下两种方式

  • HystrixDynamicPropertiesSystemProperties:
    通过System.getProperty()获取,天然支持动态性,但不支持callback回调及外部化配置
public final class HystrixDynamicPropertiesSystemProperties implements HystrixDynamicProperties {
    public HystrixDynamicPropertiesSystemProperties() {}
    private static class LazyHolder {
        private static final HystrixDynamicPropertiesSystemProperties INSTANCE = new HystrixDynamicPropertiesSystemProperties();
    }
    public static HystrixDynamicProperties getInstance() { return LazyHolder.INSTANCE; }
    @Override
    public HystrixDynamicProperty<Integer> getInteger(final String name, final Integer fallback) {
        return new HystrixDynamicProperty<Integer>() {
            @Override
            public String getName() { return name; }
            @Override
            public Integer get() {//Integer.getInteger其实最终还是调用了System.getProperty() 了
                return Integer.getInteger(name, fallback);
            }
            @Override
            public void addCallback(Runnable callback) {}
        };
    }
    // .......... 剩余其他String、Long、Boolean类型就不列出来,用法一样 
}
  • HystrixDynamicPropertiesArchaius(Archaius是Netfix自家配置管理库):
    Netfix自家的配置管理库,支持动态化
public class HystrixDynamicPropertiesArchaius implements HystrixDynamicProperties {
    @Override
    public HystrixDynamicProperty<String> getString(String name, String fallback) {
        return new StringDynamicProperty(name, fallback);
    }
    @Override
    public HystrixDynamicProperty<Integer> getInteger(String name, Integer fallback) {
        return new IntegerDynamicProperty(name, fallback);
    }
   // ..... 省略其他类型String、Long、Boolean
    private abstract static class ArchaiusDynamicProperty<T> 
        extends PropertyWrapper<T> implements HystrixDynamicProperty<T> {
        protected ArchaiusDynamicProperty(String propName, T defaultValue) { super(propName, defaultValue); }
        @Override
        public T get() { return getValue(); }
    }
    private static class IntegerDynamicProperty extends ArchaiusDynamicProperty<Integer> {
        protected IntegerDynamicProperty(String propName, Integer defaultValue) { super(propName, defaultValue); }
        /** prop.getInteger最终调用的是ConcurrentHashMap,那他是怎么塞进去CHM得?见下面解析 **/
        @Override
        public Integer getValue() { return prop.getInteger(defaultValue); }
    }
  // ..... 省略其他类型String、Long、Boolean
}

Archaius这种模式不同于System(System.getProperty()),他会从配置文件里面读取数据,起流程如下

  1. 获取IntegerDynamicProperty,是调用到super()方法
protected IntegerDynamicProperty(String propName, Integer defaultValue) {
   super(propName, defaultValue);// 在ArchaiusDynamicProperty类中
}
  1. 追踪调用到PropertyWrapper类,会进行获取对应单例,并赋予默认值等初始化操作
public abstract class PropertyWrapper<V> {
  protected PropertyWrapper(String propName, V defaultValue) {
    // !!!重点关注这个,这里才是怎么根据属性名拿到对应属性值的入口
    this.prop = DynamicProperty.getInstance(propName);
    this.defaultValue = defaultValue;
    //.... 期初初始化操作,省略
  }
}
  1. DynamicProperty.getInstance获取实例过程中,可能会更新属性值
public class DynamicProperty {
   // 根据属性名获取对应的动态化属性实例DynamicProperty
  public static DynamicProperty getInstance(String propName) {
    if (dynamicPropertySupportImpl == null) {
      DynamicPropertyFactory.getInstance();
    }
    // 1 先从缓存中获取属性值
    DynamicProperty prop = ALL_PROPS.get(propName);
    if (prop == null) {
        // 2 如果找不到,就尝试去更新(new DynamicProperty(propName)里面就有更新操作)
      prop = new DynamicProperty(propName);
      // 然后把新的属性值设置进去
      DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);
      if (oldProp != null) {
        prop = oldProp;
      }
    }
    return prop;
  }
}
  1. 最后来看下是怎么更新拉取一个值,核心在于updateValue()
public class DynamicProperty {
  private DynamicProperty(String propName) {
    this.propName = propName;
    updateValue();
  }
  // !!!!最终可以见到是通过一个dynamicPropertySupportImpl进行获取。那这个怎么拿到了,
  // !!!!关键在于是怎么绑定的,下面继续讲解怎么绑定
  private boolean updateValue() {
    String newValue;
    try {
      if (dynamicPropertySupportImpl != null) {
          // 调用这个实例方法获取属性值
        newValue = dynamicPropertySupportImpl.getString(propName);
      } else {
        return false;
      }
    } catch (Exception e) {
      logger.error("Unable to update property: " + propName, e);
      return false;
    }
    return updateValue(newValue);
  }
}

Hystrix如何选择合适的配置类进行加载

这一步会解释上一步的dynamicPropertySupportImpl实例是怎么选择?其实最终是通过DynamicPropertyFactory生产配置对象的时候返回回来,然后赋值给dynamicPropertySupportImpl
下面看一下他这个配置对象是怎么实例化的

  1. 初始化HystrixPlugins时,会加载解析动态属性
public class HystrixPlugins {
  private HystrixPlugins(ClassLoader classLoader, LoggerSupplier logSupplier) {
    this.classLoader = classLoader;
    dynamicProperties = resolveDynamicProperties(classLoader, logSupplier);
  }
}
  1. 先看看有没自定义的,有的话就classForName加载。然后用SPI扫描一下,如果扫描到就用第一个配置类。
    再看看HystrixDynamicPropertiesArchaius,如果不存在或加载失败则使用HystrixDynamicPropertiesSystemProperties
public class HystrixPlugins {
  private static HystrixDynamicProperties resolveDynamicProperties(ClassLoader classLoader, LoggerSupplier logSupplier) {
    // 第一步:先看看有没设置属性:"hystrix.plugin.hystrixDynamicProperties.implementation",如果有就用自定义的属性类
    HystrixDynamicProperties hp = getPluginImplementationViaProperties(HystrixDynamicProperties.class,
            HystrixDynamicPropertiesSystemProperties.getInstance());
    if (hp != null) {
      logSupplier.getLogger().debug(
              "Created HystrixDynamicProperties instance from System property named "
                      + "\"hystrix.plugin.HystrixDynamicProperties.implementation\". Using class: {}",
              hp.getClass().getCanonicalName());
      return hp;
    }
    // 第二步:用SPI的加载器进行扫描,拿到第一个然后返回
    hp = findService(HystrixDynamicProperties.class, classLoader);
    if (hp != null) {
      logSupplier.getLogger()
              .debug("Created HystrixDynamicProperties instance by loading from ServiceLoader. Using class: {}",
                      hp.getClass().getCanonicalName());
      return hp;
    }
    // 第三步:再看看HystrixDynamicPropertiesArchaius这个解析成功否,成功就用这个属性配置类
    hp = HystrixArchaiusHelper.createArchaiusDynamicProperties();
    if (hp != null) {
      logSupplier.getLogger().debug("Created HystrixDynamicProperties. Using class : {}",
              hp.getClass().getCanonicalName());
      return hp;
    }
    // 第四步:实在上面都找不到了,直接用兜底的系统属性类,用System.getProperty()获取
    hp = HystrixDynamicPropertiesSystemProperties.getInstance();
    logSupplier.getLogger().info("Using System Properties for HystrixDynamicProperties! Using class: {}",
            hp.getClass().getCanonicalName());
    return hp;
  }
}

看到这步骤,是不是有一点还比较懵逼的!哦,原来发现我们还没搞清他的配置信息是什么时候加载进来的,配置文件是哪一个。别慌,下面讲

配置信息如何加载进来

Hystrix有一个ConfigurationManager,初始化配置加载的时候会调用(默认会从config.properties加载,如果需要添加配置信息请加到这里来)。核心方法步骤如下:

  1. 获取系统配置(就是通过System.getProperties()全部加载进来)
  2. 从配置文件里面进行加载数据
public class ConfigurationManager {
    //.... 其他逻辑省略
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null && !Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG)) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = new ConcurrentCompositeConfiguration();
                    if (!Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_SYS_CONFIG)) {
                        // 第一步:获取系统配置(就是通过System.getProperties()全部加载进来)
                        SystemConfiguration sysConfig = new SystemConfiguration();
                        ((ConcurrentCompositeConfiguration) instance).addConfiguration(sysConfig, DynamicPropertyFactory.SYS_CONFIG_NAME);
                        try {
                            // 第二步:从默认的config.properties文件加载数据
                            //          然后看有没设置archaius.configurationSource.additionalUrls属性,如果有也从这个文件进行加载动态配置
                            //        最后会开启一个长轮询,默认每隔一分钟去刷新缓存数据
                            DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
                            ((ConcurrentCompositeConfiguration) instance).addConfiguration(defaultURLConfig, DynamicPropertyFactory.URL_CONFIG_NAME);
                        } catch (Throwable e) {
                            logger.warn("Failed to create default dynamic configuration", e);
                        }
                    }
                    registerConfigBean();
                }
            }
        }
        return instance;
    }
}

动态配置长轮询刷新大概代码如下

// 位置:DynamicURLConfiguration.java
public DynamicURLConfiguration() {
    URLConfigurationSource source = new URLConfigurationSource();
    if (source.getConfigUrls() != null && source.getConfigUrls().size() > 0) {
        startPolling(source, new FixedDelayPollingScheduler());
    }
}
// 位置:DynamicConfiguration.java
public void startPolling(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {
    this.scheduler = scheduler;
    this.source = source;
    init(source, scheduler);
    scheduler.startPolling(source, this);
}