深入理解jmx_exporter,java如何高效暴露监控指标!

2,086 阅读2分钟

JMX Exporter 配置说明

配置可以参考文档 github.com/prometheus/…

配置结合源码

exporter通过MBeanServer获取MBeanName,查询条件为whitelistObjectNames配置的值,若是没有配置值,则默认查询全部beanName, whitelistObjectNames的配置可以支持正则表达式,若配置的值为精确值,则直接查询Mbean直接返回。

读取whitelistObjectNames配置

    if (yamlConfig.containsKey("whitelistObjectNames")) {
      List<Object> names = (List<Object>) yamlConfig.get("whitelistObjectNames");
      for(Object name : names) {
        cfg.whitelistObjectNames.add(new ObjectName((String)name));
      }
    } else {
      //没有配置则加入null进whitelistObjectNames列表中
      cfg.whitelistObjectNames.add(null);
    }

当我们调用exporter暴露的接口时,会调用JmxScraper的 doScrape()方法

public void doScrape() throws Exception {
        MBeanServerConnection beanConn;
        JMXConnector jmxc = null;
        //若没有配置jmxUrl则本地获取连接
        if (jmxUrl.isEmpty()) {
          beanConn = ManagementFactory.getPlatformMBeanServer();
        } else {
          //若配置jmxUrl则远程连接
          Map<String, Object> environment = new HashMap<String, Object>();
          if (username != null && username.length() != 0 && password != null && password.length() != 0) {
            String[] credent = new String[] {username, password};
            environment.put(javax.management.remote.JMXConnector.CREDENTIALS, credent);
          }
          if (ssl) {
              environment.put(Context.SECURITY_PROTOCOL, "ssl");
              SslRMIClientSocketFactory clientSocketFactory = new SslRMIClientSocketFactory();
              environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientSocketFactory);
              environment.put("com.sun.jndi.rmi.factory.socket", clientSocketFactory);
          }

          jmxc = JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl), environment);
          beanConn = jmxc.getMBeanServerConnection();
        }
        try {
            // Query MBean names, see #89 for reasons queryMBeans() is used instead of queryNames()
            Set<ObjectName> mBeanNames = new HashSet<ObjectName>();
            //若是whitelistObjectNames没有配置,默认配置为[null],beanConn.queryMBeans(null,null)查全部mBeanName,
            //配置了whitelistObjectNames则按名称检索
            for (ObjectName name : whitelistObjectNames) {
                for (ObjectInstance instance : beanConn.queryMBeans(name, null)) {
                    mBeanNames.add(instance.getObjectName());
                }
            }
            //若是配置了blacklistObjectNames则移除黑名单中的MbeanName,由此可以看出,blacklistObjectNames的配置只是影响接口返回参数,但是检索MbeanName还是正常检索
            for (ObjectName name : blacklistObjectNames) {
                for (ObjectInstance instance : beanConn.queryMBeans(name, null)) {
                    mBeanNames.remove(instance.getObjectName());
                }
            }

            // Now that we have *only* the whitelisted mBeans, remove any old ones from the cache:
            jmxMBeanPropertyCache.onlyKeepMBeans(mBeanNames);

            for (ObjectName objectName : mBeanNames) {
                long start = System.nanoTime();
                //通过beanName查询对应的指标
                scrapeBean(beanConn, objectName);
                logger.fine("TIME: " + (System.nanoTime() - start) + " ns for " + objectName.toString());
            }
        } finally {
          if (jmxc != null) {
            jmxc.close();
          }
        }
    }

跟踪queryMBeans方法,最后会调Register的query()用方法

public Set<NamedObject> query(ObjectName pattern, QueryExp query) {

        final Set<NamedObject> result = new HashSet<NamedObject>();

        // The following filter cases are considered:
        // null, "", "*:*" : names in all domains
        // ":*", ":[key=value],*" : names in defaultDomain
        // "domain:*", "domain:[key=value],*" : names in the specified domain

        // Surely one of the most frequent cases ... query on the whole world
        ObjectName name;
        //若是白名单中的beanName为空
        if (pattern == null ||
            pattern.getCanonicalName().length() == 0 ||
            pattern.equals(ObjectName.WILDCARD))
           name = ObjectName.WILDCARD;
        else name = pattern;

        lock.readLock().lock();
        try {

            // If pattern is not a pattern, retrieve this mbean !
            if (!name.isPattern()) {
                final NamedObject no = retrieveNamedObject(name);
                if (no != null) result.add(no);
                return result;
            }

            // All names in all domains
            if (name == ObjectName.WILDCARD) {
                for (Map<String,NamedObject> moiTb : domainTb.values()) {
                    result.addAll(moiTb.values());
                }
                return result;
            }

            final String canonical_key_property_list_string =
                    name.getCanonicalKeyPropertyListString();
            final boolean allNames =
                    (canonical_key_property_list_string.length()==0);
            final ObjectNamePattern namePattern =
                (allNames?null:new ObjectNamePattern(name));

            // All names in default domain
            if (name.getDomain().length() == 0) {
                final Map<String,NamedObject> moiTb = domainTb.get(domain);
                if (allNames)
                    result.addAll(moiTb.values());
                else
                    addAllMatching(moiTb, result, namePattern);
                return result;
            }

            if (!name.isDomainPattern()) {
                final Map<String,NamedObject> moiTb = domainTb.get(name.getDomain());
                if (moiTb == null) return Collections.emptySet();
                if (allNames)
                    result.addAll(moiTb.values());
                else
                    addAllMatching(moiTb, result, namePattern);
                return result;
            }

            // Pattern matching in the domain name (*, ?)
            final String dom2Match = name.getDomain();
            for (String dom : domainTb.keySet()) {
                if (Util.wildmatch(dom, dom2Match)) {
                    final Map<String,NamedObject> moiTb = domainTb.get(dom);
                    if (allNames)
                        result.addAll(moiTb.values());
                    else
                        addAllMatching(moiTb, result, namePattern);
                }
            }
            return result;
        } finally {
            lock.readLock().unlock();
        }
    }

从上面的代码中可以得到结论: 配置 whitelistObjectNames参数可以使查询bean的效率能够提高,避免全部查询。