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的屬性、MonitorConfigMap<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動態重寫MonitorConfigclass

在應用啟動時,使用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存入MonitorConfigMap<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秒。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM