前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo监控机制之监控中心实现分析

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

作者头像
技术蓝海
修改2020-10-26 13:03:12
3.2K0
修改2020-10-26 13:03:12
举报
文章被收录于专栏:wannshan(javaer,RPC)wannshan(javaer,RPC)

这里的监控中心以dubbo-monitor-simple项目说

github 地址 https://github.com/apache/dubbo-admin ,被单独移到dubbo-admin中维护,在master 分支中

总的来说是监控中心启动一个sevlet容器,通过web页面向用户多维度的展示dubbo服务信息。如下图

从页面结构来说,图中红框框住的几个部分,第一行是菜单,第二上是导航,第三行是表格title,最后一部分是表格。我们先从服务服务启动类开始

代码语言:javascript
复制
public class MonitorStarter {
    public static void main(String[] args) {
    //通过main方法启动
        System.setProperty(Constants.DUBBO_PROPERTIES_KEY, "conf/dubbo.properties");
         Main.main(args);
    }
}

再看下Main 的main方法

代码语言:javascript
复制
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 文件中配置项

代码语言:javascript
复制
dubbo.container=log4j,spring,registry,jetty

通过spi相关文件找到如下实现

代码语言:javascript
复制
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

代码语言:javascript
复制
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配置

代码语言:javascript
复制
<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方法:

代码语言:javascript
复制
  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

代码语言:javascript
复制
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主要完成了从注册中心对服务信息的收集,并提供了相关方法对收集到的数据进行使用,例如

代码语言:javascript
复制
    //获取所有的应用
    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://cloud.tencent.com/developer/article/1109464)博文提到的上报信息,可知监控中心的监控信息大致可分为两类,一类是服务消费者和提供者通过MonitorService服务上报来的,服务调用动态信息,还有一类是监控中心通过订阅注册中心获取的服务分布静态信息。

JettyContainer

代码语言:javascript
复制
   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

代码语言:javascript
复制
//初始化资源路径
     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);
                }
            }
        }
    }
代码语言:javascript
复制
  //过滤逻辑
      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

代码语言:javascript
复制
//初始化
 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());
    }
代码语言:javascript
复制
//处理请求过程
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方法如下:

代码语言:javascript
复制
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实现的。

代码语言:javascript
复制
 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目录下统计文件和文件具体内容

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringContainer
  • RegistryContainer
  • JettyContainer
  • ResourceFilter
  • 最后
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档