DUBBO監控,設置接口調用數據的上報周期
dubbo是目前比較好用的,用來實現soa架構的一個工具,dubbo的用法和好處,我們這里略過,今天我們來討論跟監控有關的話題。
大家大知道,在確保系統的穩定道路上,系統監控是必不可少的,只有實時監控系統中接口的調用情況,並定期匯總統計數據,才能知道系統是否到了瓶頸,以及是否有接口異常調用情況。
dubbo已有的監控方案
dubbo中自帶了dubbo-monitor模塊,其作用簡單來說就是:
-
根據配置判斷是否啟用
MonitorFilter
過濾器
當dubbo的xml配置文件中配置了<dubbo:monitor xxx/>
,比如:<dubbo:monitor protocol="registry" />
或<dubbo:monitor protocol="dubbo" address="dubbo://127.0.0.1:30004/MonitorService" />
等等
-
根據protocol配置,判斷獲取
MonitorService
接口方式以及獲取接口地址
如果是registry
根據<dubbo:registry xxx />
配置獲取,如果是dubbo
直接使用address屬性配置的地址
-
根據protocol配置,判斷使用哪個
MonitorFactory
如果是registry
,則根據最后獲取到的具體通信協議來決定;
如果是dubbo
,使用默認工廠類:DubboMonitorFactroy。見dubbo默認配置文件:
META-INF/dubbo/internal/com.alibaba.dubbo.monitor.MonitorFactory
dubbo=com.alibaba.dubbo.monitor.dubbo.DubboMonitorFactroy
這里我們可以自定義協議,比如myDubbo
,然后在項目中創建一個文件:
META-INF/dubbo/com.alibaba.dubbo.monitor.MonitorFactory
myDubbo=org.rembau.dubboTest.MyDubboMonitorFactory
這時項目啟動時,如果發現協議為myDubbo,會使用MyDubboMonitorFactory
執行相關操作。
問題:這里暴露了監控配置的一個問題,就是我們不能為dubbo協議重新定義一個MonitorFactory
,所以連帶的不能重新實現Monitor
。
-
使用
MonitorFilter
過濾器處理每一個接口調用
如果我們用的dubbo協議來連接MonitorService
服務,則是使用DubboMonitor
實例來處理接口調用數據,該類實現了對調用數據的簡單匯總,並每60秒調用MonitorService
服務的collect方法,把數據發送出來。
-
使用dubbo-monitor-simple接收數據
dubbo-monitor-simple是dubbo提供的簡單監控中心,可以用來顯示接口暴露,注冊情況,也可以看接口的調用明細,調用時間等。
還有其他一些比較好的工具比如Dubbo-Monitor等
針對已有方案的改進
寫到這里才是本文的重點,我們絕大部分使用者都是使用dubbo協議進行通信,所以也就是使用DubboMonitor
發送數據,這已經完全夠用了。但是DubboMonitor
是每分鍾上報一次,並且每個應用中,與之有關的所有接口都會產生一條數據,生產環境中,數據量還是挺大的。如果上報的時間間隔可以設置為10分鍾或者30分鍾,數據量也會相應減少10倍、30倍,如果我們自己匯聚還需要分布式服務對數據的一致性處理,會把事情變復雜。
-
查看
DubboMonitor
源碼:
DubboMonitor
的構造方法:
this.monitorInterval = (long)monitorInvoker.getUrl().getPositiveParameter("interval", '60000');
this.sendFuture = this.scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
DubboMonitor.this.send();
} catch (Throwable var2) {
DubboMonitor.logger.error("Unexpected error occur at send statistic, cause: " + var2.getMessage(), var2);
}
}
}, this.monitorInterval, this.monitorInterval, TimeUnit.MILLISECONDS);
其中.getPositiveParameter("interval", '60000')
方法大概意思是,在URL對象實例中找interval參數的值。那URL的參數會從什么地方獲取呢?
-
MonitorConfig
的屬性、MonitorConfig
中Map<String, String> parameters
屬性的key-value
最終獲取的URL是根據MonitorConfig
得來的,查看AbstractInterfaceConfig
代碼:
protected URL loadMonitor(URL registryURL) {
if (monitor == null) {
String monitorAddress = ConfigUtils.getProperty("dubbo.monitor.address");
String monitorProtocol = ConfigUtils.getProperty("dubbo.monitor.protocol");
if (monitorAddress != null && monitorAddress.length() > 0
|| monitorProtocol != null && monitorProtocol.length() > 0) {
monitor = new MonitorConfig();
} else {
return null;
}
}
appendProperties(monitor);
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.INTERFACE_KEY, MonitorService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, monitor);
String address = monitor.getAddress();
String sysaddress = System.getProperty("dubbo.monitor.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (ConfigUtils.isNotEmpty(address)) {
if (! map.containsKey(Constants.PROTOCOL_KEY)) {
if (ExtensionLoader.getExtensionLoader(MonitorFactory.class).hasExtension("logstat")) {
map.put(Constants.PROTOCOL_KEY, "logstat");
} else {
map.put(Constants.PROTOCOL_KEY, "dubbo");
}
}
return UrlUtils.parseURL(address, map);
} else if (Constants.REGISTRY_PROTOCOL.equals(monitor.getProtocol()) && registryURL != null) {
return registryURL.setProtocol("dubbo").addParameter(Constants.PROTOCOL_KEY, "registry").addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map));
}
return null;
}
-
方案1 使用javassist動態重寫
MonitorConfig
class
在應用啟動時,使用javassist給MonitorConfig
添加interval字段,並設置默認值,DubboMonitor
初始化時會獲取到該字段的值,並使用這個值當作發送數據的周期。
如果應用使用了spring框架,則下面代碼有關在加載spring配置文件之前執行。如果應用是web應用則處理起來更麻煩,需要重寫org.springframework.web.context.ContextLoaderListener
代碼如下:
ClassPool pool = ClassPool.getDefault();
try {
CtClass cc = pool.get("com.alibaba.dubbo.config.MonitorConfig");
// 增加屬性,這里僅僅是增加屬性字段
CtField ctField = new CtField(CtClass.intType, "interval", cc);
ctField.setModifiers(Modifier.PUBLIC);
cc.addField(ctField, "10000");//設置10秒周期
CtMethod ctMethod = new CtMethod(CtClass.intType, "getInterval", null, cc);
ctMethod.setBody("return interval;");
ctMethod.setModifiers(Modifier.PUBLIC);
cc.addMethod(ctMethod);
cc.toClass();
} catch (NotFoundException | CannotCompileException e) {
logger.error("", e);
}
-
方案2 使用
<dubbo:registry xxx />
配置
分析源碼,可以知道,如果配置<dubbo:registry interval="10000" xxx />
,dubbo解析文件時,會把interval="10000"當作一個key-value存入MonitorConfig
中Map<String, String> parameters
DubboBeanDefinitionParser
中parse方法(摘選)
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node node = attributes.item(i);
String name = node.getLocalName();
if (! props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
parameters.put(name, new TypedStringValue(value, String.class));
}
}
if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
但是有個問題,如果我給<dubbo:registry xxx>
加上interval屬性,應用啟動時會報錯。我們知道原因是dubbo的xsd文件沒有放開這個限制,我們只要修改dubbo.xsd文件就可以了,但是dubbo.xsd是在jar包里,所以我們改不了。
我們可以自己寫xsd配置,重命名為dubbo-youyue.xsd:
文件頭改為:
<xsd:schema xmlns="http://code.alibabatech.com/schema/youyue/dubbo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://code.alibabatech.com/schema/youyue/dubbo">
monitorType節點添加:
<xsd:attribute name="interval" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The monitor interval. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
然后把dubbo.xml中關聯的dubo.xsd改成我們自己寫的xsd,如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
改成
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/youyue/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://code.alibabatech.com/schema/youyue/dubbo
http://code.alibabatech.com/schema/youyue/dubbo/dubbo.xsd">
添加 spring.schemas 文件,用來使用本地文件取代遠程文件
http://code.alibabatech.com/schema/youyue/dubbo/dubbo.xsd=META-INF/dubbo-youyue.xsd
添加 spring.handlers 文件,用來標記分析dubbo.xml中<dubbo:xx xx>
節點的分析處理類
至此,配置好<dubbo:registry interval="10000" xxx />
啟動應用,可以看到上報周期由60秒變成了10秒。