推荐一款一站式性能监控工具(开源项目)java
Pepper-Metrics是跟一位同事一块儿开发的开源组件,主要功能是经过比较轻量的方式与经常使用开源组件(jedis/mybatis/motan/dubbo/servlet)集成,收集并计算metrics,并支持输出到日志及转换成多种时序数据库兼容数据格式,配套的grafana dashboard友好的进行展现。项目当中原理文档齐全,且所有基于SPI设计的可扩展式架构,方便的开发新插件。另有一个基于docker-compose的独立demo项目能够快速启动一套demo示例查看效果github.com/zrbcool/pep…。若是你们以为有用的话,麻烦给个star,也欢迎你们参与开发,谢谢:)git
Motan的SPI与Dubbo的SPI相似,它在Java原生SPI思想的基础上作了优化,而且与Java原生SPI的使用方式很类似。github
介绍Java原生SPI的相关文章有不少,这里再也不赘述。下面主要介绍一下Motan中的SPI机制,先从使用提及。docker
下面以实现一个Filter的扩展为例,说说他咋用。数据库
Filter的做用是在 Provider
端接收请求或 Consumer
端发送请求时进行拦截,作一些特别的事情。下面从 Consumer
端的视角,作一个计算单次请求响应时间的统计需求。api
首先须要写一个类,实现 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
mybatis
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 机制。