推薦一款一站式性能監控工具(開源項目)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 機制。