推荐一款一站式性能监控工具(开源项目)
Pepper-Metrics 是跟一位同事一起开发的开源组件,主要功能是通过比较轻量的方式与常用开源组件(jedis/mybatis/motan/dubbo/servlet)集成,收集并计算metrics,并支持输出到日志及转换成多种时序数据库兼容数据格式,配套的grafana dashboard友好的进行展示。项目当中原理文档齐全,且全部基于SPI设计的可扩展式架构,方便的开发新插件。另有一个基于docker-compose的独立demo项目可以快速启动一套demo示例查看效果 github.com/zrbcool/pep… 。如果大家觉得有用的话,麻烦给个star,也欢迎大家参与开发,谢谢:)
Motan的SPI与Dubbo的SPI类似,它在Java原生SPI思想的基础上做了优化,并且与Java原生SPI的使用方式很相似。
介绍Java原生SPI的相关文章有很多,这里不再赘述。下面主要介绍一下Motan中的SPI机制,先从使用说起。
下面以实现一个Filter的扩展为例,说说他咋用。
Filter的作用是在 Provider
端接收请求或 Consumer
端发送请求时进行拦截,做一些特别的事情。下面从 Consumer
端的视角,做一个计算单次请求响应时间的统计需求。
首先需要写一个类,实现 com.weibo.api.motan.filter.Filter
接口的 filter
方法。
@SpiMeta(name = "profiler") public class ProfilerFilter implements Filter { @Override public Response filter(Caller<?> caller, Request request) { // 记录开始时间 long begin = System.nanoTime(); try { final Response response = caller.call(request); return response; } finally { // 打印本次响应时间 System.out.println("Time cost : " + (System.nanoTime() - begin)); } } } 复制代码
其次,在 META-INF/services
目录下创建名为 com.weibo.api.motan.filter.Filter
的文件,内容如下:
# 例如:com.pepper.metrics.integration.motan.MotanProfilerFilter #全限定名称#.MotanProfilerFilter 复制代码
然后给Protocol配置 filter
ProtocolConfigBean config = new ProtocolConfigBean(); config.setName("motan"); config.setMaxContentLength(1048576); config.setFilter("profiler"); // 配置filter return config; 复制代码
最后在 RefererConfig
中使用这个 ProtocolConfig
即可。
BasicRefererConfigBean config = new BasicRefererConfigBean(); config.setProtocol("demoMotan"); // ... 省略其他配置 ... 复制代码
如此一来,在 Consumer
端就可以拦截每次请求,并打印响应时间了。
接下来,继续研究一下Motan是如何做到这件事的。
Motan的SPI的实现在 motan-core/com/weibo/api/motan/core/extension
中。组织结构如下:
motan-core/com.weibo.api.motan.core.extension |-Activation:SPI的扩展功能,例如过滤、排序 |-ActivationComparator:排序比较器 |-ExtensionLoader:核心,主要负责SPI的扫描和加载 |-Scope:模式枚举,单例、多例 |-Spi:注解,作用在接口上,表明这个接口的实现可以通过SPI的形式加载 |-SpiMeta:注解,作用在具体的SPI接口的实现类上,标注该扩展的名称 复制代码
private static ConcurrentMap<Class<?>, ExtensionLoader<?>> extensionLoaders = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); private ConcurrentMap<String, T> singletonInstances = null; private ConcurrentMap<String, Class<T>> extensionClasses = null; private Class<T> type; private ClassLoader classLoader; // 类加载使用的ClassLoader 复制代码
extensionLoaders
是类变量,他管理的是由 @Spi
注解标注的接口与其 ExtensionLoader
的映射,作为所有SPI的全局管理器。
singletonInstances
维护了当前 ExtensionLoader
中的单例扩展。
extensionClasses
维护了当前 ExtensionLoader
所有扩展实例的Class对象,用于创建多例(通过class.newInstance创建)。
type
维护了当前 @Spi
注解标注的接口的 class
对象。
在 Motan
中,可以通过以下方式初始化ExtensionLoader(以上文中的Filter SPI为例):
// 初始化 Filter 到全局管理器 `extensionLoaders` 中 ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class); 复制代码
然后我们具体看一下 ExtensionLoader.getExtensionLoader(Filter.class)
都干了啥。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // 检查type是否为空、type是否是接口类型、type是否被@Spi标注,检查失败会抛出异常 checkInterfaceType(type); // 尝试从上文提到的 `extensionLoaders` 管理器中获取已有的ExtensionLoader ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type); // 获取失败的话,尝试扫描并加载指定type的扩展,并初始化之 if (loader == null) { loader = initExtensionLoader(type); } return loader; } 复制代码
然后看看 initExtensionLoader
方法干了啥。
// synchronized锁控制,防止并发初始化 public static synchronized <T> ExtensionLoader<T> initExtensionLoader(Class<T> type) { ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type); // 二层检查,防止并发问题 if (loader == null) { // type会赋值给实例变量 `type`,并初始化实例变量 `classLoader` 为当前线程上线文的ClassLoader loader = new ExtensionLoader<T>(type); // 添加到全局管理器 `extensionLoaders` 中 extensionLoaders.putIfAbsent(type, loader); loader = (ExtensionLoader<T>) extensionLoaders.get(type); } return loader; } 复制代码
至此,我们就初始化了 Filter
接口的 ExtensionLoader
,并将它托管到了 extensionLoaders
中。
Motan
是懒加载策略,当第一次获取具体的某一扩展实例时,才会扫描和加载所有的扩展实例。
例如,可以通过以下方式获取我们上面创建的名为 profiler
的 Filter
接口的扩展实例。
// 初始化 Filter 到全局管理器 `extensionLoaders` 中 ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class); Filter profilterFilter = extensionLoader.getExtension("profiler"); 复制代码
Filter
接口的 ExtensionLoader
实际上是在第一次 extensionLoader.getExtension("profiler")
时完成的。下面具体看一下 getExtension
方法干了啥。
public T getExtension(String name) { // Notes:就是通过这个checkInit方法扫描和加载的 checkInit(); // .. 暂时省略 .. } 复制代码
继续研究 checkInit
方法。
private volatile boolean init = false; private void checkInit() { // 用init标记,只初始化一次 if (!init) { loadExtensionClasses(); } } private static final String PREFIX = "META-INF/services/"; private synchronized void loadExtensionClasses() { if (init) { return; } // 扫描和加载 extensionClasses = loadExtensionClasses(PREFIX); singletonInstances = new ConcurrentHashMap<String, T>(); init = true; } 复制代码
loadExtensionClasses
方法会扫描 META-INF/services/
下的所有文件,并解析文件内容,它会调用 loadClass
方法,该方法实现如下:
// classNames 就是每个文件中的具体扩展实现的全限定名称。 private ConcurrentMap<String, Class<T>> loadClass(List<String> classNames) { ConcurrentMap<String, Class<T>> map = new ConcurrentHashMap<String, Class<T>>(); for (String className : classNames) { try { Class<T> clz; if (classLoader == null) { clz = (Class<T>) Class.forName(className); } else { clz = (Class<T>) Class.forName(className, true, classLoader); } checkExtensionType(clz); // 获取 @SpiMeta 注解声明的名称 String spiName = getSpiName(clz); if (map.containsKey(spiName)) { failThrows(clz, ":Error spiName already exist " + spiName); } else { map.put(spiName, clz); } } catch (Exception e) { failLog(type, "Error load spi class", e); } } return map; } 复制代码
这个方法做的事情就是获取所有合法的扩展的class。最终其返回值会赋值给实例变量 extensionClasses
,至此完成了扫描和加载工作。
PS:由上可知,extensionClasses的K-V是具体扩展实现的 @SpiMeta 名称和对应class的映射。以上文的 ProfilerFilter
为例来说,KEY=profiler,VALUE=ProfilerFilter.class
继续看刚才 getExtension
方法中省略的部分。
public T getExtension(String name) { checkInit(); if (name == null) { return null; } try { Spi spi = type.getAnnotation(Spi.class); // 获取单例 if (spi.scope() == Scope.SINGLETON) { return getSingletonInstance(name); } else { // 获取多例 Class<T> clz = extensionClasses.get(name); if (clz == null) { return null; } return clz.newInstance(); } } catch (Exception e) { failThrows(type, "Error when getExtension " + name, e); } return null; } 复制代码
获取多例的情况很容易,直接从前面加载好的 extensionClasses
中获取,如果获取到就 newInstance()
一个新的实例。
下面看下单例的情况: getSingletonInstance
方法。
private T getSingletonInstance(String name) throws InstantiationException, IllegalAccessException { T obj = singletonInstances.get(name); if (obj != null) { return obj; } Class<T> clz = extensionClasses.get(name); if (clz == null) { return null; } synchronized (singletonInstances) { obj = singletonInstances.get(name); if (obj != null) { return obj; } obj = clz.newInstance(); singletonInstances.put(name, obj); } return obj; } 复制代码
获取单例时,会优先尝试从单例集合 singletonInstances
中获取,如果获取不到,说明这个单例实例还没添加到单例集合中(或没有对应名称的具体实例),然后会尝试从 extensionClasses
中获取,如果还获取不到,就是真没有了,如果获取到,会new一个instance到单例集合中。
以上就是 Motan 获取具体SPI扩展实现的方式。
以上便是 Motan 的 SPI 机制。