[享学Netflix] 十九、Netflix Hystrix插件机制:SPI接口介绍和HystrixPlugins详解

千万不要觉得学数学不重要,认为买菜都用不到。但是,但是,但是:它能决定你在哪儿买菜(毕竟拉分一般都靠数学)。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

Hystrix提供了插件机制(SPI机制)来提升自身的扩展性,提高弹性。这里所指的插件包括:

  • HystrixConcurrencyStrategy:获取并发相关类
  • HystrixEventNotifier:事件通知类
  • HystrixMetricsPublisher:度量信息类
  • HystrixPropertiesStrategy:Properties配置类
  • HystrixCommandExecutionHook:HystrixCommand回调函数类
  • HystrixDynamicProperties:配置信息

共六类插件。其实也可以说是五类,因为HystrixDynamicProperties严格意义上讲不算插件,它是给插件提供外部化配置的一个配置类,只是初始化它的时候也恰好支持到了外部化,所以索性也叫做插件吧。

这些插件被HystrixPlugins管理着,由它统一负责加载和实例化。本文就介绍这几大插件的作用,以及讲述Hystrix是如何管理、加载它们的。


正文

此部分分为两大块进行讲解:

  • SPI接口介绍
  • HystrixPlugins详解

SPI接口介绍

SPI:Service Provider Interface,是一种服务发现机制,JDK自带有ServiceLoader来实现这种机制,当然更为出名的是Spring的SpringFactoriesLoader(Dubbo也有自己的SPI实现哦),具体实现方式上都大同小异。

另外,这里所指的Interface并不强要求必须是接口,比如本文里使用均为抽象类,也是一样的可以正常使用。


HystrixConcurrencyStrategy

并发相关的策略类。抽象类,用于使用默认实现为系统的并发相关方面定义不同的行为或实现。

public abstract class HystrixConcurrencyStrategy {

	// ThreadFactory由HystrixThreadPoolKey来完成分组
	// 线程均以守护线程形式,线程名为:`hystrix-threadPoolKey.name()-1/2/3/4...`
    public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        final ThreadFactory threadFactory = getThreadFactory(threadPoolKey);
        final int dynamicCoreSize = corePoolSize.get();
        final int dynamicMaximumSize = maximumPoolSize.get();
        if (dynamicCoreSize > dynamicMaximumSize) {
            return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory);
        } else {
            return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory);
        }
    }
    // 配置来自于HystrixThreadPoolProperties,默认值是:
	// core核心是10,最大值max也是10,keepAliveTimeMinutes=1分钟
	// BlockingQueue因为不能配置,所以参见下面的这个getBlockingQueue方法
    public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { ... }
	public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
		// 如果maxQueueSize木值,那就使用同步队列 -> 木有缓冲区
        if (maxQueueSize <= 0) {
            return new SynchronousQueue<Runnable>();
        } else { // 否则使用Linked,队列大小是maxQueueSize哦(并不是无界的哦)
            return new LinkedBlockingQueue<Runnable>(maxQueueSize);
        }
	}

	// 给调用者一个机会,然你可以对callback进行包装一把
	// 该方法子啊HystrixContextRunnable、HystrixContextCallable、HystrixContexSchedulerAction
	// 里均会被调用
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return callable;
    }
}

它主要是用于获取/创建线程池,根据实际配置*(每个线程池的参数是可以不一样的)创建ThreadPoolExecutor。另外提供一个可以对Callback进行包装、装饰的方法~


HystrixConcurrencyStrategyDefault

这是它的唯一实现,也是默认实现。以单例形式提供服务:

public class HystrixConcurrencyStrategyDefault extends HystrixConcurrencyStrategy {

    private static HystrixConcurrencyStrategyDefault INSTANCE = new HystrixConcurrencyStrategyDefault();
    public static HystrixConcurrencyStrategy getInstance() {
        return INSTANCE;
    }
	...
}

简单的说,它是个空实现,用于兜底。


HystrixEventNotifier

hystrix命令执行过程中,接收相应的事件通知。

需要注意的是:这个notifier是同步调用的,因此里头方法的实现不能太耗时,不然则会阻塞,如果方法太耗时则需要考虑异步到其他线程。

public abstract class HystrixEventNotifier {

	// 空实现:当任意事件被触发时,均执行此方法
    public void markEvent(HystrixEventType eventType, HystrixCommandKey key) {
        // do nothing
    }
    // 空实现:当使用线程隔离方式,触发事件时执行此方法
    // 注意:如果被拒绝rejected(比如断路器全开了)、或者短路short-circuited了,那么此方法是不会被调用的
    public void markCommandExecution(HystrixCommandKey key, ExecutionIsolationStrategy isolationStrategy, int duration, List<HystrixEventType> eventsDuringExecution) {
        // do nothing
    }
}

HystrixCommand以及HystrixObservableCommand调用的时候,都会调用HystrixEventNotifier发布事件,提供给开发者自定义实现,来做指标收集及监控报警。

同样的,它的默认实现HystrixEventNotifierDefault属于空实现,作为兜底使用。


HystrixEventType

Hystrix事件类型枚举,并且还提供了分类:

  • EXCEPTION_PRODUCING_EVENT_TYPES:异常事件类型。包括:BAD_REQUEST、FALLBACK_FAILURE、FALLBACK_MISSING、FALLBACK_REJECTION
    • 注意:FALLBACK_MISSING表示木有提供fallabck函数,所以抛出异常哦。但是如果你正常提供了fallabck函数,那就是FALLBACK_SUCCESS就不属于异常的
  • TERMINAL_EVENT_TYPES:终端事件。SUCCESS、BAD_REQUEST、FALLBACK_SUCCESS、FALLBACK_FAILURE、FALLBACK_REJECTION、FALLBACK_MISSING、RESPONSE_FROM_CACHE、CANCELLED,也就是遇上这些事件表示处理结束喽。

这些事件类型和事件分类在Hystrix处理结果ExecutionResult里将会有体现~


HystrixMetricsPublisher

抽象类,默认实现工厂方法,用于创建“Metrics Publisher”实例,以获取指标和其他相关数据。

public abstract class HystrixMetricsPublisher {

    public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) {
        return new HystrixMetricsPublisherCommandDefault(commandKey, commandGroupKey, metrics, circuitBreaker, properties);
    }
    public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) {
        return new HystrixMetricsPublisherThreadPoolDefault(threadPoolKey, metrics, properties);
    }
    public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) {
        return new HystrixMetricsPublisherCollapserDefault(collapserKey, metrics, properties);
    }
}

三个方法参数都几乎一模一样,分别用于创建如上三组接口的实例。需要说明的是:他们的默认实现XXXDefault均为空实现。

该接口和Hystrix的监控密切相关,你可以自己将metrics指标信息落地存储,然后集成到其它指标监控系统。值得注意的是:Micrometer对它便有集成,详见MicrometerMetricsPublisher就是一种指标采集的实现。

说明:指标、监控是个较大的话题,也是将后指标监控专题的重中之重。


HystrixPropertiesStrategy

创建HystrixCommandProperties、HystrixThreadPoolProperties、HystrixCollapserProperties、HystrixTimerThreadPoolProperties实例,方便在Hystrix的各方各面上使用。

public abstract class HystrixPropertiesStrategy {

    public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) {
        return new HystrixPropertiesCommandDefault(commandKey, builder);
    }
    public HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) {
        return new HystrixPropertiesThreadPoolDefault(threadPoolKey, builder);
    }
    public HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) {
        return new HystrixPropertiesCollapserDefault(collapserKey, builder);
    }
    public HystrixTimerThreadPoolProperties getTimerThreadPoolProperties() {
        return new HystrixPropertiesTimerThreadPoolDefault();
    }

}

这个策略接口比较简单,并且相关xxxProperties类在上文有详细解释,本处就不再鳌诉。


HystrixCommandExecutionHook

HystrixCommand的不同声明周期的回调接口,默认实现是无操作。

public abstract class HystrixCommandExecutionHook {

	// 命令开始执行前调用
    public <T> void onStart(HystrixInvokable<T> commandInstance) {
        //do nothing by default
    }
    // 发送数据时调用
    public <T> T onEmit(HystrixInvokable<T> commandInstance, T value) {
        return value; //by default, just pass through
    }
    public <T> Exception onError(HystrixInvokable<T> commandInstance, FailureType failureType, Exception e) {
        return e; //by default, just pass through
    }
    public <T> void onSuccess(HystrixInvokable<T> commandInstance) {
        //do nothing by default
    }
	.... // 省略其它方法,实在太多了
}

hystrix在执行命令的各个节点会调用HystrixCommandExecutionHook,这些都是留给调用者的钩子方法。

HystrixCommandExecutionHook提供了对HystrixCommandHystrixObservableCommand生命周期的钩子方法,开发者可以自定义实现,做一些额外的处理,比如日志打印、覆盖response、更改线程状态等等。


HystrixPlugins详解

本类是Hystrix内置插件机制的实现。上面介绍了几个SPI接口/抽象类,但是你会发现Default默认实现均是空实现,因为这些均是Hystrix留给使用者的钩子,交给你去定制,而这种定制化的实现就是通过HystrixPlugins来做的。


初始化HystrixDynamicProperties

实际上HystrixDynamicProperties也是HystrixPlugins所管理的六大SPI接口之一,但是它稍有点特殊,它亦是给其它5个SPI接口提供配置的基础,所以它伴随着HystrixPlugins的初始化而初始化,并且它只能被初始化一次(其它的均可多次)

// 它自己以单例形式展示
public class HystrixPlugins {

	// 获取HystrixPlugins 单例
    public static HystrixPlugins getInstance() {
        return LazyHolder.INSTANCE;
    }	

	// =======================它的成员属性们=======================
    private final ClassLoader classLoader;
    
	// 下面这几个属性均是pachage的访问级别~~~~~~~~~~~~~~~
	// 使用AtomicReference来保证原子性
	final AtomicReference<HystrixEventNotifier> notifier = new AtomicReference<HystrixEventNotifier>();
    final AtomicReference<HystrixConcurrencyStrategy> concurrencyStrategy = new AtomicReference<HystrixConcurrencyStrategy>();
    final AtomicReference<HystrixMetricsPublisher> metricsPublisher = new AtomicReference<HystrixMetricsPublisher>();
    final AtomicReference<HystrixPropertiesStrategy> propertiesFactory = new AtomicReference<HystrixPropertiesStrategy>();
    final AtomicReference<HystrixCommandExecutionHook> commandExecutionHook = new AtomicReference<HystrixCommandExecutionHook>();
    
    private final HystrixDynamicProperties dynamicProperties;
	
	// 初始化的时候(只会进行一次),给成员属性赋值
    private HystrixPlugins(ClassLoader classLoader, LoggerSupplier logSupplier) {
        //This will load Archaius if its in the classpath.
        this.classLoader = classLoader;
        dynamicProperties = resolveDynamicProperties(classLoader, logSupplier);
    }
}

HystrixPlugins初始化的时候,会给dynamicProperties赋值,获取它的实例优先级顺序是这样的:

  1. 从System系统属性里找"hystrix.plugin." + classSimpleName + ".implementation",也就是hystrix.plugin.HystrixDynamicProperties.implementation这个key。若存在就Class.forName()
  2. 通过ServiceLoader的SPI方式加载HystrixDynamicProperties的实现类
  3. 通过HystrixArchaiusHelper.createArchaiusDynamicProperties()创建一个实现类:
    1. hystrix-plugins.propertieshystrix-plugins-环境名.properties里的属性都加入到全局配置中
    2. 从类路径下Class.forName("com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius")
    3. 这样Hystrix的属性就和全局属性关联上了喽,并且使用Archaius动态管理
  4. 最后,若都还没找到,那就兜底使用HystrixDynamicPropertiesSystemProperties:使用System属性(一般情况下,到第三步肯定就能完成实例化了)

实际生产中,大概率会在第3步完成初始化,也就是HystrixDynamicPropertiesArchaius实例,这样的话hystrix-plugin.properties里的属性也都是会生效的哦。


初始化其它SPI接口

其它SPI接口并不会主动初始化,而是按需被调用的时候完成查找、初始化动作。

下面以获取HystrixEventNotifier实例为例,其它的接口获取逻辑一毛一样:

HystrixPlugins:

    public void registerEventNotifier(HystrixEventNotifier impl) {
        if (!notifier.compareAndSet(null, impl)) {
            throw new IllegalStateException("Another strategy was already registered.");
        }
    }
    public HystrixEventNotifier getEventNotifier() {
    	// 由此可见,手动注册进来的优先级是最高的
        if (notifier.get() == null) {
            Object impl = getPluginImplementation(HystrixEventNotifier.class);
		
			// 如果还找到,就使用Defualt默认实例:空实现
            if (impl == null) {
                notifier.compareAndSet(null, HystrixEventNotifierDefault.getInstance());
            } else {
                notifier.compareAndSet(null, (HystrixEventNotifier) impl);
            }
        }
        return notifier.get();
    }

	// 从
    private <T> T getPluginImplementation(Class<T> pluginClass) {
        T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);
        if (p != null) return p;        
        return findService(pluginClass, classLoader);
    }

查找一个SPI接口的实现类的流程如下(优先级由高到低):

  1. 手动register进来的
  2. 从上一步初始化好的dynamicProperties里找key为:hystrix.plugin." + classSimpleName + ".implementation属性作为实现类
  3. 使用ServiceLoader的SPI方式查找实现类
  4. 使用Default实现(空实现)

由于插件的实现类一般不可能动态改变,所以它一般有个最佳实践:采用hystrix.plugin." + classSimpleName + ".implementation的方式把它各插件的实现类全类名统一配置在hystrix-plugin.properties文件里,这也方便了管理。相信这也便是Hystrix设计一个名hystrix-plugin的文件的唯一目的吧(因为刚好它里面的属性是不具有动态性的,完全符合条件)~


使用示例

为了看到效果,自定义一个SPI实现类:

public class MyHystrixMetricsPublisher extends HystrixMetricsPublisher {

    public MyHystrixMetricsPublisher() {
        System.out.println("MyHystrixMetricsPublisher被实例化了...");
    }
}

hystrix-plugins.properties里配置如下:

hystrix.plugin.HystrixMetricsPublisher.implementation=com.yourbatman.hystrix.MyHystrixMetricsPublisher

书写测试程序:

@Test
public void fun1() {
    HystrixPlugins instance = HystrixPlugins.getInstance();
    HystrixDynamicProperties dynamicProperties = instance.getDynamicProperties();
    System.out.println(dynamicProperties.getString("name", null).get());

    System.out.println("===========================================");
    // 类型
    System.out.println(dynamicProperties.getClass());
    System.out.println(instance.getMetricsPublisher().getClass());
    System.out.println(instance.getEventNotifier().getClass());
    System.out.println(instance.getConcurrencyStrategy().getClass());
}

运行程序,控制台输出:

YourBatman
===========================================
class com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius
MyHystrixMetricsPublisher被实例化了...
class com.yourbatman.hystrix.MyHystrixMetricsPublisher
class com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifierDefault
class com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault

说明:MyHystrixMetricsPublisher的配置项放在config.properties也是有效的,但是放在hystrix-plugins.properties里乃最佳实践(因为它还有一个优点:hystrix-plugins-环境名称.properties这个配置相同key的优先级更高,所以非常方便你进行多环境测试使用)。


总结

关于Netflix Hystrix插件机制:SPI接口介绍和HystrixPlugins详解就介绍到这了,这里最为重要的我认为是对后续自定制监控模块打好基础,为扩展做好准备。

同时本文也能告诉我们,一个优秀的框架是需要具备良好的扩展性,以及预留足够多的钩子程序的,这样才能有更多人参与进来,流行度才会铺开。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了316 篇原创文章 · 获赞 480 · 访问量 41万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104531461