Dubbo發(fā)布代碼中,自帶了一個簡易的監(jiān)控中心實現(xiàn)。對于一般的小業(yè)務這個監(jiān)控中心應該能夠滿足需求,對于那些大業(yè)務量的大公司一般都會有自己的監(jiān)控中心,更加豐富的功能如常用的報警短信通知等等。這章講解分析使得讀者能夠了解一般的監(jiān)控中心實現(xiàn),也使得有自己接入監(jiān)控中心需求的大概知道如何集成自己的監(jiān)控中心實現(xiàn)。下面我們就以dubbo自帶的監(jiān)控中心開始講解。
監(jiān)控中心1. 監(jiān)控中心啟動,我們先看下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 監(jiān)控中心配置了監(jiān)控服務的實現(xiàn)SimpleMonitorService, 并且作為一個普通的dubbo服務暴露到注冊中心,供服務的提供者和服務的消費方調(diào)用,將服務提供者和服務的消費方的調(diào)用數(shù)據(jù)保存到監(jiān)控中心。 監(jiān)控服務的接口定義 public interface MonitorService { /** * 監(jiān)控數(shù)據(jù)采集. * 1. 支持調(diào)用次數(shù)統(tǒng)計: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記錄監(jiān)控來源主機,應用,接口,方法信息。 * 1.2 如果是消費者發(fā)送的數(shù)據(jù),加上provider地址參數(shù),反之,加上來源consumer地址參數(shù)。 * 1.3 success,faulure,elapsed 記錄距上次采集,調(diào)用的成功次數(shù),失敗次數(shù),成功調(diào)用總耗時,平均時間將用總耗時除以成功次數(shù)。 * * @paramstatistics */ void collect(URLstatistics);
/** * 監(jiān)控數(shù)據(jù)查詢. * 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 查詢由調(diào)用的哪一端采集的數(shù)據(jù),缺省為都查詢。 * 1.3 缺省為view=summary,返回全天匯總信息,支持view=chart表示返回全天趨勢圖表圖片的URL地址,可以進接嵌入其它系統(tǒng)的頁面上展示。 * 1.4 date=2012-07-03指定查詢數(shù)據(jù)的日期,缺省為當天。 * * @param query * @returnstatistics */ List<URL> lookup(URL query); }
注: lookup方面可能在開源過程中依賴了阿里的什么系統(tǒng),并沒有具體的實現(xiàn),如果使用著需要此功能則需要根據(jù)接口定義自己實現(xiàn)
MonitorService的dubbo默認實現(xiàn)SimpleMonitorService Collect方法被遠程調(diào)用后將數(shù)據(jù)url(傳過來的url包含監(jiān)控需要的數(shù)據(jù))保存到一個阻塞隊列中BlockingQueue<URL> 啟動定時任務將統(tǒng)計日志記錄到本地, String filename =${user.home}/monitor/statistics + "/" + day + "/" +statistics.getServiceInterface() + "/" +statistics.getParameter(METHOD) + "/" + consumer + "/" + provider + "/" + type + "." + key 這是文件在本地存儲的格式 文件內(nèi)容如圖保存時間方法消費耗時
3. 起定時任務利用JFreeeChart繪制圖表,保存路徑 ${user.home}\monitor\charts\date\interfaceName\methodName
產(chǎn)生監(jiān)控數(shù)據(jù)注冊中心暴露了MonitorService服務,它是被誰調(diào)用的呢,監(jiān)控中心的數(shù)據(jù)是從哪里來呢,下面我們看下服務提供方與服務的消費方式如何介入監(jiān)控中心的。 在服務的提供方跟消費方的dubbo配置加入如下配置 通過注冊中心<dubbo:monitor protocol="registry" /> 或者直連 <dubbo:monitor address="127.0.0.1:7070" /> 在構建服務的調(diào)用鏈的時候有如上基于監(jiān)控的擴展,下面我們就來看下這個類 @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)) { //有注監(jiān)控中心處理 1. 獲取invoker的調(diào)用上下文 2. 記錄起始時間戳 3. 并發(fā)計數(shù)加一 try { 4. 調(diào)用調(diào)用鏈的下一步 5. 采集調(diào)用信息 } finally { 6. 并發(fā)計數(shù)減一 } } else { //沒有配置監(jiān)控中心,直接往下調(diào)用 return invoker.inovke(invocation); } }
上面第5點信息采集 1. 計算調(diào)用耗時 2. 獲取并發(fā)數(shù) 3. 獲取服務名稱 4. 獲取方法名 5. 判斷是服務消費方監(jiān)控還是服務提供方監(jiān)控 6. 由工廠類monitorFactory.getMonitor(監(jiān)控url),獲取DubboMonitor對象, 構建調(diào)用監(jiān)控中心服務的的Url, url中包括了監(jiān)控中心所需的監(jiān)控信息 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是調(diào)用監(jiān)控中心的服務的封裝,之所以沒有直接調(diào)監(jiān)控中心而是通過DubboMonitor調(diào)用,是因為監(jiān)控是附加功能,不應該影響主鏈路更不應該損害主鏈路的新能,DubboMonitor采集到數(shù)據(jù)后通過任務定時調(diào)用監(jiān)控中心服務將數(shù)據(jù)提交到監(jiān)控中心。
RegistryContainer監(jiān)控中心refer引用了注冊中心暴露的RegistryService服務,主要是被下面的RegistryContainer使用的。
RegistryContainer主要是到注冊中心收集服務,分組,版本信息,并注冊回調(diào)當注冊中心數(shù)據(jù)發(fā)生變化的時候更新到監(jiān)控中心 下面看下RegistryContainer的start方法流程: 1. 通過SpringContainer獲取前面初始化的RegistryService, 得到其實是對注冊中心的一個遠程代理服務 2. 構建訂閱注冊中心數(shù)據(jù)的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. 調(diào)注冊中心服務registry.subscirbe(subscribeUrl,listener)訂閱所有數(shù)據(jù), NotifyListener在監(jiān)控中心暴露為回調(diào)服務,由注冊中心回調(diào) 回調(diào)接口NotifyListener實現(xiàn)的功能主要是按服務提供者和服務的消費者分類,收集服務名稱,服務的url,服務提供方或者消費方的系統(tǒng)相關信息。 同時提供了一系列方法供注冊中心調(diào)用查詢。
JettyContainer監(jiān)控中心將采集到的信息通過內(nèi)置jetty來展現(xiàn)給用戶,這里為了不依賴與jsp, velocity,freemarker等一些編寫web應用的技術,采用在servlet中將html,css,js打印出來 JettyContainer的start方法啟動了內(nèi)置的jettyweb容器 將監(jiān)控中心訪問的本地文件目錄設置到ResourceFilter中,并設置這個filter的訪問映射到jetty中 , ResourceFilter主要是讀取本地保存的JFreeChart繪制的圖片到瀏覽器中去。 將監(jiān)控中心的前置控制器PageServlet, 以及這個servlet的訪問映射配置到jetty中。之所以叫PageServet為前置控制器,就像其他的mvc框架一樣用來分發(fā)具體的業(yè)務類
PageServet的init初始化方法在web容器啟動的時候加載所有的頁面處理器PageHandler, 用來根據(jù)不同的請求生成不同的頁面,前面說過這里頁面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的頁面內(nèi)容 } }
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大概提供了哪些擴展 下面截幾張圖看看監(jiān)控中心的頁面。
|
|