这里的监控中心以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目录下统计文件和文件具体内容