Zabbix学习资源由浅入深 关于我们 联系我们 加入我们
5

Zabbix如何实现对大流量监控数据的高效处理?

01


监控数据的独立性—逻辑视角

微信图片_20220210153046.jpg


监控数据的第一个特点是具有相互独立性。在不同的数据处理阶段,这种独立性会体现在不同的层面,在数据采集阶段会体现在value层面,如果不考虑日志监控的话,同一监控项的多次数据采集之间不会互相影响,相当于是无状态的。在数据传输阶段,也就是从agent发送数据到server接收数据的阶段,独立性体现在host层面,不同的host使用不同的地址和接口进行数据传输,互不影响,而在同一个host内部,每次传输的数据往往包含多个监控项的值。


在随后的数据预处理阶段,独立性主要体现在监控项层面,每个监控值的预处理过程不会依赖于其他监控项,而在同一监控项内部则可能会依赖于前一个值以计算变化率。在最后的数据存储和触发器运算阶段,独立性主要体现在host层面,虽然触发器表达式允许跨host使用监控项,但是在实际应用中,使用最多的还是在同一个host内部的监控项之间构造表达式,所以触发器表达式的计算只需要使用同一host内部的监控数据。


上述的独立性是逻辑意义上的独立,而在数据处理的次序方面还存在一种限制性约束,即同一监控项的多次采集值构成一个时间序列,意味着这些值在时间维度上具有先后关系,那么Zabbix在预处理阶段和触发器表达式运算阶段至少需要保证同一监控项的值严格按照时间的先后顺序进行处理。


除了监控数据本身,Zabbix中的元数据(或者叫主数据)也体现出独立性,元数据主要包含host、item、trigger等信息,其独立性主要体现在host层面,因为item属性与host属性是关联的,而trigger又是与item关联的。所以每个host元数据相互关联,构成整体。然而,某个host属性的修改不会影响到其他host。


在Zabbix系统中所反映出的监控数据以及元数据之间的独立性,为并行和可扩展性提供了基础,可以说Zabbix监控系统非常适合基于数据分组的并行化和微服务化。


02

监控数据的热值以及热数据的规模


监控数据的另一个特点体现在监控数据的热值以及热数据的规模方面。如果将数据的每分钟访问次数定义为数据的热值,那么监控数据的热值与数据的存活时间(也就是年龄)直接相关。


在监控数据写入数据库之前,需要完成数据采集、数据传输和预处理,在这个时间段,Zabbix需要在最短的时间内快速处理完数据,因此其热值很高,但是这时的热数据范围很小,主要是每个监控项的当前值。而在触发器表达式计算阶段,当前值的处理会引起对该监控项下的多个历史值的访问,此时热数据的范围大大增加。如果某个触发器表达式需要使用最近60分钟的数据,那么热数据的范围就扩增到60分钟。


对于大部分监控项而言,随着时间流逝,监控数据会快速地脱离Zabbix server的使用范围,进入冷数据区。虽然冷数据脱离了Zabbix server的使用范围,但是Zabbix仍然不能对冷数据进行离线化,因为前端UI和网络API需要访问这些冷数据,这些访问一般仅限于少量监控项的读取。从Zabbix server的角度来说,对于任一特定监控项,数据的热区和冷区分界线是固定的,并且热区的数据量相对于冷数据要小得多。


如果使用mysql来存储监控数据,由于索引的存在,数据的写入性能会随着存储规模的增加而降低,查询性能也是一样。因此,控制数据规模成为解决数据库访问性能的关键。如果将热数据和冷数据分开存储,并且在需要时让数据在两者之间进行流动,则对于Zabbix server而言,可以大大降低数据规模。如果进一步考虑如何实现热数据表的最小化,就需要建立一种动态机制,每次修改触发器表达式后按照触发器的数据需求量在冷热数据之间进行双向调整。


以上的热值分析是针对监控数据,那么对于元数据来说,其总体访问频率主要取决于nvps值和元数据同步的频率,那么数据采集频率比较高的监控项,相关的元数据热值也更高。


Zabbix具体如何实现监控数据的高效处理?


03

Zabbixserver的数据IO

微信图片_20220210153048.jpg


Zabbix对数据的处理能力集中体现在Zabbix server端。总体上,Zabbix server采用流水线式的多进程架构来处理监控数据,其中比较关键的有3类进程,每一类又可以启动多个进程。这3类进程分别是:


  1. trapper/poller进程,负责接收监控数据;

  2. 预处理进程,负责对接收的监控数据进行预处理;

  3. history syncer进程负责将监控数据写入数据库以及计算触发器表达式并生成event。这3类进程之间可以通过共享内存和Unix域套接字实现进程间通信。这里没有展开讲lld进程,是因为lld进程处理的是底层自动发现的数据,并不是严格意义上的监控值。


既然进程之间是流水线式的协作,我们看一下监控数据在三类进程之间如何进行输入和输出。首先, Trapper和poller进程接收到的数据按照来源分为两种,一种是来自proxy端,一种是来自agent端。由于来自proxy端的数据已经由proxy完成了预处理,所以,这部分数据会直接输出到共享内存,供history syncer进程使用。


对于来自agent端的数据,无论其监控项是否设置了预处理步骤,都会通过unix域套接字传递给预处理进程。预处理进程接收到数据以后,如果发现该数据并没有设置预处理步骤,则直接写入共享内存,供history syncer进程使用;如果发现需要预处理,则在预处理完毕以后再写入共享内存,供history syncer进程使用。对于history syncer进程来说,其输入数据来自共享内存,它会批量地从共享内存读取数据进行处理,具体处理过程是先写入数据库,然后计算触发器表达式生成event。


以上是不同种类进程之间的数据IO,那么同一类进程内部的多个进程之间又是如何分配数据的呢?总体而言,Zabbix建立了一种机会均等的机制来保证同类的多个进程之间可以实现数据的均衡分配。比如trapper进程,每一个trapper进程所做的工作就是不断地重复一个过程,也就是接受连接,然后读取数据,再处理数据,最后关闭连接。整个过程不需要考虑其他trapper进程的状态。


从统计意义上说,每次接受连接的时候,对于每个trapper进程来说都有相同的概率获得连接,最后的结果就是所有连接在所有trapper进程之间均衡分配。如果考虑每个连接中包含的数据量不均衡,那么当某个trapper进程接受的某个连接包含较多的数据时,意味着该进程需要花更多的时间来读取和处理数据,这就意味着下一次接受连接的时间被推迟。对于poller进程,道理也是一样,每个poller进程都以相同的概率从Zabbix server端的配置缓存接收到任务,从而实现数据分配均衡。


预处理进程的工作模式与trapper进程不同,预处理进程采用manager-worker模式,即manager进程是唯一的数据入口,负责从trapper和poller接收所有数据,然后通过unix域套接字将数据分配给各个worker进程处理。其分配的规则是,每次每个worker只分配一个数据,只有当worker完成前一个任务之后才会分配下一个任务。


最后,history syncer进程之间的数据均衡也是遵循机会均等的机制。其主要特点是,每个循环最多处理1000个监控值,所以每次写数据库的监控值也不会超过1000个。


我们会发现,总体的数据处理过程都基于监控数据之间的独立性,从而可以灵活地在各个进程之间调度。


04

大流量数据的接收


作为监控系统,Zabbix所面临的挑战之一在于需要从大量的监控目标接收数据,这一方面意味着需要连接非常多的监控目标,另一方面意味着监控数据的流量(访问量)比较大。


影响这种通信效率的一个重要因素是通信协议,考虑到单个消息中包含的数据量不同,在数据接收阶段,Zabbix采用了两种不同的通信协议。Trapper进程要求在每个消息中包含较大量的监控数据,因此Zabbix采用基于TCP协议和JSON格式的自定义协议进行通信。而对于poller进程,当poller进程向agent请求单个监控值时,Zabbix采用简化的文本协议进行通信,以提高通信效率。


两种消息格式均为文本格式,相较于使用二进制格式,文本格式使得agent端的接口设计更简单,从而为自定义agent开发提供了便利,可以吸纳更多的监控目标转向Zabbix。在tcp连接的管理方面,Zabbix的每个连接只处理一个请求,处理完就会关闭,无论在trapper进程还是poller进程中都是这样。每个tcp连接的建立和关闭都意味着一定的开销,但是这种模式也有优点,就是灵活性。每次连接只处理一个请求,意味着每个请求都可以在多个进程之间重新调度。从而可以更好地在众多进程之间实现动态均衡。退一步说,通过增加单个连接的数据量也能够降低频繁建立连接的成本。

在扩展性方面,除了可以通过增加进程数量来增加数据吞吐量之外,Zabbix还支持多地址监听,从而可以突破单套接字的吞吐量限制。


05

数据结构设计—解决处理速度问题


假设有一个监控系统存在100万个监控项,30万个触发器,nvps值为一万,意味着每秒需要处理1万个值,而每处理1个值都需要从100万个监控项中找到所对应的item状态信息。同样地,在计算触发器时也需要从大量触发器中查找目标。对于这一查找问题,Zabbix通过哈希表结构来解决,也就是,以itemid、hostid、triggerid等作为键,构建哈希表来组织这些需要频繁查找的数据。


哈希表结构存在于配置缓存、历史缓存、历史索引缓存、值缓存valuecache和趋势缓存中,配置信息缓存中的哈希表主要是解决刚才说的频繁查找大规模元数据的问题。历史缓存和历史索引缓存中也使用了哈希表,当有大量的监控数据来不及处理时,可以先放到历史缓存的哈希表中,等待history syncer进程处理。由于使用了哈希表,即使有大量监控项的数据进入缓存,history syncer进程仍然可以以固定的时间复杂度来查找这些数据。


ValueCache以哈希表形式存储每个监控项的热数据,这些数据主要用于触发器表达式的计算,从而,监控项数量的增长基本不会影响触发器表达式计算所消耗的时间。趋势缓存中存储的是每个监控项最近自然小时内的累计趋势数据,这些数据的更新和写库均需要经历查找的过程,因为数据的更新和写库都是由最新到来的监控数据驱动的,需要根据到来的监控数据查找对应的监控项数据。


除了需要解决快速查找问题,Zabbix还需要解决快速排序问题,这一问题源于history syncer进程处理监控数据时应该按照什么样的顺序进行处理,Zabbix是以监控项为单位对所有监控数据按照时间戳从小到大进行全局性的排序,所使用的排序方法就是堆排序,当需要选择目标时,只需要访问根节点即可。在使用堆排序的情形下,每次排序所消耗的时间与堆的规模直接相关。所以我们应该尽可能降低历史缓存中的监控项的数量,以加快数据处理速度。在压力较小的Zabbix系统中,历史缓存中的数据总是接近于0。


上述哈希表结构的设计体现了监控数据的独立性,正是由于独立性,每次查找数据只需要访问单个目标。而堆排序的设计则体现了监控数据在时间维度的先后次序。


06

元数据的访问与协调


下面看一下元数据对数据处理效率的影响。元数据位于共享内存中,包含监控项、主机、触发器、接口等所有必要的信息,这些信息分别存储在很多个哈希表结构中。这些信息供几乎所有Zabbix进程使用,在Zabbix server端,无论是数据接收阶段、预处理阶段、数据计算阶段都离不开对元数据的访问。此外,元数据还需要通过configuration syncer进程进行定期数据同步,目的是将数据库中所作的修改同步到缓存中。当在同步过程中需要修改元数据,configuration syncer进程会对面临一个较大的挑战,就是与其他进程的协调。考虑到大部分进程只是读取元数据,而不会进行写操作,Zabbix选择使用读写锁来协调所有进程对元数据的访问,从而允许多个进程同时读取元数据。


即使这样,当configuration syncer进程持有写锁时,其他进程将无法访问元数据。对这一问题的优化,Zabbix采用临界区最小化以及拆分临界区的方式,将元数据的同步过程拆分为依次执行的多个同步序列,每个序列结束后都会暂时释放写锁,从而给其他进程运行的机会。这些序列总体上是先同步主机信息,然后同步监控项信息,最后同步触发器和事件处理相关的信息。在使用读写锁的情形下,单纯的读锁基本不会影响多进程的执行进度,而一旦写锁介入就会对所有其他进程产生排斥效果。


Zabbix通过最大程度地减少写锁持有时间,并将持有时间碎片化,从而降低了写锁对Zabbix进程并行性能的影响。我们注意到configuration syncer进程是唯一的,并未采用多进程方式,这一方面是因为元数据内部需要维持某些一致性,如果使用多进程方式,无疑会增加维持一致性方面的开销,另一方面,如果采用多进程,这些进程之间仍然需要解决锁冲突的问题,最终的结果与单进程并无多大区别。不过,如果能够基于元数据之间的独立性,实现基于数据分组的并行化,那就另当别论。


07

大流量数据的存储


监控数据的存储是由history syncer进程负责写入数据库,history syncer进程应对大流量数据的方式是,一方面通过批量插入来提高写库的效率,另一方面通过多进程的方式来扩展数据处理能力。在写库过程中,历史数据写入history表,即使数据变为冷数据也需要留在history表中供前端UI访问。趋势数据写入trends表,相较于历史数据,趋势数据的写库负载要低的多,是因为对于每个监控项来说,平均每小时只需要写库一次,其他时间只需要更新趋势缓存,不需要写库。


在history syncer进程的触发器计算阶段,主要访问值缓存,但是如果值缓存不能命中则需要查询history表,这种情况下就需要使用history表索引了。考虑索引对insert操作的影响,索引在提高查询速度的同时也降低了写入性能。


触发器计算之后生成的event信息也需要写入数据库,这些信息存储在events表、problem、escalation等表中。因此,这些表的数据增长率取决于Zabbix生成的事件的数量,当事件数量陡增时,这些表的写入压力也会随之增加。


在数据存储方面主要体现了热数据和冷数据的转换过程以及数据规模对读写性能的影响。


今天我的演讲到这里,谢谢大家。


鲍光亚

《深入理解Zabbix监控系统》图书作者

2021-09-16