对于监视服务器和服务,我们长期以来一直成功使用基于Nagios和Munin的组合解决方案。 但是,这一堆有很多缺点,因此我们像许多人一样,正在积极利用
Zabbix 。 在本文中,我们将讨论如何在增加指标数量和增加MySQL数据库数量时以最小的努力解决性能问题
在Zabbix中使用MySQL数据库时出现问题
虽然数据库很小,并且其中存储的度量标准数量也很小,但是一切都很棒。 启动Zabbix Server本身的常规管家进程已成功从数据库中删除了过时的记录,从而阻止了该记录的增长。 但是,一旦捕获的指标数量增加并且数据库大小达到一定大小,一切都会变得更糟。 管家停止在指定的时间间隔内删除数据,旧数据开始保留在数据库中。 管家运行期间,Zabbix服务器上的负载增加,可能会持续很长时间。 显然,有必要以某种方式解决当前局势。
这是一个已知的问题,几乎所有在Zabbix上进行大量监视的人都面临着同样的事情。 也有几种解决方案:例如,用PostgreSQL或什至Elasticsearch代替MySQL,但是最简单,最成熟的解决方案是切换到将表数据存储在MySQL数据库中的分区表。 我们决定就这样走。
从常规MySQL表迁移到分区表
Zabbix有据可查,并且它存储度量的表是已知的。 这些表是:
history
(用于存储浮点值),
history_str
(用于存储短字符串值),
history_text
(用于存储长文本值)和
history_uint
(用于存储整数值)。 还有一个
trends
表,用于存储变化的动态,但是我们决定不去修改它,因为它的大小很小,稍后我们将返回它。
通常,需要处理哪些表是很明确的。 我们决定根据每月的数字(除 每月四个分区:从1号到7号,从8号到14号,从15号到21号,以及从22号到1号(下个月)。 困难在于,我们需要“快速”将所需的表转换为分区,而又不会中断Zabbix Server和收集指标。
奇怪的是,这些表的结构在此方面对我们有所帮助。 例如,
history
表具有以下结构:
`itemid` bigint(20) unsigned NOT NULL, `clock` int(11) NOT NULL DEFAULT '0', `value` double(16,4) NOT NULL DEFAULT '0.0000', `ns` int(11) NOT NULL DEFAULT '0',
一会儿
KEY `history_1` (`itemid`,`clock`)
如您所见,每个指标最终都输入到一个表中,其中有两个对我们来说非常重要且方便的
itemid和
clock字段。 因此,我们可以很好地创建一个临时表,例如,使用名称
history_tmp
,为其创建一个
history_tmp
,然后将
history
表中的所有数据传输到该表,然后将
history
表重命名为
history_old
,将
history_tmp
表重命名为
history
,然后添加数据我们已经从
history_old
填充到
history
并删除了
history_old
。 您可以完全安全地进行此操作,我们不会有任何损失,因为上面指出的
itemid和
clock字段提供了到特定时间而不是某种序列号的链接指标。
过渡程序本身
注意! 在开始任何操作之前,非常需要从数据库进行完整备份。 我们都是有生命的人,可能会在命令集中犯错,从而导致数据丢失。 是的 备份副本不会提供最大的相关性,但最好有一个总比没有好。
因此,请勿关闭或停止任何操作。 最主要的是,在MySQL服务器本身上,应该有足够的可用磁盘空间,即 因此,对于上述每个表
history
,
history_text
,
history_str
,
history_uint
,至少要有足够的空间来创建后缀为“ _tmp”的表,因为它的数量与原始表的数量相同。
我们将不会为上述每个表多次描述所有内容,而仅以其中一个示例(
history
表)的形式考虑所有内容。
因此,基于
history
表的结构创建一个空的
history_tmp
表。
CREATE TABLE `history_tmp` LIKE `history`;
我们创建所需的分区。 例如,让我们做一个月。 每个分区都是基于分区规则创建的,基于
时钟字段的值,我们将其与时间戳进行比较:
ALTER TABLE `history_tmp` PARTITION BY RANGE( clock ) ( PARTITION p20190201 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-01 00:00:00")), PARTITION p20190207 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-07 00:00:00")), PARTITION p20190214 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-14 00:00:00")), PARTITION p20190221 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-21 00:00:00")), PARTITION p20190301 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-01 00:00:00")) );
该运算符将分区添加到我们创建的
history_tmp
表中。 让我们澄清一下,
时钟字段值小于“ 2019-02-01 00:00:00”的数据将落入分区
p20190201 ,然后
时钟字段值大于“ 2019-02-01 00:00:00”但小于的数据“ 2019-02-07 00:00:00”将加入聚会
p20190207 ,依此类推。
重要说明:如果分区表中的数据中的时钟字段值大于或等于“ 2019-03-01 00:00:00”,会发生什么? 由于没有适合该数据的分区,因此它们将不会落入表中并丢失。 因此,您一定不要忘记及时创建其他分区,以避免此类数据丢失(有关以下内容)。
因此,准备了临时表。 填写数据。 该过程可能需要很长时间,但是幸运的是它不会阻止任何其他请求,因此您只需要耐心:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;
初始填充期间不需要IGNORE关键字,因为表中仍然没有数据,但是,添加数据时将需要它。 此外,如果您必须中断该过程并在填写数据时重新开始,则可能会很有用。
因此,经过一段时间(甚至几个小时),第一次数据上传就过去了。 如您所知,
history_tmp
表现在不包含
history
表中的所有数据,而仅包含查询开始时其中的数据。 实际上,您可以在这里进行选择:要么再进行一次通过(如果填充过程持续很长时间),要么立即重命名上述表。 首先,让我们进行第二遍。 首先,我们需要了解
history_tmp
最后插入记录的时间:
SELECT max(clock) FROM history_tmp;
假设您收到:
1551045645 。 现在,我们在第二次数据填充中使用获得的值:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;
该段落应以更快的速度结束。 但是,如果第一次通过进行了数小时,而第二次也进行了很长时间,则进行第三次通过可能是正确的,该过程与第二次完全相似。
最后,我们再次执行以下操作来获得在
history_tmp
最后一次插入记录的时间:
SELECT max(clock) FROM history_tmp;
假设您有
1551085645 。 保留此值-我们将需要它来重新填充。
现在,实际上,当填充
history_tmp
的主要数据结束时,我们将重命名表:
BEGIN; RENAME TABLE history TO history_old; RENAME TABLE history_tmp TO history; COMMIT;
为了避免将数据插入不存在的表的时刻,我们将此块设计为一个事务,因为在第一个RENAME执行之后直到第二个RENAME执行之后,
history
表将不存在。 但是,即使
history
表中的RENAME操作之间有一些数据到达而该表本身尚不存在(由于重命名),我们也会得到少量可以忽略的插入错误(我们有监视功能,没有库)。
现在,我们有了一个带有分区的新
history
表,但是在将数据插入到
history_tmp
表的最后一次传递中,它没有收到足够的数据。 但是我们在
history_old
表中有此数据,现在我们从那里共享它。 为此,我们将需要先前保存的值1551085645。为什么要保存此值,并且不使用当前
history
表中已存在的最大填充时间? 因为已经有新数据进入了,所以我们会得到错误的时间。 因此,我们测量数据:
INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645;
完成此操作后,我们将在新的分区
history
表中保留旧表中的所有数据,以及重命名该表后出现的数据。
history_old
表不再需要。 您可以立即删除它,也可以在删除它之前制作它的备份副本(如果您有偏执狂)。
对于
history_str
,
history_text
和
history_uint
,需要重复上述整个过程。
Zabbix服务器设置中需要解决的问题
现在,有关数据历史记录的数据库维护工作落在了我们的肩上。 这意味着Zabbix不再应该删除旧数据-我们将自己完成。 为了使Zabbix Server不会尝试清除数据本身,您需要转到Zabbix Web界面,在菜单中选择“管理”,然后选择“常规”子菜单,然后在右侧的下拉列表中选择“清除历史记录”。 在出现的页面上,取消选中“历史记录”组的所有复选框,然后单击“更新”按钮。 这将防止我们通过管家清理
history*
表。
在同一页面上注意“变化的动态”组。 这只是
trends
表,我们已承诺将返回该表。 如果对于您来说它也变得太大而需要分区,则也请取消选中该组,然后按照与
history*
表相同的方式处理该表。
进一步的数据库维护
如前所述,为了对分区表进行正常操作,必须及时创建分区。 您可以这样做:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));
另外,由于我们创建了分区表并禁止Zabbix Server清理它们,因此删除旧数据现在是我们关注的问题。 幸运的是,根本没有问题。 只需删除不再需要其数据的分区即可完成。
例如:
ALTER TABLE history DROP PARTITION p20190201;
与具有日期范围的DELETE FROM语句不同,DROP PARTITION在几秒钟内执行,根本不会加载服务器,并且在MySQL中使用复制时也能顺利进行。
结论
所描述的解决方案是经过时间检验的。 数据量正在增长,但是性能没有明显下降。