dubbo监控机制之监控中心实现分析

这里的监控中心以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("<", "&lt;").replace(">", "&lt;").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> &gt; " + 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("&", "&amp;"));
                    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> &gt; " + service
                    + " &gt; 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("&", "&amp;"));
                    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> &gt; " + NetUtils.getHostName(host) + "/" + host + " &gt; 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("&", "&amp;"));
                    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> &gt; " + application + " &gt; 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目录下统计文件和文件具体内容

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏软件开发

MyBatis学习总结(四)——MyBatis缓存与代码生成

缓存可以提高系统性能,可以加快访问速度,减轻服务器压力,带来更好的用户体验。缓存用空间换时间,好的缓存是缓存命中率高的且数据量小的。缓存是一种非常重要的技术。

2403
来自专栏编码小白

tomcat源码解读四 tomcat中的processer

     Processor是一个接口,针对于不同协议下具有不同的具体实现类,其实现类的具体功能是处理http请求,主要是对协议进行解析,状态处理以及响应。然后...

4567
来自专栏瓜大三哥

Yaffs_guts(三)

1.垃圾回收 1.static int yaffs_InitialiseBlocks(yaffs_Device *dev,int nBlocks)//块初始化 ...

2225
来自专栏清晨我上码

spring websocket推送

3424
来自专栏石奈子的Java之路

原 SpringBoot 2.0 系列00

1414
来自专栏zhisheng

渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

上篇文章写了 ElasticSearch 源码解析 —— 环境搭建 ,其中里面说了启动 打开 server 模块下的 Elasticsearch 类:org.e...

1341
来自专栏魂祭心

原 结合源码分析 setTimeout /

3656
来自专栏Flutter入门

Weex是如何在Android客户端上跑起来的

Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MV...

4305
来自专栏闵开慧

Mapreduce任务实现邮件监控

Mapreduce任务实现邮件监控     这里主要使用Java自带邮件类实现Mapreduce任务的监控,如果Mapreduce任务报错则发送报错邮件。Map...

3348
来自专栏菩提树下的杨过

java学习:weblogic下JNDI及JDBC连接测试(weblogic环境)

JNDI的专业解释,大家自行去网络搜索吧,这里就不啰嗦了。 单纯从使用角度看,可以简称把它看成一个key-value的“哈希资源”容器。给定一个string类型...

2869

扫码关注云+社区

领取腾讯云代金券