在MySQL中为Zabbix使用分区和大量监视对象

对于监视服务器和服务,我们长期以来一直成功使用基于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`) 

如您所见,每个指标最终都输入到一个表中,其中有两个对我们来说非常重要且方便的itemidclock字段。 因此,我们可以很好地创建一个临时表,例如,使用名称history_tmp ,为其创建一个history_tmp ,然后将history表中的所有数据传输到该表,然后将history表重命名为history_old ,将history_tmp表重命名为history ,然后添加数据我们已经从history_old填充到history并删除了history_old 。 您可以完全安全地进行此操作,我们不会有任何损失,因为上面指出的itemidclock字段提供了到特定时间而不是某种序列号的链接指标。

过渡程序本身


注意! 在开始任何操作之前,非常需要从数据库进行完整备份。 我们都是有生命的人,可能会在命令集中犯错,从而导致数据丢失。 是的 备份副本不会提供最大的相关性,但最好有一个总比没有好。
因此,请勿关闭或停止任何操作。 最主要的是,在MySQL服务器本身上,应该有足够的可用磁盘空间,即 因此,对于上述每个表historyhistory_texthistory_strhistory_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_strhistory_texthistory_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中使用复制时也能顺利进行。

结论


所描述的解决方案是经过时间检验的。 数据量正在增长,但是性能没有明显下降。

Source: https://habr.com/ru/post/zh-CN480082/


All Articles