dubbo源碼分析18 -- 服務監控

在分佈式服務當中監控服務的各項指標至關重要,而 dubbo 也提供了一個簡單的監控中心(Simple Monito)。Simple Monitor掛掉不會影響到Consumer和Provider之間的調用,所以用於生產環境不會有風險。 並且配置好了之後可以結合 admin 管理後臺使用,可以清晰的看到服務的訪問記錄、成功次數、失敗次數等…

Simple Monitor 採用磁盤存儲統計信息,請注意安裝機器的磁盤限制,如果要集羣,建議用mount共享磁盤。

1、監控中心

我們先來看一下 dubbo 監控中心的配置文件:

# 1
dubbo.container=log4j,spring,registry,jetty

# 2
dubbo.application.name=simple-monitor
dubbo.application.owner=dubbo
#dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=redis://127.0.0.1:6379
#dubbo.registry.address=dubbo://127.0.0.1:9090
dubbo.protocol.port=7070

# 3
dubbo.jetty.port=8080
dubbo.jetty.directory=${user.home}/monitor

# 4
dubbo.charts.directory=${dubbo.jetty.directory}/charts
dubbo.statistics.directory=${user.home}/monitor/statistics
dubbo.log4j.file=logs/dubbo-monitor-simple.log
dubbo.log4j.level=WARN

我把這個配置文件分成了 4 個部分:

  • dubbo Container SPI 配置,用於啓動 dubbo 服務,監控中心等
  • dubbo 服務配置,連接配置中心暴露配置在dubbo-monitor-simple.xml中的 MonitorService 以及引用 RegistryService。
  • Jetty 容器相關的配置,監控中心顯示頁面使用 Jetty 容器啓動。
  • dubbo monitor 顯示頁面持久化的數據,包括顯示展示圖、調用統一、dubbo 監控中心的日誌以及日誌級別

下面我們來分別分析一下配置文件中配置的 4 個部分:

1、Container

可以看到在配置文件中, dubbo 配置了 4 個容器(Container)。我們首先來看一下容器接口的定義:

@SPI("spring")
public interface Container {

    void start();

    void stop();

}

dubbo 定義的 SPI 接口 Container 很簡單,一個 start 方法用於容器的啓動還有一個 stop 方法用於容器的停止。在 dubbo 監控中心啓動的時候用調用 com.alibaba.dubbo.container.Main 中的 main 方法。在 main
方法裏面會依次啓動這 4 個容器對應類。下面我們來看一下配置的這 4 個容器對應的容器類。

log4j      ---      Log4jContainer
spring     ---      SpringContainer
registry   ---      RegistryContainer
jetty      ---      JettyContainer

Log4jContainer

Log4jContainer 用於配置 Log4j 日誌參數,如果配置了 dubbo.log4j.subdirectory 參數就會打印日誌

SpringContainer

SpringContainer 會加載classpath*:META-INF/spring/*.xml目錄下面的 spring 配置文件, 也就是dubbo-monitor-simple.xml

dubbo-monitor-simple.xml

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://code.alibabatech.com/schema/dubbo 
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
        <property name="location" value="classpath:dubbo.properties"/>
    </bean>

    <bean id="monitorService" class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService">
    </bean>

    <dubbo:application name="${dubbo.application.name}" owner="${dubbo.application.owner}"/>

    <dubbo:registry address="${dubbo.registry.address}"/>

    <dubbo:protocol name="dubbo" port="${dubbo.protocol.port}"/>

    <dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" delay="-1"/>

    <dubbo:reference id="registryService" interface="com.alibaba.dubbo.registry.RegistryService"/>

</beans>

它的作用與需要監控的註冊中心以註冊中心用 Spring 裏面的 ClassPathXmlApplicationContext 來啓動 dubbo 服務。暴露一個 MonitorService 的服務,用於監控數據採集以及監控數據查詢(後面會詳細分析)。並且引用註冊中心的服務 RegistryService,訂閱符合條件的已註冊數據,當有註冊數據變更時自動推送或者取消訂閱。並且提供一個靜態方法 getContext() 用於獲取 ClassPathXmlApplicationContext 來操作 Spring 容器裏面的 bean 對象。

RegistryContainer

它的類屬性包括服務提供者和服務的消費者分類,收集服務名稱,服務的url,服務提供方或者消費方的系統相關信息。並且提供了一個 getInstance 返回當前 RegistryContainer 的實例對象用於查詢這些屬性。下面我們來看一下 start 方法做了哪些事:

  • 通過SpringContainer獲取前面初始化的RegistryService,獲取到一個遠程代理服務
  • 構建訂閱註冊中心數據的URL,看可以看出下面的url是訂閱服務提供者和服務消費者的所有服務
  • 調註冊中心服務 registry.subscirbe(subscribeUrl,listener) 訂閱所有數據, NotifyListener在監控中心暴露爲回調服務,由註冊中心回調. 回調接口NotifyListener實現的功能主要是按服務提供者和服務的消費者分類,收集服務名稱,服務的url,服務提供方或者消費方的系統相關信息。 同時提供了一系列方法供註冊中心調用查詢。

JettyContainer

JettyContainer 容器用於啓動一個內置的 Jetty web 服務.將 dubbo 自定義的前置控制器 PageServlet 及這個Servlet 的訪問映射配置到 jetty 容器當中用於分發請求。把本地文件目錄設置到 ResourceFilter 中,並設置這個filter的訪問映射到 Jetty 中。ResourceFilter 主要是讀取本地保存的 JFreeChart 繪製的圖片到瀏覽器中去。

PageServlet 會在 init 方法中初始化對應菜單 URL 與 相應的處理類。這些處理類都是實現了 PageHandler 這個 SPI 接口。這些 key 被 HomePageHandler 用來生成主頁以及各個頁面的 URI。而對應的 PageHandler 用來處理頁面分頁請求邏輯。

在這裏插入圖片描述

2、獲取數據

如果想在 dubbo 的監控中心獲取數據必須在 dubbo 服務的 provider 與 consumer 中加入以下代碼:

<dubbo:monitor protocol="registry" />

這樣它就會激活 dubbo 服務中的 provider 與 consumer 中的 Filter 擴展 MonitorFilter 了。在 MonitorFilter 裏面會引用到 dubbo 監控服務通過 SpringContainer 暴露出來的 MonitorService 服務。

Monitor monitor = monitorFactory.getMonitor(url);

過程是通過 SPI 機制獲取 MonitorFactory 的實現類 DubboMonitorFactroy,由 DubboMonitorFactroy 創建 MonitorService 接口的實例 DubboMonitor. 主要代碼如下:

        Invoker<MonitorService> monitorInvoker = protocol.refer(MonitorService.class, url);
        MonitorService monitorService = proxyFactory.getProxy(monitorInvoker);
        return new DubboMonitor(monitorInvoker, monitorService);

下面我們來看一下 MonitorService 接口的定義:

public interface MonitorService {

    /**
     * 監控數據採集.
     * 1. 支持調用次數統計:count://host/interface?application=foo&method=foo&provider=10.20.153.11:20880&success=12&failure=2&elapsed=135423423
     * 1.1 host,application,interface,group,version,method 記錄監控來源主機,應用,接口,方法信息。
     * 1.2 如果是消費者發送的數據,加上provider地址參數,反之,加上來源consumer地址參數。
     * 1.3 success,faulure,elapsed 記錄距上次採集,調用的成功次數,失敗次數,成功調用總耗時,平均時間將用總耗時除以成功次數。
     *
     * @param statistics
     */
    void collect(URL statistics);

    /**
     * 監控數據查詢. 
     * 1. 支持按天查詢:count://host/interface?application=foo&method=foo&side=provider&view=chart&date=2012-07-03
     * 1.1 host,application,interface,group,version,method 查詢主機,應用,接口,方法的匹配條件,缺失的條件的表示全部,host用0.0.0.0表示全部。
     * 1.2 side=consumer,provider 查詢由調用的哪一端採集的數據,缺省爲都查詢。
     * 1.3 缺省爲view=summary,返回全天彙總信息,支持view=chart表示返回全天趨勢圖表圖片的URL地址,可以進接嵌入其它系統的頁面上展示。
     * 1.4 date=2012-07-03 指定查詢數據的日期,缺省爲當天。
     *
     * @param query
     * @return statistics
     */
    List<URL> lookup(URL query);

}

它其實就是使用的是 Dubbo 監控中心暴露出來的 SimpleMonitorService。它的 collect 方法被遠程調用後將數據url(傳過來的url包含監控需要的數據)保存到一個阻塞隊列中BlockingQueue 當中並且啓動兩個定時任務。

一個定時任務用於把統計數據寫在如下的文件中:

                String filename = ${user.home}/monitor/statistics
                        + "/" + day
                        + "/" + statistics.getServiceInterface()
                        + "/" + statistics.getParameter(METHOD)
                        + "/" + consumer
                        + "/" + provider
                        + "/" + type + "." + key;

文件格式如下:

在這裏插入圖片描述

一個定時任務利用 JFreeeChart 繪製圖表,保存路徑文件夾爲:

${user.home}\monitor\charts\date\interfaceName\methodName
  • 包含成功圖片(success.png)、耗時圖片(elapsed.png)

在這裏插入圖片描述

  • 請求平均耗時、響應平均耗時

在這裏插入圖片描述

然後我們再回到如果把數據發送到 SimpleMonitorService,也就是激活的 provider 與 consumer 端的 MonitorFilter.

主要邏輯在 com.alibaba.dubbo.monitor.support.MonitorFilter#collect

    // 信息採集
    private void collect(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
        try {
            // ---- 服務信息獲取 ----
            long elapsed = System.currentTimeMillis() - start; // 計算調用耗時
            int concurrent = getConcurrent(invoker, invocation).get(); // 當前併發數
            String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
            String service = invoker.getInterface().getName(); // 獲取服務名稱
            String method = RpcUtils.getMethodName(invocation); // 獲取方法名
            URL url = invoker.getUrl().getUrlParameter(Constants.MONITOR_KEY);
            Monitor monitor = monitorFactory.getMonitor(url);
            if (monitor == null) {
                return;
            }
            int localPort;
            String remoteKey;
            String remoteValue;
            if (Constants.CONSUMER_SIDE.equals(invoker.getUrl().getParameter(Constants.SIDE_KEY))) {
                // ---- 服務消費方監控 ----
                localPort = 0;
                remoteKey = MonitorService.PROVIDER;
                remoteValue = invoker.getUrl().getAddress();
            } else {
                // ---- 服務提供方監控 ----
                localPort = invoker.getUrl().getPort();
                remoteKey = MonitorService.CONSUMER;
                remoteValue = remoteHost;
            }
            String input = "", output = "";
            if (invocation.getAttachment(Constants.INPUT_KEY) != null) {
                input = invocation.getAttachment(Constants.INPUT_KEY);
            }
            if (result != null && result.getAttachment(Constants.OUTPUT_KEY) != null) {
                output = result.getAttachment(Constants.OUTPUT_KEY);
            }
            monitor.collect(new URL(Constants.COUNT_PROTOCOL,
                    NetUtils.getLocalHost(), localPort,
                    service + "/" + method,
                    MonitorService.APPLICATION, application,
                    MonitorService.INTERFACE, service,
                    MonitorService.METHOD, method,
                    remoteKey, remoteValue,
                    error ? MonitorService.FAILURE : MonitorService.SUCCESS, "1",
                    MonitorService.ELAPSED, String.valueOf(elapsed),
                    MonitorService.CONCURRENT, String.valueOf(concurrent),
                    Constants.INPUT_KEY, input,
                    Constants.OUTPUT_KEY, output));
        } catch (Throwable t) {
            logger.error("Failed to monitor count service " + invoker.getUrl() + ", cause: " + t.getMessage(), t);
        }
    }

    // 獲取併發計數器
    private AtomicInteger getConcurrent(Invoker<?> invoker, Invocation invocation) {
        String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
        AtomicInteger concurrent = concurrents.get(key);
        if (concurrent == null) {
            concurrents.putIfAbsent(key, new AtomicInteger());
            concurrent = concurrents.get(key);
        }
        return concurrent;
    }

代碼裏面的註釋已經很清晰了,就不需要過多的說明了。需要注意的是 DubboMonitor 是調用監控中心的服務的封裝。之所以沒有直接調監控中心而是通過DubboMonitor調用,是因爲監控是附加功能,不應該影響主鏈路更不應該損害主鏈路的新能,DubboMonitor採集到數據後通過任務定時調用監控中心服務將數據提交到監控中心。

3、Simple Monitor 安裝

從官方網站下載 dubbo monitor 打包並解壓

git clone https://github.com/apache/incubator-dubbo-ops
cd incubator-dubbo-ops && mvn package
cd dubbo-monitor-simple/target && tar xvf dubbo-monitor-simple-2.0.0-assembly.tar.gz
cd dubbo-monitor-simple-2.0.0

修改監控中心配置文件conf/dubbo.properties 把註冊中心修改爲你的 dubbo 服務真實的註冊中心。

vi conf/dubbo.properties

dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
#dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=redis://127.0.0.1:6379
#dubbo.registry.address=dubbo://127.0.0.1:9090
dubbo.protocol.port=7070
dubbo.jetty.port=8080
dubbo.jetty.directory=${user.home}/monitor
dubbo.charts.directory=${dubbo.jetty.directory}/charts
dubbo.statistics.directory=${user.home}/monitor/statistics
dubbo.log4j.file=logs/dubbo-monitor-simple.log
dubbo.log4j.level=WARN

這裏我是在本地演示,所以服務是使用 dubbo 的 demo 服務,然後在本地啓動的 zookeeper 做爲註冊中心。

監控中心命令:

# 啓動:
./assembly.bin/start.sh
# 停止:
./assembly.bin/stop.sh
# 重啓:
./assembly.bin/restart.sh
# 調試:
./assembly.bin/start.sh debug
# 系統狀態:
./assembly.bin/dump.sh

可以通過http://127.0.0.1:8080進行訪問:
在這裏插入圖片描述

4、頁面菜單

dubbo monitor 主要展現了以下幾個頁面菜單:Home、Applications、Services、Hosts、Registries、Servers、Status、Log、System 十個頁面。下面我們簡單的爲這十個頁面截圖一下。

4.1 Home

在這裏插入圖片描述

4.2 Applications

在這裏插入圖片描述

4.3 Services

在這裏插入圖片描述

還可以點擊具體服務的 Statistics 查看統計信息:

在這裏插入圖片描述

4.4 Hosts

在這裏插入圖片描述

4. 5 Registries

在這裏插入圖片描述

4. 6 Servers

在這裏插入圖片描述

4.7 Status

在這裏插入圖片描述

4.8 Log

在這裏插入圖片描述

4.9 System

在這裏插入圖片描述

參考文章:

  • http://dubbo.apache.org/books/dubbo-admin-book/install/simple-monitor-center.html
  • https://blog.csdn.net/quhongwei_zhanqiu/article/details/41896667
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章