之前写过一篇用容器技术快速构建jvm监控服务的文章,那篇侧重讲的是实现,说的是“怎么做”的问题。这篇会去探讨“为什么”,算是对前一篇的补充。
什么是监控
软件技术虽然层出不穷,但本质上大同小异。
在讲JVM监控之前,我们可以先类比一下现实生活经常可以看到的视频监控。视频监控一般由摄像头,网络传输线,视频服务器,监控终端这几个组件构成。摄像头用来采集视频数据(有些摄像头自带转码,有些会有另外的转码器),网络传输线用来传输数据,视频服务器用来处理和存储数据,监控终端用来展示视频图像。在这里你可以看到,监控的整个工作流,其实就是在跟数据打交道。只不过各个环节打交道的方式不同。JVM监控也是如此,它的整个工作流也是围绕数据进行。
JMX
监控的第一步也是最重要的一步就是采集数据。假如现在让你采集一个程序的运行时信息,你会在程序里预先写好数据采集的代码,常见的场景如打日志。我们可以定义把日志打到文件里,标准输出里,网络协议里等等。jdk在的java.lang.management包中实现了采集JVM状态数据的功能。java代码很简单,真正实现数据采集功能的是底层的native方法。这里不做过多讨论。但是问题来了,并不是每个人在任何时候都需要监控JVM的状态。也并不是每个JVM都需要被监控。所以我们需要一种动态机制,能够让我们选择用不用,在什么时候用,以何种频率用。在我们自己写程序的时候,通常使用配置文件或者环境变量等方式来达到这种目的。jdk则提供JMX来解决动态管理的问题。
JMX技术的实现分为三层架构。第一层叫Instrumentation,用于管理使用JMX的资源。JMX定义:如果一个java类是使用JMX技术的,那么这个类的名字必须要以MBean或者MXBean结尾。所以第一层的工作就是发现MBean,并把它们注册到第二层,JMX Agent。JMX Agent可以控制注册在其上的资源,也就是一些java类。并使得外部的管理程序可以使用这些资源。第三层叫Remote management,可以理解为用于帮助MBeanServer与外部管理程序连接的组件。如果要开启JMX的功能,只需要在启动时设置com.sun.management.jmxremote.porty以及相关的参数即可。下面这段代码简单演示了如何获取一个JVM的内存使用态:
在上面的例子中,我们看到了某一时间点上的JVM内存使用情况。这是一种监控吗?在特殊的情况下是的。有时候需要获取某一时间点的JVM状态。比如说在系统发生宕机前,hook在shutdown signal的JVM监控,可以帮助分析系统为什么宕机。但在通常情况下,我们所说的监控,是对事物状态的连续观测。我们可以把上面的代码定义成一个job,用quartz这样的框架去定时执行。但这样费时费力。更好的选择是用那些已经比较成熟的框架,谷歌的jmxTrans就是其中之一。有兴趣的同学可以去了解一下。
数据处理
实现了JVM状态数据的持续采集之后该怎么做呢。一种选择是我们可以把数据稍加处理,在前台页面展示成dashbord,就像jconsole那样。但如果只是做到像jconsole那样,是不能被称为监控服务的。一旦前端展示页面的进程退出,历史的JVM状态将无法追溯。而我们经常需要追溯历史数据。所以监控数据需要被存储,监控服务才能更稳定。我们依旧可以用自己的方法把数据直接存储,然后再抽取存储的数据,稍加处理传给前台的展示页面。我们可以用es,mongoDB甚至是mysql等来完成这样的工作。只要你不嫌麻烦,心中所念:所谓监控无非就是处理数据,你一定能成功。但市面上已经有很多工具可以很好地完成这样的工作,而且完成的还很好,很方便。你大可不必再费心费力自己实现一套。我们只需要稍加利用这些框架就好。在我搭建JVM监控的时候,我用的是graphite。这是一款比较易用的工具,外部的易用一定意味着其内部的精心设计与大量工作。有兴趣的同学可以自行查阅。
数据展示
接下来就是数据展示的问题了,其实graphite本身是有数据展示的web服务的。它的优点是,图表都是自动生成,无需配置。它的缺点是丑,不清晰,看着累。所以可以把graphite作为数据源,在grafana中展示。grafana可能大家都听说过,但很多人对它有误解。以为可以单独用它来完成监控,其实不是的。它主要还是被用作监控报表,报表的原始数据来自配置的数据源。
总结:
回过头来看,要完成JVM监控其实还是很容易的。如果不出什么幺蛾子,搭建的过程无非就是安装和配置的过程。如果你用容器来完成,那简直就跟搭积木一样简单了。但我要说的是,幺蛾子肯定是会出现的,只是时间的问题。要想搭出健壮的监控系统,一定需要勤学苦练。仅仅知道本文说的“为什么”问题也许还不够。如果有机会,下次再来深入探讨下“是什么”的问题。