目录
- DUBBO监控,设置接口调用数据的上报周期
- dubbo已有的监控方案
- 针对已有方案的改进
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>
节点的分析处理类
http://code.alibabatech.com/schema/youyue/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
至此,配置好<dubbo:registry interval="10000" xxx />
启动应用,可以看到上报周期由60秒变成了10秒。