专栏|Zabbix server多进程协作式数据流处理过程及负载分析

网友投稿 811 2022-05-30

鲍光亚 | zabbix开源社区签约专家

2021 Zabbix深圳大会演讲嘉宾

长期专注监控系统软件领域,精通Zabbix,具有多年Zabbix监控系统运维和相关开发经验,持续关注Zabbix的发展,熟悉Zabbix源码。曾就职于某大型互联网企业,专门从事Zabbix系统运维和开发工作。

作为一款适用广泛、功能强大的监控软件,Zabbix的核心能力在于快速高效地处理持续涌入的大量监控数据。这种能力所面对的挑战,一方面是监控数据的规模庞大,另一方面是这些数据要求在极短的时间内处理完毕。两方面的挑战集中体现在了Zabbix Server端,因为相对于agent和proxy来说,Zabbix server所需处理的数据流量最大,所要完成的功能也最多、最复杂。那么,Zabbix server如何做到对监控数据的流畅处理?我们又如何使Zabbix server达到最佳性能呢?

本文试图通过剖析Zabbix server内部的数据处理流程,从数据流的角度为该问题提供一种解释。本文将从三部分阐述:

1.梳理出数据流在Zabbix server端各种进程之间如何传递和加工;

2.考察各进程的工作量以及影响工作量的因素;

3.讲述Zabbix内部监控所提供的进程繁忙率是如何计算的,以及进程数量如何影响繁忙率。

(本文所述内容基于Zabbix 5.0版本,其他版本可能有所不同。)

1. 数据流加工和传递过程

Zabbix server采用多进程架构,极端情况下可以启动数千个进程。这些进程进一步划分为很多类,不同类的进程之间接力传递和加工数据,形成上下游关系。每一类进程负责完成特定的数据加工,当某类进程的数据加工流量过大时,可以通过增加该类进程的数量来提高吞吐量。

如果我们将Zabbix server的总体数据处理流程绘制成图表,将如图1所示。

图1. Zabbix server数据加工和传递过程

在监控数据的源头,Zabbix server需要从agent或者proxy获取数据输入(主动或者被动方式),这一过程主要由poller或者trapper进程完成。Zabbix可以监控的对象种类很多、很全面,实际上该过程所涉及的进程不只是poller和trapper这两种,但是所有这些进程的作用归根结底是实现数据输入。为了简化问题,本文使用poller/trapper来代表数据输入进程。

监控数据通过poller/trapper进程流入Zabbix server。随后poller/trapper进程会将数据传递给预处理进程(preprocessing),并在预处理进程中完成监控数据的初步加工,这些加工遵照用户配置的规则完成。在Zabbix 5.0中,这些加工可以很复杂,加工的方式也很多。当然,用户也可以不设置预处理,这种情况下预处理进程相当于什么也不做。

在预处理完成之后,监控数据会分流到不同的进程。其中一部分是LLD(low level discovery)数据,这部分会被传递给lld进程加工;剩余的部分则通过共享内存传递给history syncer进程加工。LLD进程所做的加工是根据监控数据生成配置信息(监控项、触发器等),所生成的信息最后写入共享内存和数据库。

history syncer进程是所有数据加工中最关键的,它一方面负责将监控数据本身写入到数据库中,另一方面还负责计算监控数据,并根据计算结果生成事件和问题(problem),以及为事件和问题生成响应计划。事件、问题以及对事件的响应计划也写入到数据库中。

事情到这里并没有结束,history syncer进程所生成的响应计划是一系列的操作,这些操作可能会持续较长的时间。escalator进程和alert进程负责跟进(follow up)并完成这一系列的操作,它们会从数据库读取所需要的计划信息,并遵照计划进行跟进。

除了以上进程外,Zabbix server中还有一些不直接参与监控数据处理但是仍然非常重要的进程,例如用于配置信息同步的configuration syncer进程、用于自身监控的self-monitoring进程。因为它们不直接参与外来监控数据的处理,本文忽略这些进程。

2. 各进程的工作量及其影响因素

现在考虑一个问题,图1所示各进程的工作量受哪些因素影响?这些工作量之间又具有怎样的比例关系?图中各进程所完成的任务既有区别又有联系。

其区别之处,一方面在于各进程的工作职责不同,另一方面也体现在其所加工的数据成分不同。进程之间的联系则在于它们构成了上下游的流水线,如果某个节点发生拥堵,那么这种拥堵会向上游传导,影响整体的吞吐量。

poller/trapper进程的职责是获取数据输入,这就要求它能够接受大量的连接,并从socket及时读取大量数据,这些数据就是原始的监控数据。该进程的应然工作量取决于监控项的数量和采集频率,工作量大小可以用nvps值(new values per second)来衡量。网络传输具有波动性,因为监控值通过网络输入,所以实际的每秒传输数据量并非恒定,而是有一定的起伏变化。如果poller/trapper进程处理数据的速度赶不上数据流入的速度,就会发生拥堵排队,所造成的结果就是监控数据无法及时处理或者数据丢失。此时我们往往会观察到zabbix[queue]监控值的升高。由于poller/trapper是整个数据流的源头,如果下游的进程发生拥堵,最终也会传导到该进程(在数据库之后的一些进程例外,因为数据库相当于一个极大的缓冲池,会截断向上游的传导)。

预处理进程的职责是按照用户设置对原始监控数据进行各种加工,这意味着,该进程的工作量取决于加工的步骤数以及复杂程度。Zabbix 5.0的预处理功能支持25种加工,这些加工既有简单的数值加减也有复杂的文本处理(正则表达式、JSON Path等),甚至可以自定义处理函数(JavaScript脚本)。预处理进程的工作量取决于每一种加工步骤所处理的数据量。显然,对一个5000字符长度的字符串执行复杂的正则运算要远比两个整数的减法运算工作量大(读者可以在网上找到正则表达式拒绝服务攻击——ReDoS的例子)。一般来说,并非所有的监控数据都需要进行预处理。如果某些监控项没有配置预处理,那么这些监控项的数据就不需要进行这一步的加工。当预处理进程的处理能力不能满足要求时同样会发生拥堵,所造成的结果就是流向下游的数据量减少,而上游的进程则无法向下传递数据。

lld进程的职责是解析预处理之后的监控数据(JSON串),并按照规则创建或者修改监控项和触发器等。该进程所加工的数据量取决于lld规则的数量和采集频率。lld规则所采集到的数据是特定格式的JSON串,其中包含了自动发现的对象信息,lld进程根据这些新发现的对象创建或者修改监控项、触发器、主机等。显然,当自动发现的对象数量很多时,就意味着lld进程需要创建或者修改大量的监控项、触发器等(对于重复发现的对象则不需要创建)。lld进程的下游是数据库和共享内存,当数据库出现问题时,lld进程也会受到影响。如果lld进程发生拥堵,那么上游的预处理进程也无法幸免。

history syncer进程既需要进行大量的计算又需要完成大量的IO。计算工作主要是根据监控数据和触发器表达式进行运算,以决定是否生成事件和问题(problem)。IO工作主要是将所有监控数据以及事件信息写入数据库中。显然,监控值和触发器的数量共同决定了计算量,而IO的工作量则主要取决于监控值的数量和类型以及事件的多少。当数据量很大时,IO往往首先成为瓶颈,进而导致history syncer的处理能力下降,继而发生拥堵。但是IO能力受限于数据库的性能,所以即使增加history syncer进程的数量也无助于提高其处理能力。

数据库相当于一个巨大的缓冲池,可以隔绝上游和下游的拥堵传递。当数据库下游的进程发生拥堵时,数据库中的事件数据不能得到及时处理,但是如果写库的速度没有受到影响,那么位于数据库上游的进程(例如history syncer、lld进程)仍然可以正常工作,继续向数据库写入数据。

在数据库的下游有escalator和alert进程,虽然逻辑上后者的数据来自前者,但是实际上两者都是从数据库中消费数据的。escalator进程主要消费escalations表中的数据并将生产的数据写入alerts表中,alert进程则消费alerts表中的数据。显然两个进程的工作量取决于所消费的表中尚未处理的数据的量。一般来说,当监控数据触发了大量事件和问题时,就会导致escalations表数据增速上升,进而导致alerts数据增速上升。换句话说,这两个进程的压力与被监控对象的整体健康程度有关。在拥堵传导方面,因为两者中间有alerts表作为缓冲,下游的拥堵不会传导至上游。

根据上述内容,Zabbix server的稳定运行要求每种进程都有能力避免发生拥堵,这就要求每种进程应达到足够的处理能力。一般来说,处理能力的扩展可以通过增加进程数量来实现,但是Zabbix server的进程数量是通过配置文件设置的,修改进程数量就意味着重启服务。因此,为了尽可能少调整进程数量,在设置进程数时不仅要考虑当前负载还要考虑未来的负载,以及监控频率、触发器、预处理、lld规则等的调整。另外,考虑到监控数据流本身会有自然的波动,还需要在负载本身的基础上再加上一定的冗余(例如留出20%~30%的余量)。而对于各种进程之间的相对负载比例,则因系统而异,不能一概而论。

专栏|Zabbix server多进程协作式数据流处理过程及负载分析

3. 进程繁忙率的计算与进程数量调整

Zabbix自身监控提供了Zabbix server各个进程的繁忙率,这是衡量各进程负载的重要指标。本节讲述Zabbix自身进程繁忙率的计算方法,以帮助运维人员了解繁忙率的准确意义,从而可以基于此对进程数量进行可靠的调整。

分析Zabbix server端各进程的代码会发现,这些进程采用两种不同的数据处理模式:主动式脉冲和IO阻塞式脉冲。主动式脉冲是指进程每次处理一小批数据,如此循环往复,只要有待处理的数据就持续循环,当所有待处理数据都处理完毕时调用sleep函数,主动休眠一定时长,从而形成图2所示的不定长的繁忙脉冲(黄色方块),每次繁忙脉冲时间长短不固定,受到数据处理速度的影响。history syncer进程就属于主动式脉冲,其繁忙脉冲时长不固定,但是每次休眠的时长是固定的(每次1秒)。poller进程和escalator进程也属于主动式脉冲,其繁忙时长不固定,而且休眠时长也不固定(因为他们的任务都是定点的,要求在准确的时间点醒来)。

图2. 脉冲式数据处理

IO阻塞式脉冲是指进程不会主动休眠,而是被动IO阻塞。这些进程每次循环会首先读取数据,当没有数据可消费时被迫阻塞,直到有新数据到来。trapper进程、预处理进程、lld进程和alert进程都属于IO阻塞式,其中trapper进程阻塞于TCP套接字,另外3种进程则阻塞于Unix域套接字。

Zabbix自身监控的进程繁忙率实际上是将主动休眠或者被动IO阻塞的时间计为空闲,而把所有其他时间都计为繁忙,按照这种口径统计出繁忙时间的百分比。基于这种计算方法,当增加某种进程的数量时,分配到每个进程的数据量相应减少,从而增加了每个进程进入休眠或者阻塞的几率,从而降低繁忙率。

4. 总结

总之,Zabbix server采用多进程架构,不同种类的进程分别承担不同的数据加工职责,所有进程构成了上下游式的流水线,监控数据在各种进程之间传递,并最终流出系统或者流入数据库。由于进程之间的这种上下游关系,下游进程的拥堵很容易传导至上游,为了避免拥堵,应使每一种进程具有足够的处理能力,其处理数据的速度应超过数据流入的速度。另一方面,监控数据的成分结构和类型也会影响到各种进程之间的能力配比。当需要调整各进程的能力配比时,Zabbix自身的内部监控所提供的进程繁忙率是一个重要依据,繁忙率采用两种不同的计算方式,因进程而异。

Zabbix 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:JAVA编程讲义之J多线程
下一篇:TensorFlow 介绍(简单版)
相关文章