这里的监控中心以dubbo-ops\dubbo-monitor-simple项目说
总的来说是监控中心启动一个sevlet容器,通过web页面向用户多维度的展示dubbo服务信息。如下图
从页面结构来说,图中红框框住的几个部分,第一行是菜单,第二上是导航,第三行是表格title,最后一部分是表格。我们先从服务服务启动类开始
- public class MonitorStarter {
- public static void main(String[] args) {
- //通过main方法启动
- System.setProperty(Constants.DUBBO_PROPERTIES_KEY, "conf/dubbo.properties");
- Main.main(args);
- }
- }
再看下Main 的main方法
- 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 文件中配置项
dubbo.container=log4j,spring,registry,jetty
通过spi相关文件找到如下实现
- 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方法入手
SpringContainer
- 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配置
- <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方法:
- 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服务代理实现。
RegistryContainer
- 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;
- }
加上这篇(https://my.oschina.net/u/146130/blog/1634970)博文提到的上报信息,可知监控中心的监控信息大致可分为两类,一类是服务消费者和提供者通过MonitorService服务上报来的,服务调用动态信息,还有一类是监控中心通过订阅注册中心获取的服务分布静态信息。
JettyContainer
- 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);
- }
- }
ResourceFilter
- //初始化资源路径
- 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目录下统计文件和文件具体内容