Zabbix/J的源代码解析

Zabbix/J是一种能够将Zabbix监控整合入Java应用程序的开源框架,使得技术人员能够通过Zabbix,针对整个系统的基础设施、应用软件/中间件和业务系统进行全方位的分层监控。

Zabbix/J的简介和示例代码在《一种Java实现的Zabbix监控框架——Zabbix/J》中有着详细的介绍,本文将对Zabbix/J的源码进行较为详细的分析。

一、Zabbix/J的依赖和包结构

1. 依赖包

Zabbix/J使用Maven来管理依赖包,所有的依赖关系都存放在/zabbixj-parent/zabbixj/pom.xml文件中。

Zabbix/J依赖的jar包如下图所示:

ZabbixJ的依赖关系

Zabbix/J的依赖树如下图所示:

Zabbix/J的依赖树

2. 包结构

Zabbix/J源码的包结构如下图所示:

ZabbixJ源码的包结构

上图中每个包的作用如下所述:

  • com.quigley.zabbixj.agent
    这个包主要实现了Java版本的Zabbix探针,其中的两个子包activepassive分别实现了探针的主动模式和被动模式。

  • com.quigley.zabbixj.client
    这个包主要实现了被动式探针的客户端,这个客户端既可以获取Zabbix/J探针的监控数据,也可以获取Zabbix原生探针的监控数据。

  • com.quigley.zabbixj.metrics
    这个包主要实现了Zabbix监控项的数据结构,包括监控项关键字、监控项容器、监控项异常和监控数据提供者接口。

  • com.quigley.zabbixj.providers
    这个包主要实现了一个能够提供JVM监控数据的监控数据提供者,以后还可以根据实际需要,向这个包添加适用于自己的监控数据提供者的实现类。

3. UML类图

Zabbix/J的UML(统一建模语言)类图如下图所示:

Zabbix/J的UML类图

由上图可知,Zabbix/J的探针(由ZabbixAgent类实现)具有两种工作模式:被动模式(由ListenerThread类实现)和主动模式(由ActiveThread类实现)。在被动模式中,监听线程会开启5个工作线程(由WorkerThread类实现),用于处理外界的监控数据查询请求。主动模式还包含一个内部类(ActiveChecksResponseIndex),用于存储Zabbix/J探针向Zabbix服务器请求的主动式监控项列表的响应信息的索引数据。

Zabbix/J还包含了一个被动式探针的客户端(由PassiveAgentClient类实现),这个客户端既可以向Zabbix/J的被动式探针请求查询监控数据,也可以向Zabbix原生的被动式探针请求查询监控数据。

主动模式和被动模式各自包含一个监控数据提供者的容器(由MetricsContainer类实现),这个容器实际上是一个Map结构,key是监控项关键字,value是监控数据提供者的实例对象。监控项关键字用于区分不同的监控项,它具有两种表现形式,一种是字符串,另一种是MetricsKey的实例对象。MetricsKey对象会将监控项关键字拆分成三部分存储,分别是提供者名称(成员变量provider)、关键字(成员变量key)和参数列表(成员变量parameters)。每个监控数据提供者的实现类都要实现一个名为MetricsProvider的接口,需要实现这个接口的getValue()方法,而监控数据实际上正是由这个方法提供的。Zabbix/J提供了一个MetricsProvider接口的实现类,名为JVMMetricsProvider,这个类能够提供空闲堆内存、最大分配堆内存、堆内存总量、操作系统名称和操作系统架构等监控数据。

Zabbix/J还包含两个异常类:MetricsExceptionZabbixException。这两种异常都继承了RuntimeException异常,前者包含了监控项关键字的相关处理异常,而后者则包装了所有其他的异常信息。

二、Zabbix/J源码分析

本文将会分析每个包中所有类的源码,由于源码比较冗长,因此只会分析关键部分的代码。

1. com.quigley.zabbixj

由Zabbix/J的UML类图可知,com.quigley.zabbixj包只有一个名为ZabbixException的异常类。这个异常继承了RuntimeException运行时异常。如果主动式探针的JSON缓冲区发生异常,或者被动式探针的客户端和探针进行通信时发生IO异常,那么便会抛出ZabbixException异常。

2. com.quigley.zabbixj.agent

2.1 ZabbixAgent

这个类是Zabbix/J的核心类,它实现了一个Java版本的Zabbix探针。由Zabbix/J的UML类图可知,ZabbixAgent类的成员变量如下所述:

  • enablePassive:被动模式的启用/禁用开关。

  • listenAddress:被动式探针的监听IP地址。

  • listenPort:被动式探针的监听端口号。

  • enableActive:主动模式的启用/禁用开关。

  • hostName:主动式探针的主机名,必须和在Zabbix前端页面上注册的主机名完全相同。

  • serverAddress:Zabbix服务器的IP地址。

  • serverPort:Zabbix服务器监听的端口号。

  • refreshInterval:主动式探针请求刷新主动式监控项列表的时间间隔。

  • metricsContainer:监控数据提供者的容器,实际上就是一个Map结构,Key是提供者名称,Value是提供者对象。

  • listenerThread:被动模式的监听线程。

  • activeThread:主动模式的工作线程。

如果要启动探针,那么首先需要实例化ZabbixAgent对象,然后根据工作模式和实际情况配置探针的各项参数,最后调用agent.start()方法即可。start()方法的源码如下图所示:

ZabbixAgent的start方法

由上图中的代码可知,探针启动时:

  • 如果工作在被动模式下,则实例化和启动监听线程(线程对象名为listenerThread);
  • 如果工作在主动模式下,则实例化和启动主动工作线程(线程对象名为activeThread)。

如果要停止探针,则调用agent.stop()方法即可。stop()方法的源码如下图所示:

ZabbixAgent的stop方法

由上图中的代码可知,探针停止时:

  • 如果工作在被动模式下,那么就关闭监听线程(调用listenerThread.shutdown()方法,该方法会将运行标志running设置为false),然后等待这个线程结束运行;
  • 如果工作在主动模式下,那么就关闭主动工作线程(调用activeThread.shutdown()方法,该方法会将运行标志running设置为false),然后等待这个线程结束运行。
2.2 ListenerThread

ListenerThread类是Zabbix/J探针在被动模式下的监听线程的实现类。由Zabbix/J的UML类图可知,这个类的成员变量如下所述:

  • running:监听线程运行开关标志。

  • container:监控数据提供者的容器,本质上是个Map结构。

  • serverSocket:用于和Zabbix服务器建立TCP连接的套接字。

监听线程的主要工作流程都是在run()方法中实现的,源码如下图所示:

监听线程的run()方法

由上图可知,监听线程的工作流程主要有以下几个步骤:

Step-1 检查监听线程是否正在运行(running标志是否为true)。

Step-2 若监听线程正在运行,则接受Zabbix服务器的TCP连接请求;若监听线程停止运行,则跳转至Step-6。

Step-3 实例化工作线程的对象,设置监控数据提供者容器和客户端套接字。

Step-4 启动工作线程。

Step-5 跳转至Step-1,迭代下一次循环。

Step-6 工作流程结束。

注意,当accept()方法超时的时候,Zabbix/J每秒钟都会发生SocketTimeoutException异常,这样是为了能够有机会跳出阻塞的accept()方法,从而能够干净地关闭相应的网络连接。被动式探针的详细工作流程,请参考《Zabbix探针工作模式解析》中的被动模式的相关章节。

2.3 WorkerThread

WorkerThread类是由监听线程新建的工作线程的实现类,这些工作线程会收集Zabbix服务器请求的各项监控数据。由Zabbix/J的UML类图可知,这个类的成员变量如下所述:

  • container:监控数据提供者的容器,本质上是个Map结构。

  • socket:探针连接至Zabbix服务器的TCP套接字。

工作线程的主要工作流程都是在run()方法中实现的,源码如下图所示:

WorkerThread的run()方法

由上图可知,工作线程的工作流程主要有以下几个步骤:

Step-1 从TCP套接字中获取客户端的IP地址,并输出至日志。

Step-2 将套接字的Socket超时时间设置为1秒钟。

Step-3 从输入流读取一行字符串,这个字符串便是监控项关键字。

Step-4 判断是不是Zabbix服务器发来的监控项关键字:若是,则取第13个字符至最后1个字符作为监控项关键字;若不是,则不作任何处理。

Step-5 若监控项关键字不为空,则根据监控项关键字获取相应的监控数据,然后发送给请求查询监控数据的客户端;若监控项关键字为空,则不作任何处理。

Step-6 关闭输入流和输出流。

Step-7 断开TCP连接。

注意,Step-1中的客户端可以是Zabbix服务器,也可以是Zabbix/J被动式探针的客户端。Step-4中判断是不是Zabbix服务器发送的监控项关键字,其实就是判断前4字节是不是“ZBXD”前导字符。

2.4 ActiveThread

ActiveThread类是Zabbix/J探针在主动模式下的主动工作线程的实现类。由Zabbix/J的UML类图可知,这个类的成员变量如下所述:

  • running:主动工作线程运行开关标志。

  • checks:监控数据的更新时间间隔,它实际上是一个Map结构。key是更新时间间隔,对应于在Zabbix前端页面中配置的主动式监控项的更新时间间隔;value是主动式监控项关键字的列表。

  • lastChecked:上次更新时间,它实际上是一个Map结构。key是更新时间间隔,对应于在Zabbix前端页面中配置的主动式监控项的更新时间间隔;value是上次更新时间。

  • lastRefresh:上次请求刷新主动式监控项列表的时间。

  • metricsContainer:监控数据提供者容器,它实际上是一个Map结构。key是监控项关键字,value是监控数据提供者的实例对象。

  • hostName:运行主动式探针的服务器的主机名,对应于探针配置文件中的Hostname参数。需要和在Zabbix前端页面中配置的监控服务器的主机名完全相同。

  • serverAddress:Zabbix服务器的主机名或IP地址,对应于探针配置文件中的ServerActive参数。

  • serverPort:Zabbix服务器的监听端口,默认为10051。

  • refreshInterval:请求刷新主动式监控项列表的时间间隔,对应于探针配置文件中的RefreshActiveChecks参数

除了成员变量之外,ActiveThread还有一个名为ActiveChecksResponseIndex的内部类。这个内部类的实例对象用于存储主动式探针从Zabbix服务器获取的主动式监控项列表的索引信息,它包含两个成员变量:

  • index:主动式监控项的索引数据,它实际上是一个Map结构。key是监控项关键字,value是对应的监控数据更新时间间隔。

  • delays:监控数据更新时间间隔的列表。

主动工作线程的主要工作流程都是在run()方法中实现的,源码如下图所示:

ActiveThread的run()方法

由上图可知,主动工作线程的工作流程主要有以下几个步骤:

Step-1 首次向Zabbix服务器请求主动式监控项的列表。

Step-2 检查主动工作线程是否正在运行(running标志是否为true)。

Step-3 若主动工作线程正在运行,则该线程休眠1秒钟;若主动工作线程停止运行,则跳转至Step-11。

Step-4 获取系统当前时间。

Step-5 判断系统当前时间和上次刷新时间的差值是否大于等于刷新间隔时间:若大于等于,则向Zabbix服务器请求主动式监控项的列表;若反之,则不作任何处理。

Step-6 从监控数据的更新时间间隔列表中获取一个更新时间间隔。

Step-7 根据上一步获取的更新时间间隔,在上次更新时间的Map中获取相应的监控数据上次更新的时间。

Step-8 判断系统当前时间和上次监控数据更新时间的差值是否大于等于更新时间间隔:若大于等于,则获取相应监控项的监控数据,然后发送给Zabbix服务器;若反之,则不作任何处理。

Step-9 跳转至Step-6,迭代处理下一个更新时间间隔。

Step-10 跳转至Step-2,迭代下一次循环。

Step-11 工作流程结束。

由Zabbix/J的UML类图可知,除了上述的主要工作流程之外,ActiveThread类还包含一些私有的辅助方法,此处只简述这些方法的大致作用:

  • requestActiveChecks()
    向Zabbix服务器请求主动式监控项列表。

  • refreshFromActiveChecksResponse()
    合并和修整主动式探针的监控项配置,确保和Zabbix服务器的监控项配置一致。这个方法会按顺序执行getActiveChecksResponseIndex()insertNewChecks()pruneChangedChecks()pruneUnusedDelays()这四个私有方法。

  • getActiveChecksResponseIndex()
    获取主动式监控项列表的索引。

  • insertNewChecks()
    向本地添加新获取的监控数据更新时间间隔配置,并且修改相应的上次更新时间。

  • pruneChangedChecks()
    修整当前的监控数据更新时间间隔的配置。

  • pruneUnusedDelays()
    修整当前未使用的更新时间间隔,最终确保本地的主动式监控项配置和Zabbix服务器保持一致。

  • sendMetrics()
    获取指定的更新时间间隔所关联的所有主动式监控项的监控数据,然后批量发送给Zabbix服务器。

  • getRequest()
    当主动式探针向Zabbix服务器请求刷新主动式监控项列表或发送监控数据时,将json类型的请求数据转换为字节数组类型。

  • getResponse()
    当主动式探针接收Zabbix服务器发送的主动式监控项列表或监控数据处理结果时,将字节数组类型的返回数据转换为json类型。

主动式探针的详细工作流程,请参考《Zabbix探针工作模式解析》中的主动模式的相关章节。

3. com.quigley.zabbixj.client

这个包中只有一个名为PassiveAgentClient的类,它是Zabbix/J被动式探针的客户端的实现类。由Zabbix/J的UML类图可知,这个类具有两个成员变量,如下所述:

  • agentAddress:被动式探针所在主机的IP地址。

  • port:被动式探针监听的端口号,默认为10050。

PassiveAgentClient的实例对象不仅能够请求Zabbix/J的被动式探针查询监控数据,还可以请求Zabbix原生的被动式探针查询监控数据,它的主要工作流程都在getValues()方法中,源码如下图所示:

PassiveAgentClient的getValues方法

由上图可知,getValues()方法的输入参数为一个监控项关键字的列表;返回值为一个Map结构,key是监控项关键字,value是相应的监控数据。客户端请求查询监控数据的流程如下所述:

Step-1 从输入参数的列表中获取一个监控项关键字。

Step-2 向Zabbix/J的被动式探针或Zabbix原生的被动式探针请求建立TCP连接,参数为探针主机的IP地址和监听端口号。

Step-3 将监控项关键字转换为字节数组类型的请求数据。

Step-4 通过输出流,向探针发送请求数据。

Step-5 通过输入流,接收探针返回的响应数据,这些响应数据就是监控数据。

Step-6 判断监控数据是不是Zabbix的原生探针返回的:若是,则去掉开头4字节的“ZBXD”前导字符,取响应数据的第13到最后一个字符作为监控数据;若不是,则不作任何处理。

Step-7 判断监控数据的类型(只能是长整数、浮点数或字符串类型),进行相应的类型转换,然后以监控项关键字为key,监控数据为value,放入需要返回的Map接口中。

Step-8 返回Step-1,迭代处理下一个监控项关键字,直至处理完列表中的所有条目。

Step-9 返回表示监控数据的Map结构。

PassiveAgentClient类的使用方法请参考《一种Java实现的Zabbix监控框架——Zabbix/J》。

4. com.quigley.zabbixj.metrics

4.1 MetricsKey

监控项关键字具有两种表示方法,一种是字符串,另一种是MetricsKey类的实例对象。由Zabbix/J的UML类图可知,MetricsKey类的实例对象会将监控项关键字拆分为三部分存储,如下所述:

  • provider:监控数据提供者的名称。

  • key:关键字。

  • parameters:参数列表。

可以通过MetricsKey实例对象的parseKey()方法将字符串格式的监控项关键字转换为MetricsKey的存储格式,源码如下图所示:

MetricsKey的parseKey方法

由于转换过程较为简单明晰,此处就不再赘述了。注意,字符串格式的监控项关键字,它的关键字分隔符(对应上图源码中的KEY_SEPARATOR常量)是“.”字符,参数列表由“[]”括起来(对应上图源码中的PARAMS_STARTPARAMS_END常量),参数之间以“,”字符分隔(对应上图源码中的PARAMS_SEPARATOR常量)。

4.2 MetricsProvider

这是一个接口,它只有一个名为getValue()的抽象方法。由Zabbix/J的UML类图可知,所有的监控数据提供者都必须实现MetricsProvider接口的getValue()方法:

  • 输入参数:MetricsKey类型的监控项关键字。

  • 返回值:监控数据,只能是长整数、浮点数或字符串类型的。

Zabbix/J提供的JVMMetricsProvider类就是MetricsProvider类的一个实现类。

4.3 MetricsContainer

这是监控数据提供者的容器,它实际上是一个Map结构,key是监控数据提供者的名称,value是MetricsProvider接口的实例对象。

4.4 MetricsException

MetricsException异常继承了RuntimeException运行时异常。如果在处理MetricsKey格式的监控项关键字时发生了异常,那么便会将异常信息包装为MetricsException异常,然后向外抛出。

5. com.quigley.zabbixj.providers

在这个包中,Zabbix/J提供了MetricsProvider接口的一个实现类,名为JVMMetricsProvider。这个类实现了getValue()方法,源码如下图所示:

JVMMetricsProvider的getValue()方法

由上图可知,JVMMetricsProvider能够提供空闲堆内存、最大分配堆内存、堆内存总量、操作系统名称和操作系统架构等监控数据。

三、总结

至此,Zabbix/J的源码便全部分析完了。可以看出,对Zabbix/J的二次开发主要集中在实现MetricsProvider接口上,这个接口的实现类能够提供各种各样的监控数据。下一篇文章将会示例如何将Zabbix/J整合至一个最简化的Spring MVC框架之中,并且在Zabbix的前端页面上显示这个框架的监控数据。