当前位置:   article > 正文

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

dubbo监控中心怎么实现的

这里的监控中心以dubbo-ops\dubbo-monitor-simple项目说
总的来说是监控中心启动一个sevlet容器,通过web页面向用户多维度的展示dubbo服务信息。如下图

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

  1. public class MonitorStarter {
  2. public static void main(String[] args) {
  3. //通过main方法启动
  4. System.setProperty(Constants.DUBBO_PROPERTIES_KEY, "conf/dubbo.properties");
  5. Main.main(args);
  6. }
  7. }

再看下Main 的main方法

  1. public static void main(String[] args) {
  2. try {
  3. //通过dubbo.container 获取要启动的容器名,多个以逗号分割
  4. if (args == null || args.length == 0) {
  5. String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
  6. args = Constants.COMMA_SPLIT_PATTERN.split(config);
  7. }
  8. //通过spi 加装容器实例 放入list
  9. final List<Container> containers = new ArrayList<Container>();
  10. for (int i = 0; i < args.length; i++) {
  11. containers.add(loader.getExtension(args[i]));
  12. }
  13. logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
  14. //优雅停机的回调设置
  15. if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
  16. Runtime.getRuntime().addShutdownHook(new Thread() {
  17. public void run() {
  18. for (Container container : containers) {
  19. try {
  20. container.stop();
  21. logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
  22. } catch (Throwable t) {
  23. logger.error(t.getMessage(), t);
  24. }
  25. try {
  26. LOCK.lock();//重入锁
  27. STOP.signal();//释放信号量
  28. } finally {
  29. //释放锁
  30. LOCK.unlock();
  31. }
  32. }
  33. }
  34. });
  35. }
  36. //分别调用容器的start方法 启动指定的容器。
  37. for (Container container : containers) {
  38. container.start();
  39. logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
  40. }
  41. System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
  42. } catch (RuntimeException e) {
  43. e.printStackTrace();
  44. logger.error(e.getMessage(), e);
  45. System.exit(1);
  46. }
  47. try {
  48. LOCK.lock();//获取锁
  49. STOP.await();//等待信号量
  50. } catch (InterruptedException e) {
  51. //不通过钩子函数停机,记录异常
  52. logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
  53. } finally {
  54. //释放锁
  55. LOCK.unlock();
  56. }
  57. }

在dubbo.properties 文件中配置项

dubbo.container=log4j,spring,registry,jetty

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

  1. spring=com.alibaba.dubbo.container.spring.SpringContainer
  2. log4j=com.alibaba.dubbo.container.log4j.Log4jContainer
  3. registry=com.alibaba.dubbo.monitor.simple.container.RegistryContainer
  4. jetty=com.alibaba.dubbo.monitor.simple.container.JettyContainer

这里重点解析下面spring,registry,jetty三种实现。具体从分析他们的start方法入手

SpringContainer

  1. public void start() {
  2. //通过指定配置文件,启动了一个spring容器
  3. String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
  4. if (configPath == null || configPath.length() == 0) {
  5. configPath = DEFAULT_SPRING_CONFIG;
  6. }
  7. context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
  8. context.start();
  9. }

看下spring配置

  1. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  5. http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
  6. <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  7. <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
  8. <property name="location" value="classpath:conf/dubbo.properties"/>
  9. </bean>
  10. <bean id="monitorService" class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService">
  11. </bean>
  12. <dubbo:application name="${dubbo.application.name}" owner="${dubbo.application.owner}"/>
  13. <!--指定注册中心地址-->
  14. <dubbo:registry client="curator" address="${dubbo.registry.address}"/>
  15. <!--服务发布协议和端口-->
  16. <dubbo:protocol name="dubbo" port="${dubbo.protocol.port}"/>
  17. <!--发布com.alibaba.dubbo.monitor.MonitorService服务 实现类是com.alibaba.dubbo.monitor.simple.SimpleMonitorService-->
  18. <!--这就是消费方和服务提供方在上报统计数据时用的服务实现-->
  19. <dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" delay="-1"/>
  20. <!--注入com.alibaba.dubbo.registry.RegistryService服务引用 这个服务实现有dubbo自身实现,不需要从注册中心获取-->
  21. <!--具体在RegistryProtocol的refer方法中做了特殊处理,源码在下面-->
  22. <dubbo:reference id="registryService" interface="com.alibaba.dubbo.registry.RegistryService"/>
  23. </beans>

RegistryProtocol的refer方法:

  1. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  2. //通过register 可以获取具体,注册中心协议,这里是zookeeper,并设置为url 协议。
  3. url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
  4. //这里会通过ZookeeperRegistryFactory的getRegistry方法实现,得到zookeeper的Registry 实现ZookeeperRegistry类,
  5. //而Registry 接口继承了RegistryService接口
  6. Registry registry = registryFactory.getRegistry(url);
  7. if (RegistryService.class.equals(type)) {
  8. //所以这里直接把ZookeeperRegistry作为RegistryService服务的实现,创建代理
  9. return proxyFactory.getInvoker((T) registry, type, url);
  10. }
  11. // group="a,b" or group="*"
  12. Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
  13. String group = qs.get(Constants.GROUP_KEY);
  14. if (group != null && group.length() > 0) {
  15. if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
  16. || "*".equals(group)) {
  17. return doRefer(getMergeableCluster(), registry, type, url);
  18. }
  19. }
  20. //这里cluster是Cluster$Adpative类对象
  21. return doRefer(cluster, registry, type, url);
  22. }

这里SpringContainer的主要完成了,MonitorService服务发布,创建RegistryService服务代理实现。

RegistryContainer

  1. public void start() {
  2. //验证注册中心地址
  3. String url = ConfigUtils.getProperty(REGISTRY_ADDRESS);
  4. if (url == null || url.length() == 0) {
  5. throw new IllegalArgumentException("Please set java start argument: -D" + REGISTRY_ADDRESS + "=zookeeper://127.0.0.1:2181");
  6. }
  7. //通过spring 容器获取获取registryService服务,所以需要spring容器先启动
  8. registry = (RegistryService) SpringContainer.getContext().getBean("registryService");
  9. URL subscribeUrl = new URL(Constants.ADMIN_PROTOCOL, NetUtils.getLocalHost(), 0, "",
  10. Constants.INTERFACE_KEY, Constants.ANY_VALUE,
  11. Constants.GROUP_KEY, Constants.ANY_VALUE,
  12. Constants.VERSION_KEY, Constants.ANY_VALUE,
  13. Constants.CLASSIFIER_KEY, Constants.ANY_VALUE,
  14. Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + ","
  15. + Constants.CONSUMERS_CATEGORY,
  16. Constants.CHECK_KEY, String.valueOf(false));
  17. //通过registry去注册中心订阅,服务消费者,提供者信息
  18. //这里 subscribeUrl = admin://10.47.16.51?category=providers,consumers&check=false&classifier=*&group=*&interface=*&version=*
  19. //作用是订阅所有的服务提供和消费方节点。
  20. registry.subscribe(subscribeUrl, new NotifyListener() {
  21. //通过监听器回调方法,把订阅的服务信息系分类存储到相应数据结构中
  22. //以备使用
  23. public void notify(List<URL> urls) {
  24. if (urls == null || urls.size() == 0) {
  25. return;
  26. }
  27. Map<String, List<URL>> proivderMap = new HashMap<String, List<URL>>();
  28. Map<String, List<URL>> consumerMap = new HashMap<String, List<URL>>();
  29. for (URL url : urls) {
  30. String application = url.getParameter(Constants.APPLICATION_KEY);
  31. if (application != null && application.length() > 0) {
  32. //应用统计
  33. applications.add(application);
  34. }
  35. String service = url.getServiceInterface();
  36. //服务统计
  37. services.add(service);
  38. //获取url的类别信息,默认分类是 providers
  39. String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
  40. if (Constants.PROVIDERS_CATEGORY.equals(category)) {//服务提供者信息
  41. if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
  42. serviceProviders.remove(service);
  43. } else {
  44. List<URL> list = proivderMap.get(service);
  45. if (list == null) {
  46. list = new ArrayList<URL>();
  47. proivderMap.put(service, list);
  48. }
  49. list.add(url);
  50. if (application != null && application.length() > 0) {
  51. //获取提供服务的应用集合
  52. Set<String> serviceApplications = providerServiceApplications.get(service);
  53. if (serviceApplications == null) {
  54. providerServiceApplications.put(service, new ConcurrentHashSet<String>());
  55. serviceApplications = providerServiceApplications.get(service);
  56. }
  57. serviceApplications.add(application);
  58. //应用提供的服务集合
  59. Set<String> applicationServices = providerApplicationServices.get(application);
  60. if (applicationServices == null) {
  61. providerApplicationServices.put(application, new ConcurrentHashSet<String>());
  62. applicationServices = providerApplicationServices.get(application);
  63. }
  64. applicationServices.add(service);
  65. }
  66. }
  67. } else if (Constants.CONSUMERS_CATEGORY.equals(category)) {//消费者列表
  68. if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
  69. serviceConsumers.remove(service);
  70. } else {
  71. List<URL> list = consumerMap.get(service);
  72. if (list == null) {
  73. list = new ArrayList<URL>();
  74. consumerMap.put(service, list);
  75. }
  76. list.add(url);
  77. if (application != null && application.length() > 0) {
  78. Set<String> serviceApplications = consumerServiceApplications.get(service);
  79. if (serviceApplications == null) {
  80. //消费者的应用列表
  81. consumerServiceApplications.put(service, new ConcurrentHashSet<String>());
  82. serviceApplications = consumerServiceApplications.get(service);
  83. }
  84. serviceApplications.add(application);
  85. Set<String> applicationServices = consumerApplicationServices.get(application);
  86. if (applicationServices == null) {
  87. consumerApplicationServices.put(application, new ConcurrentHashSet<String>());
  88. applicationServices = consumerApplicationServices.get(application);
  89. }
  90. applicationServices.add(service);
  91. }
  92. }
  93. }
  94. }
  95. if (proivderMap != null && proivderMap.size() > 0) {
  96. //所有的服务提供者
  97. serviceProviders.putAll(proivderMap);
  98. }
  99. if (consumerMap != null && consumerMap.size() > 0) {
  100. //所有消费者信息
  101. serviceConsumers.putAll(consumerMap);
  102. }
  103. }
  104. });
  105. }

RegistryContainer主要完成了从注册中心对服务信息的收集,并提供了相关方法对收集到的数据进行使用,例如

  1. //获取所有的应用
  2. public Set<String> getApplications() {
  3. return Collections.unmodifiableSet(applications);
  4. }
  5. /***
  6. * reverse:true 获取某个应用,所有服务的消费者
  7. * false:某个应用,所有服务提供者
  8. * @param application
  9. * @param reverse
  10. * @return
  11. */
  12. public Set<String> getDependencies(String application, boolean reverse) {
  13. if (reverse) {
  14. Set<String> dependencies = new HashSet<String>();
  15. //应用的所有的服务
  16. Set<String> services = providerApplicationServices.get(application);
  17. if (services != null && services.size() > 0) {
  18. for (String service : services) {
  19. //所有服务消费者
  20. Set<String> applications = consumerServiceApplications.get(service);
  21. if (applications != null && applications.size() > 0) {
  22. dependencies.addAll(applications);
  23. }
  24. }
  25. }
  26. return dependencies;
  27. } else {
  28. Set<String> dependencies = new HashSet<String>();
  29. Set<String> services = consumerApplicationServices.get(application);
  30. if (services != null && services.size() > 0) {
  31. for (String service : services) {
  32. //所有服务提供者
  33. Set<String> applications = providerServiceApplications.get(service);
  34. if (applications != null && applications.size() > 0) {
  35. dependencies.addAll(applications);
  36. }
  37. }
  38. }
  39. return dependencies;
  40. }
  41. }
  42. //获取所有的服务
  43. public Set<String> getServices() {
  44. return Collections.unmodifiableSet(services);
  45. }
  46. //获取某个应用所有提供所有的服务urls
  47. public List<URL> getProvidersByApplication(String application) {
  48. List<URL> urls = new ArrayList<URL>();
  49. if (application != null && application.length() > 0) {
  50. for (List<URL> providers : serviceProviders.values()) {
  51. for (URL url : providers) {
  52. if (application.equals(url.getParameter(Constants.APPLICATION_KEY))) {
  53. urls.add(url);
  54. }
  55. }
  56. }
  57. }
  58. return urls;
  59. }

加上这篇(https://my.oschina.net/u/146130/blog/1634970)博文提到的上报信息,可知监控中心的监控信息大致可分为两类,一类是服务消费者和提供者通过MonitorService服务上报来的,服务调用动态信息,还有一类是监控中心通过订阅注册中心获取的服务分布静态信息。

JettyContainer

  1. public void start() {
  2. //jetty 服务端口
  3. String serverPort = ConfigUtils.getProperty(JETTY_PORT);
  4. int port;
  5. if (serverPort == null || serverPort.length() == 0) {
  6. //默认端口8080
  7. port = DEFAULT_JETTY_PORT;
  8. } else {
  9. port = Integer.parseInt(serverPort);
  10. }
  11. //初始化连接器
  12. connector = new SelectChannelConnector();
  13. connector.setPort(port);
  14. ServletHandler handler = new ServletHandler();
  15. String resources = ConfigUtils.getProperty(JETTY_DIRECTORY);
  16. if (resources != null && resources.length() > 0) {
  17. //添加过滤器,具体过滤逻辑看ResourceFilter
  18. //指定过滤器,过滤器匹配的路径/*
  19. FilterHolder resourceHolder = handler.addFilterWithMapping(ResourceFilter.class, "/*", Handler.DEFAULT);
  20. //设置初始参数值
  21. resourceHolder.setInitParameter("resources", resources);
  22. }
  23. //添加servlet处理,处理逻辑可以看PageServlet和匹配路径
  24. ServletHolder pageHolder = handler.addServletWithMapping(PageServlet.class, "/*");
  25. //设置初始参数值
  26. pageHolder.setInitParameter("pages", ConfigUtils.getProperty(JETTY_PAGES));
  27. pageHolder.setInitOrder(2);
  28. Server server = new Server();
  29. server.addConnector(connector);
  30. //添加处理请求的handler
  31. server.addHandler(handler);
  32. try {
  33. //在指定端口启动sevlet容器服务,接受用户请求
  34. server.start();
  35. } catch (Exception e) {
  36. throw new IllegalStateException("Failed to start jetty server on " + NetUtils.getLocalHost() + ":" + port + ", cause: " + e.getMessage(), e);
  37. }
  38. }

ResourceFilter

  1. //初始化资源路径
  2. public void init(FilterConfig filterConfig) throws ServletException {
  3. //根据初始化参数,获取资源路径,支持多个资源路径 放入resources
  4. String config = filterConfig.getInitParameter("resources");
  5. if (config != null && config.length() > 0) {
  6. String[] configs = Constants.COMMA_SPLIT_PATTERN.split(config);
  7. for (String c : configs) {
  8. if (c != null && c.length() > 0) {
  9. c = c.replace('\\', '/');
  10. if (c.endsWith("/")) {
  11. c = c.substring(0, c.length() - 1);
  12. }
  13. resources.add(c);
  14. }
  15. }
  16. }
  17. }
  1. //过滤逻辑
  2. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  3. throws IOException, ServletException {
  4. HttpServletRequest request = (HttpServletRequest) req;
  5. HttpServletResponse response = (HttpServletResponse) res;
  6. if (response.isCommitted()) {
  7. return;
  8. }
  9. String uri = request.getRequestURI();
  10. String context = request.getContextPath();
  11. if (uri.endsWith("/favicon.ico")) {
  12. uri = "/favicon.ico";
  13. } else if (context != null && !"/".equals(context)) {
  14. uri = uri.substring(context.length());
  15. }
  16. if (!uri.startsWith("/")) {
  17. uri = "/" + uri;
  18. }
  19. //获取资源的默认修改时间
  20. long lastModified = getLastModified(uri);
  21. long since = request.getDateHeader("If-Modified-Since");
  22. if (since >= lastModified) {
  23. response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
  24. return;
  25. }
  26. byte[] data;
  27. //通过url没有制定资源,就走servlet逻辑
  28. InputStream input = getInputStream(uri);
  29. if (input == null) {
  30. //没有获取到资源,通过过滤器往下走,到servlet层(***看这里**)
  31. chain.doFilter(req, res);
  32. return;
  33. }
  34. //能获取具体资源,直接返回资源
  35. try {
  36. ByteArrayOutputStream output = new ByteArrayOutputStream();
  37. byte[] buffer = new byte[8192];
  38. int n = 0;
  39. while (-1 != (n = input.read(buffer))) {
  40. output.write(buffer, 0, n);
  41. }
  42. data = output.toByteArray();
  43. } finally {
  44. input.close();
  45. }
  46. //设置modified 时间
  47. response.setDateHeader("Last-Modified", lastModified);
  48. OutputStream output = response.getOutputStream();
  49. output.write(data);
  50. output.flush();
  51. }

PageServlet

  1. //初始化
  2. public void init() throws ServletException {
  3. super.init();
  4. //维护自身实例的引用
  5. INSTANCE = this;
  6. String config = getServletConfig().getInitParameter("pages");
  7. Collection<String> names;
  8. //如果配置了pages 实现名称,赋值给names 集合,可以支持逗号分割多个pages
  9. if (config != null && config.length() > 0) {
  10. names = Arrays.asList(Constants.COMMA_SPLIT_PATTERN.split(config));
  11. } else {
  12. //没有配置,默认获取说有spi扩展的pages
  13. names = ExtensionLoader.getExtensionLoader(PageHandler.class).getSupportedExtensions();
  14. }
  15. for (String name : names) {
  16. PageHandler handler = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtension(name);
  17. pages.put(ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(handler), handler);
  18. //如果实现类上有注解 Menu,收集到添加到menus列表中,用于显示在页面最上方的顶级菜单
  19. Menu menu = handler.getClass().getAnnotation(Menu.class);
  20. if (menu != null) {
  21. menus.add(handler);
  22. }
  23. }
  24. //对menus 通过自定义的MenuComparator菜单排序
  25. Collections.sort(menus, new MenuComparator());
  26. }
  1. //处理请求过程
  2. protected final void doPost(HttpServletRequest request, HttpServletResponse response)
  3. throws ServletException, IOException {
  4. if (!response.isCommitted()) {
  5. PrintWriter writer = response.getWriter();
  6. String uri = request.getRequestURI();
  7. boolean isHtml = false;
  8. if (uri == null || uri.length() == 0 || "/".equals(uri)) {
  9. //默认用index PageHandler实现
  10. uri = "index";
  11. isHtml = true;
  12. } else {
  13. //uri 去头,截尾
  14. if (uri.startsWith("/")) {
  15. uri = uri.substring(1);
  16. }
  17. if (uri.endsWith(".html")) {
  18. uri = uri.substring(0, uri.length() - ".html".length());
  19. isHtml = true;
  20. }
  21. }
  22. if (uri.endsWith("favicon.ico")) {
  23. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  24. return;
  25. }
  26. //到这里 uri 就是某个pageHandler spi 实现名称
  27. ExtensionLoader<PageHandler> pageHandlerLoader = ExtensionLoader.getExtensionLoader(PageHandler.class);
  28. PageHandler pageHandler = pageHandlerLoader.hasExtension(uri) ? pageHandlerLoader.getExtension(uri) : null;
  29. if (isHtml) {
  30. //拼接html代码
  31. writer.println("<html><head><title>Dubbo</title>");
  32. 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>");
  33. writer.println("</head><body>");
  34. }
  35. if (pageHandler != null) {
  36. Page page = null;
  37. try {
  38. String query = request.getQueryString();
  39. //把查询条件作为参数放入handle 参数URL
  40. //通过pageHandler返回page 对象
  41. page = pageHandler.handle(URL.valueOf(request.getRequestURL().toString()
  42. + (query == null || query.length() == 0 ? "" : "?" + query)));
  43. } catch (Throwable t) {
  44. logger.warn(t.getMessage(), t);
  45. String msg = t.getMessage();
  46. if (msg == null) {
  47. msg = StringUtils.toString(t);
  48. }
  49. if (isHtml) {
  50. writer.println("<table>");
  51. writer.println("<thead>");
  52. writer.println(" <tr>");
  53. writer.println(" <th>Error</th>");
  54. writer.println(" </tr>");
  55. writer.println("</thead>");
  56. writer.println("<tbody>");
  57. writer.println(" <tr>");
  58. writer.println(" <td>");
  59. writer.println(" " + msg.replace("<", "&lt;").replace(">", "&lt;").replace("\n", "<br/>"));
  60. writer.println(" </td>");
  61. writer.println(" </tr>");
  62. writer.println("</tbody>");
  63. writer.println("</table>");
  64. writer.println("<br/>");
  65. } else {
  66. writer.println(msg);
  67. }
  68. }
  69. if (page != null) {
  70. if (isHtml) {
  71. //通过handler方法放回的page对象构造html
  72. String nav = page.getNavigation();
  73. if (nav == null || nav.length() == 0) {
  74. nav = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(pageHandler);
  75. nav = nav.substring(0, 1).toUpperCase() + nav.substring(1);
  76. }
  77. if (!"index".equals(uri)) {
  78. nav = "<a href=\"/\">Home</a> &gt; " + nav;
  79. }
  80. //绘制菜单部分
  81. writeMenu(request, writer, nav);
  82. //绘制表格部分
  83. writeTable(writer, page.getTitle(), page.getColumns(),
  84. page.getRows());
  85. } else {
  86. if (page.getRows().size() > 0 && page.getRows().get(0).size() > 0) {
  87. writer.println(page.getRows().get(0).get(0));
  88. }
  89. }
  90. }
  91. } else {
  92. //没有pageHanlder 实现提示 Not found
  93. if (isHtml) {
  94. writer.println("<table>");
  95. writer.println("<thead>");
  96. writer.println(" <tr>");
  97. writer.println(" <th>Error</th>");
  98. writer.println(" </tr>");
  99. writer.println("</thead>");
  100. writer.println("<tbody>");
  101. writer.println(" <tr>");
  102. writer.println(" <td>");
  103. writer.println(" Not found " + uri + " page. Please goto <a href=\"/\">Home</a> page.");
  104. writer.println(" </td>");
  105. writer.println(" </tr>");
  106. writer.println("</tbody>");
  107. writer.println("</table>");
  108. writer.println("<br/>");
  109. } else {
  110. writer.println("Not found " + uri + " page.");
  111. }
  112. }
  113. if (isHtml) {
  114. writer.println("</body></html>");
  115. }
  116. //写到客户端
  117. writer.flush();
  118. }
  119. }

通过上面的代码可以知道,serlvet是通过分析uri的请求路径,动态加载相应的PageHandler并通过调用其hanlder方法,来获取页面要展示的数据的。

这里看下PageHandler一个具体扩展实现ProvidersPageHandler,它的hanlder方法如下:

  1. public Page handle(URL url) {
  2. //通过url获取一些服务的基本信息
  3. String service = url.getParameter("service");
  4. String host = url.getParameter("host");
  5. String application = url.getParameter("application");
  6. if (service != null && service.length() > 0) {
  7. List<List<String>> rows = new ArrayList<List<String>>();
  8. //重点在这,对之前订阅信息的使用
  9. //通过 RegistryContainer.getInstance().getProvidersByService方法
  10. //获取 RegistryContainer容器通过订阅注册中心获取的 服务消费者和提供者信息
  11. List<URL> providers = RegistryContainer.getInstance().getProvidersByService(service);
  12. if (providers != null && providers.size() > 0) {
  13. for (URL u : providers) {
  14. List<String> row = new ArrayList<String>();
  15. String s = u.toFullString();
  16. row.add(s.replace("&", "&amp;"));
  17. row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?service=" + service + "&provider=" + URL.encode(s) + "';}\">Unregister</button>");
  18. rows.add(row);
  19. }
  20. }
  21. //Page 对象中,主要是导航,标题,表格列,行信息,对应着我们文章开头的图解说明
  22. return new Page("<a href=\"services.html\">Services</a> &gt; " + service
  23. + " &gt; Providers | <a href=\"consumers.html?service=" + service
  24. + "\">Consumers</a> | <a href=\"statistics.html?service=" + service
  25. + "\">Statistics</a> | <a href=\"charts.html?service=" + service
  26. + "\">Charts</a>", "Providers (" + rows.size() + ")",
  27. new String[]{"Provider URL:", "Unregister"}, rows);
  28. } else if (host != null && host.length() > 0) {
  29. List<List<String>> rows = new ArrayList<List<String>>();
  30. List<URL> providers = RegistryContainer.getInstance().getProvidersByHost(host);
  31. if (providers != null && providers.size() > 0) {
  32. for (URL u : providers) {
  33. List<String> row = new ArrayList<String>();
  34. String s = u.toFullString();
  35. row.add(s.replace("&", "&amp;"));
  36. row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?host=" + host + "&provider=" + URL.encode(s) + "';}\">Unregister</button>");
  37. rows.add(row);
  38. }
  39. }
  40. 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() + ")",
  41. new String[]{"Provider URL:", "Unregister"}, rows);
  42. } else if (application != null && application.length() > 0) {
  43. List<List<String>> rows = new ArrayList<List<String>>();
  44. List<URL> providers = RegistryContainer.getInstance().getProvidersByApplication(application);
  45. if (providers != null && providers.size() > 0) {
  46. for (URL u : providers) {
  47. List<String> row = new ArrayList<String>();
  48. String s = u.toFullString();
  49. row.add(s.replace("&", "&amp;"));
  50. row.add("<button onclick=\"if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?application=" + application + "&provider=" + URL.encode(s) + "';}\">Unregister</button>");
  51. rows.add(row);
  52. }
  53. }
  54. 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() + ")",
  55. new String[]{"Provider URL:", "Unregister"}, rows);
  56. } else {
  57. throw new IllegalArgumentException("Please input service or host or application parameter.");
  58. }
  59. }

最后

由消费者和服务提供者上报的动态调用信息,是以文件形式存在硬盘上的,包括图表以png形式(由jfreechart工具生成),
存储目录是dubbo.jetty.directory配置指定的。这部分工作是由SimpleMonitorService实现的。

  1. public SimpleMonitorService() {
  2. queue = new LinkedBlockingQueue<URL>(Integer.parseInt(ConfigUtils.getProperty("dubbo.monitor.queue", "100000")));
  3. //后台守护线程
  4. writeThread = new Thread(new Runnable() {
  5. public void run() {
  6. while (running) {
  7. try {
  8. write(); // write statistics 写统计文件
  9. } catch (Throwable t) {
  10. logger.error("Unexpected error occur at write stat log, cause: " + t.getMessage(), t);
  11. try {
  12. Thread.sleep(5000); // retry after 5 secs
  13. } catch (Throwable t2) {
  14. }
  15. }
  16. }
  17. }
  18. });
  19. writeThread.setDaemon(true);
  20. writeThread.setName("DubboMonitorAsyncWriteLogThread");
  21. writeThread.start();
  22. //线程池
  23. chartFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
  24. public void run() {
  25. try {
  26. draw(); // draw chart 画图
  27. } catch (Throwable t) {
  28. logger.error("Unexpected error occur at draw stat chart, cause: " + t.getMessage(), t);
  29. }
  30. }
  31. }, 1, 300, TimeUnit.SECONDS);
  32. statisticsDirectory = ConfigUtils.getProperty("dubbo.statistics.directory");
  33. chartsDirectory = ConfigUtils.getProperty("dubbo.charts.directory");
  34. }

具体生成的文件这里简单切几个图,配置文件是存${user.home}/monitor目录下

monitor目录下有 charts 和statistics两个目录,分别存放图片和统计数据文件的。

这里在日期+接口名+方法名 格式的目录下,有生成好的图表文件,形如

下面两个图是statistics目录下统计文件和文件具体内容

 

转载于:https://my.oschina.net/u/146130/blog/1649835

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/169645
推荐阅读
相关标签
  

闽ICP备14008679号