Dubbo發布代碼中,自帶了一個簡易的監控中心實現。對于一般的小業務這個監控中心應該能夠滿足需求,對于那些大業務量的大公司一般都會有自己的監控中心,更加豐富的功能如常用的報警短信通知等等。這章講解分析使得讀者能夠了解一般的監控中心實現,也使得有自己接入監控中心需求的大概知道如何集成自己的監控中心實現。下面我們就以dubbo自帶的監控中心開始講解。
?
監控中心
1.? 監控中心啟動,我們先看下dubbo的屬性文件
dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
dubbo.registry.address=zookeeper://127.0.0.1:2181
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
?
相比于provider, consumer的啟動注冊中心多了registry, jetty容器啟動
它們都是基于dubbo的spi擴展機制的。
SpringContainer容器啟動就是加載classpath*:META-INF/spring/ *.xml spring的配置文件
<beanid="monitorService"class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService">
???????<propertyname="statisticsDirectory"value="${dubbo.statistics.directory}"/>
???????<propertyname="chartsDirectory"value="${dubbo.charts.directory}"/>
</bean>
<dubbo:applicationname="${dubbo.application.name}"owner="${dubbo.application.owner}"/>
<dubbo:registryaddress="${dubbo.registry.address}"/>
<dubbo:protocolname="dubbo"port="${dubbo.protocol.port}"/>
<dubbo:serviceinterface="com.alibaba.dubbo.monitor.MonitorService"ref="monitorService"delay="-1"/>
<dubbo:referenceid="registryService"interface="com.alibaba.dubbo.registry.RegistryService"/>
?
2. SimpleMonitorService
監控中心配置了監控服務的實現SimpleMonitorService, 并且作為一個普通的dubbo服務暴露到注冊中心,供服務的提供者和服務的消費方調用,將服務提供者和服務的消費方的調用數據保存到監控中心。
監控服務的接口定義
public?interface?MonitorService {
????/**
???? *?監控數據采集.
???? * 1.?支持調用次數統計:count://host/interface?application=foo&method=foo&provider=10.20.153.11:20880&success=12&failure=2&elapsed=135423423
???? * 1.1host,application,interface,group,version,method記錄監控來源主機,應用,接口,方法信息。
???? * 1.2?如果是消費者發送的數據,加上provider地址參數,反之,加上來源consumer地址參數。
???? * 1.3 success,faulure,elapsed?記錄距上次采集,調用的成功次數,失敗次數,成功調用總耗時,平均時間將用總耗時除以成功次數。
???? *
???? *?@paramstatistics
???? */
????void?collect(URLstatistics);
?
????/**
???? *?監控數據查詢.?
???? * 1.?支持按天查詢:count://host/interface?application=foo&method=foo&side=provider&view=chart&date=2012-07-03
???? * 1.1host,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
???? *?@returnstatistics
???? */
??? List<URL> lookup(URL query);
}
?
注: lookup方面可能在開源過程中依賴了阿里的什么系統,并沒有具體的實現,如果使用著需要此功能則需要根據接口定義自己實現
?
MonitorService的dubbo默認實現SimpleMonitorService
Collect方法被遠程調用后將數據url(傳過來的url包含監控需要的數據)保存到一個阻塞隊列中BlockingQueue<URL>
啟動定時任務將統計日志記錄到本地,
String filename =${user.home}/monitor/statistics
??????????????????????? +?"/"?+ day
??????????????????????? +?"/"?+statistics.getServiceInterface()
??????????????????????? +?"/"?+statistics.getParameter(METHOD)
??????????????????????? +?"/"?+ consumer
??????????????????????? +?"/"?+ provider
??????????????????????? +?"/"?+ type +?"."?+ key
這是文件在本地存儲的格式
文件內容如圖保存時間方法消費耗時
?
3.?起定時任務利用JFreeeChart繪制圖表,保存路徑
${user.home}\monitor\charts\date\interfaceName\methodName
?
?
?
產生監控數據
注冊中心暴露了MonitorService服務,它是被誰調用的呢,監控中心的數據是從哪里來呢,下面我們看下服務提供方與服務的消費方式如何介入監控中心的。
在服務的提供方跟消費方的dubbo配置加入如下配置
通過注冊中心<dubbo:monitor protocol="registry" />
或者直連??<dubbo:monitor address="127.0.0.1:7070" />
在構建服務的調用鏈的時候有如上基于監控的擴展,下面我們就來看下這個類
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
//此過濾器在服務的提供方,服務的消費方應用中被激活,也就是起作用
public class MonitorFilter implements Filter {
private MonitorFactory monitorFactory;
??? public Result invoke(Invoker<?>invoker, Invocation invocation) throws RpcException {
??? if(invoker.getUrl().hasParameter(Constants.MONITOR_KEY)) {
???????? //有注監控中心處理
1.??獲取invoker的調用上下文
2.??記錄起始時間戳
3.??并發計數加一
try {
4.??調用調用鏈的下一步
5.??采集調用信息
} finally {
6.??并發計數減一
}
??? } else {
???????? //沒有配置監控中心,直接往下調用
???? ??? return invoker.inovke(invocation);
?? }
}
?
上面第5點信息采集
1.?計算調用耗時
2.?獲取并發數
3.?獲取服務名稱
4.?獲取方法名
5.?判斷是服務消費方監控還是服務提供方監控
6.?由工廠類monitorFactory.getMonitor(監控url),獲取DubboMonitor對象,
構建調用監控中心服務的的Url, url中包括了監控中心所需的監控信息
monitor.collect(newURL(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));
?
DubboMonitor是調用監控中心的服務的封裝,之所以沒有直接調監控中心而是通過DubboMonitor調用,是因為監控是附加功能,不應該影響主鏈路更不應該損害主鏈路的新能,DubboMonitor采集到數據后通過任務定時調用監控中心服務將數據提交到監控中心。
?
RegistryContainer
監控中心refer引用了注冊中心暴露的RegistryService服務,主要是被下面的RegistryContainer使用的。
?
RegistryContainer主要是到注冊中心收集服務,分組,版本信息,并注冊回調當注冊中心數據發生變化的時候更新到監控中心
下面看下RegistryContainer的start方法流程:
1. 通過SpringContainer獲取前面初始化的RegistryService, 得到其實是對注冊中心的一個遠程代理服務
2. 構建訂閱注冊中心數據的URL,看可以看出下面的url是訂閱服務提供者和服務消費者的所有服務
subscribeUrl =?newURL(Constants.ADMIN_PROTOCOL, NetUtils.getLocalHost(), 0,"",
??? ????????????Constants.INTERFACE_KEY,Constants.ANY_VALUE,//所有服務
??????????????? Constants.GROUP_KEY,Constants.ANY_VALUE,//所有分組
??????????????? Constants.VERSION_KEY, Constants.ANY_VALUE,//所有版本
??????????????? Constants.CLASSIFIER_KEY,Constants.ANY_VALUE,//所有分類
Constants.CATEGORY_KEY,Constants.PROVIDERS_CATEGORY +?","? + Constants.CONSUMERS_CATEGORY,//服務的提供者和服務的消費者
??? ?????????????Constants.CHECK_KEY,String.valueOf(false));//不檢查
3.? 調注冊中心服務registry.subscirbe(subscribeUrl,listener)訂閱所有數據, NotifyListener在監控中心暴露為回調服務,由注冊中心回調
回調接口NotifyListener實現的功能主要是按服務提供者和服務的消費者分類,收集服務名稱,服務的url,服務提供方或者消費方的系統相關信息。 同時提供了一系列方法供注冊中心調用查詢。
?
JettyContainer
監控中心將采集到的信息通過內置jetty來展現給用戶,這里為了不依賴與jsp, velocity,freemarker等一些編寫web應用的技術,采用在servlet中將html,css,js打印出來
JettyContainer的start方法啟動了內置的jettyweb容器
將監控中心訪問的本地文件目錄設置到ResourceFilter中,并設置這個filter的訪問映射到jetty中?? , ResourceFilter主要是讀取本地保存的JFreeChart繪制的圖片到瀏覽器中去。
將監控中心的前置控制器PageServlet, 以及這個servlet的訪問映射配置到jetty中。之所以叫PageServet為前置控制器,就像其他的mvc框架一樣用來分發具體的業務類
?
PageServet的init初始化方法在web容器啟動的時候加載所有的頁面處理器PageHandler, 用來根據不同的請求生成不同的頁面,前面說過這里頁面html都是通過java代碼打印出來的。
PageServet的init方法加載所有PageHandler時會判斷PageHandler上是否有@Menu注解,將有注解的PageHandler加入集合,以被HomePageHandl er用來生成主頁以及各個頁面的uri
PageServet的doGet, doPost接收瀏覽器請求,請求以xx.hml形式,xx就是PageHandler擴展的key,找到對應的PageHandler繪制對應的頁面返回給瀏覽器。
?
?
@Menu(name = "Home",desc = "Home page.", order = Integer.MIN_VALUE)
//有注解?name跟desc屬性都是在頁面中展示給用戶看的
public class HomePageHandlerimplements PageHandler {
??? public Page handle(URL url) {
??????? List<List<String>> rows =new ArrayList<List<String>>();
??????? for (PageHandler handler :PageServlet.getInstance().getMenus()) {
??????????? String uri =ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(handler);?//這個uri其實就是PageHandler擴展配置的key,頁面中用它來請求選擇具體的handler繪制?? ??//出具體的page
??????????? Menu menu =handler.getClass().getAnnotation(Menu.class);
??????????? List<String> row = newArrayList<String>();
??????????? row.add("<ahref=\"" + uri + ".html\">" + menu.name() +"</a>");
??????????? row.add(menu.desc());
??????????? rows.add(row);
??????? }
??????? return new Page("Home","Menus",? new String[]{"Menu Name", "Menu Desc"}, rows);??//一個Page實體就是一個頁面,這里包含所有主要HomePage的頁面內容
??? }
}
?
PageHandler的在com.alibaba.dubbo.container.page.PageHandler文件中的擴展配置
index=com.alibaba.dubbo.container.page.pages.HomePageHandler
providers=com.alibaba.dubbo.monitor.simple.pages.ProvidersPageHandler
consumers=com.alibaba.dubbo.monitor.simple.pages.ConsumersPageHandler
。。。。
下面截圖看下dubbo大概提供了哪些擴展
?下面截幾張圖看看監控中心的頁面。