這裏的監控中心以dubbo-ops\dubbo-monitor-simple項目說
總的來講是監控中心啓動一個sevlet容器,經過web頁面向用戶多維度的展現dubbo服務信息。以下圖css
從頁面結構來講,圖中紅框框住的幾個部分,第一行是菜單,第二上是導航,第三行是表格title,最後一部分是表格。咱們先從服務服務啓動類開始html
public class MonitorStarter { public static void main(String[] args) { //經過main方法啓動 System.setProperty(Constants.DUBBO_PROPERTIES_KEY, "conf/dubbo.properties"); Main.main(args); } }
再看下Main 的main方法java
public static void main(String[] args) { try { //經過dubbo.container 獲取要啓動的容器名,多個以逗號分割 if (args == null || args.length == 0) { String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); args = Constants.COMMA_SPLIT_PATTERN.split(config); } //經過spi 加裝容器實例 放入list final List<Container> containers = new ArrayList<Container>(); for (int i = 0; i < args.length; i++) { containers.add(loader.getExtension(args[i])); } logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce."); //優雅停機的回調設置 if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { for (Container container : containers) { try { container.stop(); logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!"); } catch (Throwable t) { logger.error(t.getMessage(), t); } try { LOCK.lock();//重入鎖 STOP.signal();//釋放信號量 } finally { //釋放鎖 LOCK.unlock(); } } } }); } //分別調用容器的start方法 啓動指定的容器。 for (Container container : containers) { container.start(); logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); } System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!"); } catch (RuntimeException e) { e.printStackTrace(); logger.error(e.getMessage(), e); System.exit(1); } try { LOCK.lock();//獲取鎖 STOP.await();//等待信號量 } catch (InterruptedException e) { //不經過鉤子函數停機,記錄異常 logger.warn("Dubbo service server stopped, interrupted by other thread!", e); } finally { //釋放鎖 LOCK.unlock(); } }
在dubbo.properties 文件中配置項web
dubbo.container=log4j,spring,registry,jetty
經過spi相關文件找到以下實現spring
spring=com.alibaba.dubbo.container.spring.SpringContainer log4j=com.alibaba.dubbo.container.log4j.Log4jContainer registry=com.alibaba.dubbo.monitor.simple.container.RegistryContainer jetty=com.alibaba.dubbo.monitor.simple.container.JettyContainer
這裏重點解析下面spring,registry,jetty三種實現。具體從分析他們的start方法入手數據結構
public void start() { //經過指定配置文件,啓動了一個spring容器 String configPath = ConfigUtils.getProperty(SPRING_CONFIG); if (configPath == null || configPath.length() == 0) { configPath = DEFAULT_SPRING_CONFIG; } context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+")); context.start(); }
看下spring配置app
<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:conf/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 client="curator" address="${dubbo.registry.address}"/> <!--服務發佈協議和端口--> <dubbo:protocol name="dubbo" port="${dubbo.protocol.port}"/> <!--發佈com.alibaba.dubbo.monitor.MonitorService服務 實現類是com.alibaba.dubbo.monitor.simple.SimpleMonitorService--> <!--這就是消費方和服務提供方在上報統計數據時用的服務實現--> <dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" delay="-1"/> <!--注入com.alibaba.dubbo.registry.RegistryService服務引用 這個服務實現有dubbo自身實現,不須要從註冊中心獲取--> <!--具體在RegistryProtocol的refer方法中作了特殊處理,源碼在下面--> <dubbo:reference id="registryService" interface="com.alibaba.dubbo.registry.RegistryService"/> </beans>
RegistryProtocol的refer方法:ide
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { //經過register 能夠獲取具體,註冊中心協議,這裏是zookeeper,並設置爲url 協議。 url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); //這裏會經過ZookeeperRegistryFactory的getRegistry方法實現,獲得zookeeper的Registry 實現ZookeeperRegistry類, //而Registry 接口繼承了RegistryService接口 Registry registry = registryFactory.getRegistry(url); if (RegistryService.class.equals(type)) { //因此這裏直接把ZookeeperRegistry做爲RegistryService服務的實現,建立代理 return proxyFactory.getInvoker((T) registry, type, url); } // group="a,b" or group="*" Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); String group = qs.get(Constants.GROUP_KEY); if (group != null && group.length() > 0) { if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) { return doRefer(getMergeableCluster(), registry, type, url); } } //這裏cluster是Cluster$Adpative類對象 return doRefer(cluster, registry, type, url); }
這裏SpringContainer的主要完成了,MonitorService服務發佈,建立RegistryService服務代理實現。函數
public void start() { //驗證註冊中心地址 String url = ConfigUtils.getProperty(REGISTRY_ADDRESS); if (url == null || url.length() == 0) { throw new IllegalArgumentException("Please set java start argument: -D" + REGISTRY_ADDRESS + "=zookeeper://127.0.0.1:2181"); } //經過spring 容器獲取獲取registryService服務,因此須要spring容器先啓動 registry = (RegistryService) SpringContainer.getContext().getBean("registryService"); URL subscribeUrl = new URL(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)); //經過registry去註冊中心訂閱,服務消費者,提供者信息 //這裏 subscribeUrl = admin://10.47.16.51?category=providers,consumers&check=false&classifier=*&group=*&interface=*&version=* //做用是訂閱全部的服務提供和消費方節點。 registry.subscribe(subscribeUrl, new NotifyListener() { //經過監聽器回調方法,把訂閱的服務信息系分類存儲到相應數據結構中 //以備使用 public void notify(List<URL> urls) { if (urls == null || urls.size() == 0) { return; } Map<String, List<URL>> proivderMap = new HashMap<String, List<URL>>(); Map<String, List<URL>> consumerMap = new HashMap<String, List<URL>>(); for (URL url : urls) { String application = url.getParameter(Constants.APPLICATION_KEY); if (application != null && application.length() > 0) { //應用統計 applications.add(application); } String service = url.getServiceInterface(); //服務統計 services.add(service); //獲取url的類別信息,默認分類是 providers String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); if (Constants.PROVIDERS_CATEGORY.equals(category)) {//服務提供者信息 if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { serviceProviders.remove(service); } else { List<URL> list = proivderMap.get(service); if (list == null) { list = new ArrayList<URL>(); proivderMap.put(service, list); } list.add(url); if (application != null && application.length() > 0) { //獲取提供服務的應用集合 Set<String> serviceApplications = providerServiceApplications.get(service); if (serviceApplications == null) { providerServiceApplications.put(service, new ConcurrentHashSet<String>()); serviceApplications = providerServiceApplications.get(service); } serviceApplications.add(application); //應用提供的服務集合 Set<String> applicationServices = providerApplicationServices.get(application); if (applicationServices == null) { providerApplicationServices.put(application, new ConcurrentHashSet<String>()); applicationServices = providerApplicationServices.get(application); } applicationServices.add(service); } } } else if (Constants.CONSUMERS_CATEGORY.equals(category)) {//消費者列表 if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { serviceConsumers.remove(service); } else { List<URL> list = consumerMap.get(service); if (list == null) { list = new ArrayList<URL>(); consumerMap.put(service, list); } list.add(url); if (application != null && application.length() > 0) { Set<String> serviceApplications = consumerServiceApplications.get(service); if (serviceApplications == null) { //消費者的應用列表 consumerServiceApplications.put(service, new ConcurrentHashSet<String>()); serviceApplications = consumerServiceApplications.get(service); } serviceApplications.add(application); Set<String> applicationServices = consumerApplicationServices.get(application); if (applicationServices == null) { consumerApplicationServices.put(application, new ConcurrentHashSet<String>()); applicationServices = consumerApplicationServices.get(application); } applicationServices.add(service); } } } } if (proivderMap != null && proivderMap.size() > 0) { //全部的服務提供者 serviceProviders.putAll(proivderMap); } if (consumerMap != null && consumerMap.size() > 0) { //全部消費者信息 serviceConsumers.putAll(consumerMap); } } }); }
RegistryContainer主要完成了從註冊中心對服務信息的收集,並提供了相關方法對收集到的數據進行使用,例如工具
//獲取全部的應用 public Set<String> getApplications() { return Collections.unmodifiableSet(applications); } /*** * reverse:true 獲取某個應用,全部服務的消費者 * false:某個應用,全部服務提供者 * @param application * @param reverse * @return */ public Set<String> getDependencies(String application, boolean reverse) { if (reverse) { Set<String> dependencies = new HashSet<String>(); //應用的全部的服務 Set<String> services = providerApplicationServices.get(application); if (services != null && services.size() > 0) { for (String service : services) { //全部服務消費者 Set<String> applications = consumerServiceApplications.get(service); if (applications != null && applications.size() > 0) { dependencies.addAll(applications); } } } return dependencies; } else { Set<String> dependencies = new HashSet<String>(); Set<String> services = consumerApplicationServices.get(application); if (services != null && services.size() > 0) { for (String service : services) { //全部服務提供者 Set<String> applications = providerServiceApplications.get(service); if (applications != null && applications.size() > 0) { dependencies.addAll(applications); } } } return dependencies; } } //獲取全部的服務 public Set<String> getServices() { return Collections.unmodifiableSet(services); } //獲取某個應用全部提供全部的服務urls public List<URL> getProvidersByApplication(String application) { List<URL> urls = new ArrayList<URL>(); if (application != null && application.length() > 0) { for (List<URL> providers : serviceProviders.values()) { for (URL url : providers) { if (application.equals(url.getParameter(Constants.APPLICATION_KEY))) { urls.add(url); } } } } return urls; }
加上這篇(http://www.javashuo.com/article/p-tfzzkevu-kp.html)博文提到的上報信息,可知監控中心的監控信息大體可分爲兩類,一類是服務消費者和提供者經過MonitorService服務上報來的,服務調用動態信息,還有一類是監控中心經過訂閱註冊中心獲取的服務分佈靜態信息。
public void start() { //jetty 服務端口 String serverPort = ConfigUtils.getProperty(JETTY_PORT); int port; if (serverPort == null || serverPort.length() == 0) { //默認端口8080 port = DEFAULT_JETTY_PORT; } else { port = Integer.parseInt(serverPort); } //初始化鏈接器 connector = new SelectChannelConnector(); connector.setPort(port); ServletHandler handler = new ServletHandler(); String resources = ConfigUtils.getProperty(JETTY_DIRECTORY); if (resources != null && resources.length() > 0) { //添加過濾器,具體過濾邏輯看ResourceFilter //指定過濾器,過濾器匹配的路徑/* FilterHolder resourceHolder = handler.addFilterWithMapping(ResourceFilter.class, "/*", Handler.DEFAULT); //設置初始參數值 resourceHolder.setInitParameter("resources", resources); } //添加servlet處理,處理邏輯能夠看PageServlet和匹配路徑 ServletHolder pageHolder = handler.addServletWithMapping(PageServlet.class, "/*"); //設置初始參數值 pageHolder.setInitParameter("pages", ConfigUtils.getProperty(JETTY_PAGES)); pageHolder.setInitOrder(2); Server server = new Server(); server.addConnector(connector); //添加處理請求的handler server.addHandler(handler); try { //在指定端口啓動sevlet容器服務,接受用戶請求 server.start(); } catch (Exception e) { throw new IllegalStateException("Failed to start jetty server on " + NetUtils.getLocalHost() + ":" + port + ", cause: " + e.getMessage(), e); } }
//初始化資源路徑 public void init(FilterConfig filterConfig) throws ServletException { //根據初始化參數,獲取資源路徑,支持多個資源路徑 放入resources String config = filterConfig.getInitParameter("resources"); if (config != null && config.length() > 0) { String[] configs = Constants.COMMA_SPLIT_PATTERN.split(config); for (String c : configs) { if (c != null && c.length() > 0) { c = c.replace('\\', '/'); if (c.endsWith("/")) { c = c.substring(0, c.length() - 1); } resources.add(c); } } } }
//過濾邏輯 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (response.isCommitted()) { return; } String uri = request.getRequestURI(); String context = request.getContextPath(); if (uri.endsWith("/favicon.ico")) { uri = "/favicon.ico"; } else if (context != null && !"/".equals(context)) { uri = uri.substring(context.length()); } if (!uri.startsWith("/")) { uri = "/" + uri; } //獲取資源的默認修改時間 long lastModified = getLastModified(uri); long since = request.getDateHeader("If-Modified-Since"); if (since >= lastModified) { response.sendError(HttpServletResponse.SC_NOT_MODIFIED); return; } byte[] data; //經過url沒有制定資源,就走servlet邏輯 InputStream input = getInputStream(uri); if (input == null) { //沒有獲取到資源,經過過濾器往下走,到servlet層(***看這裏**) chain.doFilter(req, res); return; } //能獲取具體資源,直接返回資源 try { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); } data = output.toByteArray(); } finally { input.close(); } //設置modified 時間 response.setDateHeader("Last-Modified", lastModified); OutputStream output = response.getOutputStream(); output.write(data); output.flush(); }
PageServlet
//初始化 public void init() throws ServletException { super.init(); //維護自身實例的引用 INSTANCE = this; String config = getServletConfig().getInitParameter("pages"); Collection<String> names; //若是配置了pages 實現名稱,賦值給names 集合,能夠支持逗號分割多個pages if (config != null && config.length() > 0) { names = Arrays.asList(Constants.COMMA_SPLIT_PATTERN.split(config)); } else { //沒有配置,默認獲取說有spi擴展的pages names = ExtensionLoader.getExtensionLoader(PageHandler.class).getSupportedExtensions(); } for (String name : names) { PageHandler handler = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtension(name); pages.put(ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(handler), handler); //若是實現類上有註解 Menu,收集到添加到menus列表中,用於顯示在頁面最上方的頂級菜單 Menu menu = handler.getClass().getAnnotation(Menu.class); if (menu != null) { menus.add(handler); } } //對menus 經過自定義的MenuComparator菜單排序 Collections.sort(menus, new MenuComparator()); }
//處理請求過程 protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (!response.isCommitted()) { PrintWriter writer = response.getWriter(); String uri = request.getRequestURI(); boolean isHtml = false; if (uri == null || uri.length() == 0 || "/".equals(uri)) { //默認用index PageHandler實現 uri = "index"; isHtml = true; } else { //uri 去頭,截尾 if (uri.startsWith("/")) { uri = uri.substring(1); } if (uri.endsWith(".html")) { uri = uri.substring(0, uri.length() - ".html".length()); isHtml = true; } } if (uri.endsWith("favicon.ico")) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } //到這裏 uri 就是某個pageHandler spi 實現名稱 ExtensionLoader<PageHandler> pageHandlerLoader = ExtensionLoader.getExtensionLoader(PageHandler.class); PageHandler pageHandler = pageHandlerLoader.hasExtension(uri) ? pageHandlerLoader.getExtension(uri) : null; if (isHtml) { //拼接html代碼 writer.println("<html><head><title>Dubbo</title>"); writer.println("<style type=\"text/css\">html, body {margin: 10;padding: 0;background-color: #6D838C;font-family: Arial, Verdana;font-size: 12px;color: #FFFFFF;text-align: center;vertical-align: middle;word-break: break-all; } table {width: 90%; margin: 0px auto;border-collapse: collapse;border: 8px solid #FFFFFF; } thead tr {background-color: #253c46; } tbody tr {background-color: #8da5af; } th {padding-top: 4px;padding-bottom: 4px;font-size: 14px;height: 20px; } td {margin: 3px;padding: 3px;border: 2px solid #FFFFFF;font-size: 14px;height: 25px; } a {color: #FFFFFF;cursor: pointer;text-decoration: underline; } a:hover {text-decoration: none; }</style>"); writer.println("</head><body>"); } if (pageHandler != null) { Page page = null; try { String query = request.getQueryString(); //把查詢條件做爲參數放入handle 參數URL //經過pageHandler返回page 對象 page = pageHandler.handle(URL.valueOf(request.getRequestURL().toString() + (query == null || query.length() == 0 ? "" : "?" + query))); } catch (Throwable t) { logger.warn(t.getMessage(), t); String msg = t.getMessage(); if (msg == null) { msg = StringUtils.toString(t); } if (isHtml) { writer.println("<table>"); writer.println("<thead>"); writer.println(" <tr>"); writer.println(" <th>Error</th>"); writer.println(" </tr>"); writer.println("</thead>"); writer.println("<tbody>"); writer.println(" <tr>"); writer.println(" <td>"); writer.println(" " + msg.replace("<", "<").replace(">", "<").replace("\n", "<br/>")); writer.println(" </td>"); writer.println(" </tr>"); writer.println("</tbody>"); writer.println("</table>"); writer.println("<br/>"); } else { writer.println(msg); } } if (page != null) { if (isHtml) { //經過handler方法放回的page對象構造html String nav = page.getNavigation(); if (nav == null || nav.length() == 0) { nav = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(pageHandler); nav = nav.substring(0, 1).toUpperCase() + nav.substring(1); } if (!"index".equals(uri)) { nav = "<a href=\"/\">Home</a> > " + nav; } //繪製菜單部分 writeMenu(request, writer, nav); //繪製表格部分 writeTable(writer, page.getTitle(), page.getColumns(), page.getRows()); } else { if (page.getRows().size() > 0 && page.getRows().get(0).size() > 0) { writer.println(page.getRows().get(0).get(0)); } } } } else { //沒有pageHanlder 實現提示 Not found if (isHtml) { writer.println("<table>"); writer.println("<thead>"); writer.println(" <tr>"); writer.println(" <th>Error</th>"); writer.println(" </tr>"); writer.println("</thead>"); writer.println("<tbody>"); writer.println(" <tr>"); writer.println(" <td>"); writer.println(" Not found " + uri + " page. Please goto <a href=\"/\">Home</a> page."); writer.println(" </td>"); writer.println(" </tr>"); writer.println("</tbody>"); writer.println("</table>"); writer.println("<br/>"); } else { writer.println("Not found " + uri + " page."); } } if (isHtml) { writer.println("</body></html>"); } //寫到客戶端 writer.flush(); } }
經過上面的代碼能夠知道,serlvet是經過分析uri的請求路徑,動態加載相應的PageHandler並經過調用其hanlder方法,來獲取頁面要展現的數據的。
這裏看下PageHandler一個具體擴展實現ProvidersPageHandler,它的hanlder方法以下:
public Page handle(URL url) { //經過url獲取一些服務的基本信息 String service = url.getParameter("service"); String host = url.getParameter("host"); String application = url.getParameter("application"); if (service != null && service.length() > 0) { List<List<String>> rows = new ArrayList<List<String>>(); //重點在這,對以前訂閱信息的使用 //經過 RegistryContainer.getInstance().getProvidersByService方法 //獲取 RegistryContainer容器經過訂閱註冊中心獲取的 服務消費者和提供者信息 List<URL> providers = RegistryContainer.getInstance().getProvidersByService(service); if (providers != null && providers.size() > 0) { for (URL u : providers) { List<String> row = new ArrayList<String>(); String s = u.toFullString(); row.add(s.replace("&", "&")); row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?service=" + service + "&provider=" + URL.encode(s) + "';}\">Unregister</button>"); rows.add(row); } } //Page 對象中,主要是導航,標題,表格列,行信息,對應着咱們文章開頭的圖解說明 return new Page("<a href=\"services.html\">Services</a> > " + service + " > Providers | <a href=\"consumers.html?service=" + service + "\">Consumers</a> | <a href=\"statistics.html?service=" + service + "\">Statistics</a> | <a href=\"charts.html?service=" + service + "\">Charts</a>", "Providers (" + rows.size() + ")", new String[]{"Provider URL:", "Unregister"}, rows); } else if (host != null && host.length() > 0) { List<List<String>> rows = new ArrayList<List<String>>(); List<URL> providers = RegistryContainer.getInstance().getProvidersByHost(host); if (providers != null && providers.size() > 0) { for (URL u : providers) { List<String> row = new ArrayList<String>(); String s = u.toFullString(); row.add(s.replace("&", "&")); row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?host=" + host + "&provider=" + URL.encode(s) + "';}\">Unregister</button>"); rows.add(row); } } return new Page("<a href=\"hosts.html\">Hosts</a> > " + NetUtils.getHostName(host) + "/" + host + " > Providers | <a href=\"consumers.html?host=" + host + "\">Consumers</a>", "Providers (" + rows.size() + ")", new String[]{"Provider URL:", "Unregister"}, rows); } else if (application != null && application.length() > 0) { List<List<String>> rows = new ArrayList<List<String>>(); List<URL> providers = RegistryContainer.getInstance().getProvidersByApplication(application); if (providers != null && providers.size() > 0) { for (URL u : providers) { List<String> row = new ArrayList<String>(); String s = u.toFullString(); row.add(s.replace("&", "&")); row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?application=" + application + "&provider=" + URL.encode(s) + "';}\">Unregister</button>"); rows.add(row); } } return new Page("<a href=\"applications.html\">Applications</a> > " + application + " > Providers | <a href=\"consumers.html?application=" + application + "\">Consumers</a> | <a href=\"dependencies.html?application=" + application + "\">Depends On</a> | <a href=\"dependencies.html?application=" + application + "&reverse=true\">Used By</a>", "Providers (" + rows.size() + ")", new String[]{"Provider URL:", "Unregister"}, rows); } else { throw new IllegalArgumentException("Please input service or host or application parameter."); } }
由消費者和服務提供者上報的動態調用信息,是以文件形式存在硬盤上的,包括圖表以png形式(由jfreechart工具生成),
存儲目錄是dubbo.jetty.directory配置指定的。這部分工做是由SimpleMonitorService實現的。
public SimpleMonitorService() { queue = new LinkedBlockingQueue<URL>(Integer.parseInt(ConfigUtils.getProperty("dubbo.monitor.queue", "100000"))); //後臺守護線程 writeThread = new Thread(new Runnable() { public void run() { while (running) { try { write(); // write statistics 寫統計文件 } catch (Throwable t) { logger.error("Unexpected error occur at write stat log, cause: " + t.getMessage(), t); try { Thread.sleep(5000); // retry after 5 secs } catch (Throwable t2) { } } } } }); writeThread.setDaemon(true); writeThread.setName("DubboMonitorAsyncWriteLogThread"); writeThread.start(); //線程池 chartFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { public void run() { try { draw(); // draw chart 畫圖 } catch (Throwable t) { logger.error("Unexpected error occur at draw stat chart, cause: " + t.getMessage(), t); } } }, 1, 300, TimeUnit.SECONDS); statisticsDirectory = ConfigUtils.getProperty("dubbo.statistics.directory"); chartsDirectory = ConfigUtils.getProperty("dubbo.charts.directory"); }
具體生成的文件這裏簡單切幾個圖,配置文件是存${user.home}/monitor目錄下
monitor目錄下有 charts 和statistics兩個目錄,分別存放圖片和統計數據文件的。
這裏在日期+接口名+方法名 格式的目錄下,有生成好的圖表文件,形如
下面兩個圖是statistics目錄下統計文件和文件具體內容