通过这篇文章,我们可以了解到,利用 JMX 技术可以方便获取 Tomcat 监控情况。但是我们采用自研的框架而非大家常见的 SpringBoot,于是就不能方便地通过设置配置开启 Tomcat 的 JMX,——尽管我们也是基于 Tomcat 的 Web 容器,而是还是 SpringMVC。
在笔者一番尝试下,终于实现了“Enable Embedded Tomcat JMX Programmatically”,所谓 Programmatically 就是编程式的用 Java 代码去配置。实际情况也很简单,就是在 Tomcat 启动的LifecycleEvent
事件中加入:
context.addLifecycleListener((LifecycleEvent event) -> {
if (isStatedSpring || (event.getLifecycle().getState() != LifecycleState.STARTING_PREP))
return;
BaseWebInitializer.coreStartup(context.getServletContext(), clz);
// anotherWayToStartStrping();
if (isEnableJMX) {
try {
LocateRegistry.createRegistry(9011);
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9011/jmxrmi"),
null,
ManagementFactory.getPlatformMBeanServer()
);
cs.start();
LOGGER.info("成功启动 JMXConnectorServer");
} catch (IOException e) {
e.printStackTrace();
}
}
isStatedSpring = true;
springTime = System.currentTimeMillis() - startedTime;
});
要注意的是端口的配置,当前是 9011。
另外如果要鉴权,把newJMXConnectorServer()
的第二个参数environment
由null
改为一个 map。
哦,对了,还有 Maven 的依赖,——貌似 8 最高的也就这个版本。
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>tomcat-catalina-jmx-remoteartifactId>
<version>8.5.75version>
<type>jartype>
dependency>
刚才的配置就像一个服务端,而接着我们这一步就相当于是客户端的连接。先准备好连接地址:
String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:9011/jmxrmi";
// 如果要鉴权,还要配置下面的,传入到 environment
Map<String, String[]> map = new HashMap<>();
String[] credentials = new String[]{"monitorRole", "tomcat"};
map.put("jmx.remote.credentials", credentials);
进行连接:
MBeanServerConnection msc = JMXConnectorFactory.connect(new JMXServiceURL(jmxURL)).getMBeanServerConnection();
成功连接后,会返回大量的信息。JMX 提供了一种 Domain 命名空间的概念,是为第一的大分类。我们可以打印 Domains 出来,再用getObjectNamesByDomain()
列出子类 :
for (String domain : msc.getDomains())
System.out.println(domain);
List<Node> tomcat = MonitorUtils.getObjectNamesByDomain(msc, "Tomcat");
Node 是我们对结果的封装,实际最重要是里面的fullName
即对应 JMX API 的CanonicalName
,它就是对象名称 ObjectName,以此来获取具体的属性。
ObjectName threadObjName = new ObjectName("Tomcat:name=\"http-nio-8301\",type=ThreadPool");
System.out.println("currentThreadCount:" + msc.getAttribute(threadObjName, "currentThreadCount"));// tomcat的线程数对应的属性值
因为是自定义的 Tomcat,所以 ObjectName 会不一样。常见的 Tomcat 是
ObjectName threadObjName = new ObjectName("Catalina:type=ThreadPool,name=http-8301");// 端口最好是动态取得
于是就必须通过前面说的getObjectNamesByDomain()
“人肉”查找。另外也要注意 Tomcat 的端口配置。
只要能成功连接并获取 JMX 信息,下一步就是将其转换为监控信息渲染到前端。