ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zabbix 7.0 LTS数据库分区实战:从部署到性能优化

Zabbix 7.0 LTS数据库分区实战:从部署到性能优化 简介Zabbix 7.0 LTS部署后历史与趋势数据表容易快速膨胀甚至出现Zabbix housekeeper进程繁忙告警而数据库分区是缓解这类问题的有效手段。这份PDF操作记录基于MySQL或MariaDB环境围绕zbx_db_partitiong.sql分区脚本展开完整覆盖脚本下载解压、分区存储过程创建、通过MySQL事件调度器或Crontab配置定时自动执行以及Zabbix前端历史与趋势保留天数调整等关键环节脚本默认保留7天历史数据与365天趋势数据读者也可按实际需求修改天数设置。通过分区方案可显著提升大数据量下的清理效率与数据库长期稳定性对需要在生产环境中实施分区策略的运维工程师有直接参考价值。资源为单个PDF文档大小约835KB命令与步骤示例均保留完整适合作为部署调优时的速查手册。目前已有354人学习下载教程内容同样适用于Zabbix 3.0之后的各主流版本是监控系统性能维护方面较为实用的参考资料。1. 为什么Zabbix 7.0 LTS要先想好数据库分区监控系统最容易被数据淹死Zabbix 7.0 LTS是目前最值得落地的长期支持版监控平台但很多团队部署完才发现真正决定监控平台能跑多久的不是server进程而是数据库里那几张越涨越离谱的表。我接过一套监控300多台服务器、历史数据保存半年的Zabbixinnodb_buffer_pool调到16G查询history_uint还是要好几秒triggers反复报慢查询。最后靠着数据库分区把单表拆成按天分片前端图表的响应终于回到秒级。这篇操作记录围绕Zabbix 7.0 LTS部署和数据库分区优化两条线展开先把安装和初始化讲透再给出一套能直接复制的分区方案与踩过坑适合正在做监控架构巡检、想把Zabbix跑长线的运维工程师。2. Zabbix 7.0 LTS部署从仓库选择到server启动的完整链路2.1 环境与版本选型Ubuntu 22.04 LTS MySQL 8.0为什么不用CentOS 7Zabbix 7.0 LTS发布后提供了长时间支持承诺对生产环境来说首选用它而不是6.0。部署之前先定底座我这次选用Ubuntu 22.04 LTS服务器版配合MySQL 8.0。原因有三个一是Zabbix官方仓库对Ubuntu 22.04 LTS同时提供deb和rpm包直接apt安装零折腾二是MySQL 8.0的RANGE分区语法比5.7更统一后面做自动化分区少踩不少坑三是CentOS 7已经EOL继续用会碰到OpenSSL和PHP扩展的依赖问题没必要给自己找不痛快。资源规划上如果被监控节点规模在500台以内4核8G内存足够磁盘建议上SSD。history表的写入模式是高频小IO机械盘在持续监控写入下容易形成瓶颈。我习惯把数据库数据目录放到单独挂载的/srv/mysql避免binlog把根目录撑满。Zabbix server自身的CPU占用不高但数据库磁盘和innodb_buffer_pool需要重点保障。为什么强调LTS版本监控平台一旦上线就是长期运行不像业务系统可以经常发版。LTS版本能保证一年半载不用考虑大版本升级减少迁移风险。7.0相比6.0在告警抑制和用户界面交互上有改进但底层数据表结构没有本质变化所以数据库分区的经验可以完全沿用这也是本篇写分区优化而不是只讲安装的原因。2.2 安装Zabbix server、前端和agent的实测命令以Ubuntu 22.04 LTS为例Zabbix 7.0 LTS官方仓库的安装顺序是先添加仓库再安装server、前端和agent。以Ubuntu 22.04 LTS为例命令如下wget https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_latestubuntu22.04_all.deb dpkg -i zabbix-release_latestubuntu22.04_all.deb apt update apt install -y zabbix-server-mysql zabbix-frontend-php zabbix-nginx-conf zabbix-sql-scripts zabbix-agent参数说明zabbix-release包会把Zabbix 7.0的apt仓库写入/etc/apt/sources.list.d/并配置好GPG key。zabbix-frontend-php是Web前端zabbix-nginx-conf是Nginx站点配置zabbix-sql-scripts用来导入数据库schema。如果不想用Nginx可以换成zabbix-apache-conf但我习惯用Nginx内存占用更小。安装完先初始化MySQL数据库mysql -uroot -p -e CREATE DATABASE zabbix CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; mysql -uroot -p -e CREATE USER zabbixlocalhost IDENTIFIED BY zabbix_passwd; mysql -uroot -p -e GRANT ALL PRIVILEGES ON zabbix.* TO zabbixlocalhost; mysql -uroot -p -e FLUSH PRIVILEGES;注意密码别用明文写在脚本里上面只是示例。CREATE DATABASE指定utf8mb4和utf8mb4_bin是为了和Zabbix 7.0官方要求一致否则导入schema时中文排序和索引匹配都可能出现异常。接着导入schemazcat /usr/share/zabbix-sql-scripts/mysql/server.sql.gz | mysql -uzabbix -pzabbix_passwd zabbix这段命令直接用zcat解压官方提供的server.sql.gz通过管道导入zabbix库。整个过程可能持续几分钟不要中断。如果终端假死先检查磁盘空间和MySQL连接数别盲目重启。然后修改Zabbix server配置sed -i s/^# DBPassword.*/DBPasswordzabbix_passwd/ /etc/zabbix/zabbix_server.conf systemctl restart zabbix-server配置好后访问http://server_ip/zabbix默认用户Admin初始密码是zabbix。登录后第一件事改密码并且把前端PHP时区调好。2.3 配置数据库与启动服务初始化schema和时区坑schema导入后还需要做两件事调整PHP时区和MySQL时区否则监控图表显示UTC时间跟本地时间差8小时。Zabbix前端默认读取PHP配置里的timezone修改/etc/zabbix/php-fpm.conf或php.inidate.timezone Asia/Shanghai然后重启php-fpm和nginx。同时MySQL的time_zone也要同步SET GLOBAL time_zone 08:00; SET GLOBAL log_timestamps SYSTEM;如果只改PHP不改MySQL数据库记录的时间仍然是UTC前端最新数据展示没问题但报表跨天统计时会出现边界错位。这一步对后面分区设计非常关键因为Zabbix分区键是clock字段存的是Unix秒时区不一致会造成分区边界偏移。agent的安装也顺手说一下在客户端执行apt install -y zabbix-agent sed -i s/^Server127.0.0.1/Server192.168.10.20/ /etc/zabbix/zabbix_agentd.conf systemctl restart zabbix-agent注意Server和ServerActive两个参数都要写。Zabbix 7.0支持被动和主动检查Server是被动模式的白名单ServerActive是主动上报的server地址只写Server会导致主动式监控失败。到这里server和agent已经跑通但数据库还没有分区下面进入分区优化正题。3. 数据库分区优化原理为什么Zabbix的history表是分区的主战场3.1 存储结构history、trends、history_uint哪些表必须分Zabbix收集到的每个监控项都会写入历史数据表history存浮点数指标history_uint存整数指标CPU使用率、内存用量、磁盘IOhistory_str和history_text存字符串和日志trends和trends_uint是趋势表用来支持长时间范围图。默认保留策略是history存7天、trends存30天但多数团队会调长到90天甚至一年导致history和trends成为数据库里体积最大的两张表。分区为什么能起作用InnoDB索引是B树结构单表超过千万行后索引层级可能从3层加深到4层每次都多一次磁盘I/O。分区把大表物理拆成多个子表查询SQL如果带clock条件优化器可以直接裁剪到对应分区扫描量大幅下降。Zabbix 7.0的监控查询SQL基本都是WHERE clock BETWEEN xxx AND xxx天然适配分区裁剪。注意不是所有表都需要分区。比如history_log这种数据量小、查询不频繁的表分区反而增加管理成本。我一般只分history、history_uint、history_str、trends、trends_uint这五张如果实际环境里history_text也超过了几百万行再考虑纳入。分区还有个隐藏好处删除过期数据只用DROP PARTITION比DELETE快几个数量级。传统清理方式在高并发监控库上会把MySQL拖到无法响应分区表则完全没有这个压力这也是Zabbix官方和社区都推荐分区的原因。3.2 分区策略设计按天RANGE分区、保留期与索引设计常见做法是按天做RANGE分区每个分区对应一天数据。这样的好处是与监控数据的自然时间轴一致每天的凌晨做维护操作删除旧数据时也直观。一天一个分区一年就是365个分区InnoDB完全能承受只要不是长事务频繁访问information_schema基本没有额外开销。分区键选Zabbix表的clock字段它是Unix时间戳整数类型。MySQL有硬性要求分区字段必须包含在表的所有唯一索引里。Zabbix 7.0原始表结构如果已经有PRIMARY KEY(id,clock)那分区字段已经在主键中直接分区即可。如果从老库升级后表没有显式主键需要先加主键或者把clock加进唯一索引否则执行PARTITION BY RANGE会报错。保留期设计我一般遵循history保留90天trends保留30天。分区自动创建到未来2天自动删掉91天前的分区。这可以避免偶尔的系统停机导致没有未来分区也可以保证数据不突破保留期。分区边界用VALUES LESS THAN它是右开区间意思是不包含右边界的值边界必须是比当天晚一天的零点。ALTER TABLE history_uint PARTITION BY RANGE (clock) ( PARTITION p20250101 VALUES LESS THAN (UNIX_TIMESTAMP(2025-01-02 00:00:00)) );这里UNIX_TIMESTAMP是MySQL函数把本地时间转换为Unix秒。边界设置为1月2日零点那么p20250101分区包含1月1日全天数据。注意分区边界必须始终比当前时间晚至少一天如果边界过期新写入的数据会因为找不到匹配分区而报错。3.3 用MySQL 8.0的RANGE分区实现一个具体的分区脚本手工为每个表建一年365个分区不现实我习惯用存储过程动态生成。以history_uint为例先写一个能创建未来N天分区的脚本DELIMITER $$ CREATE PROCEDURE dynamic_partition_create(IN dbname VARCHAR(64), IN tablename VARCHAR(64), IN days INT) BEGIN DECLARE i INT DEFAULT 1; DECLARE p_date DATE; DECLARE p_name VARCHAR(32); DECLARE p_sql TEXT; WHILE i days DO SET p_date DATE_ADD(CURDATE(), INTERVAL i DAY); SET p_name CONCAT(p, DATE_FORMAT(p_date, %Y%m%d)); SET p_sql CONCAT(ALTER TABLE , dbname, ., tablename, ADD PARTITION (PARTITION , p_name, VALUES LESS THAN (UNIX_TIMESTAMP(, DATE_ADD(p_date, INTERVAL 1 DAY), )))); SET sql p_sql; PREPARE stmt FROM sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET i i 1; END WHILE; END$$ DELIMITER ;这个存储过程接收数据库名、表名和天数循环创建未来N天的分区。每次ALTER TABLE ADD PARTITION在MySQL 8.0里不会重建全表但会有MDL锁所以高峰期不能频繁执行。我在生产环境里每天只跑一次一次创建2天分区锁冲突就少很多。注意这里用了动态SQL是因为ADD PARTITION语法不支持直接用变量作为分区名和边界值。如果你试过把变量直接写在ALTER TABLE里会看到语法错误提示用PREPARE EXECUTE是绕开这个限制的标准办法。3.4 分区后的校验清单分区数、边界和裁剪效果快速验证分区做完后要验证不能只看没有报错就认为成功。最直接的查询是看information_schemaSELECT TABLE_NAME, PARTITION_NAME, PARTITION_DESCRIPTION FROM information_schema.PARTITIONS WHERE TABLE_SCHEMAzabbix AND TABLE_NAMEhistory_uint ORDER BY PARTITION_DESCRIPTION DESC;PARTITION_DESCRIPTION显示分区的最大值最新分区应大于当前时间戳旧分区的数量要符合保留天数。如果最大分区的时间小于当前时间说明建分区的脚本有bug后续写入会失败。验证分区裁剪是否生效可以用EXPLAINEXPLAIN SELECT * FROM history_uint WHERE clock BETWEEN 1750896000 AND 1750982400;EXPLAIN结果里如果看到typeALL或者rows特别大说明分区裁剪没有起作用。常见原因是SQL里对clock字段做了函数转换比如FROM_UNIXTIME(clock)优化器无法把函数计算后的条件映射成分区范围。在Zabbix自己的查询中不会出现这个问题但如果你写了外部报表查询要特别注意分区键保持原始值。4. 实施数据库分区把规划变成定时任务4.1 先备份再动手部分数据的风险与备份策略分区改造前一定要备份。我见过有人在几千万行的大表上直接跑ALTER TABLE PARTITION BY RANGE执行到一半磁盘满整个zabbix库不可用。有了备份还能回滚否则只能从监控项的重新采集开始补数据那是灾难。MySQL备份用mysqldump注意加一致性参数mysqldump -uzabbix -p --single-transaction --set-gtid-purgedOFF --databases zabbix /backup/zabbix_$(date %F).sql--single-transaction用InnoDB事务保证备份期间数据一致不会锁表--set-gtid-purgedOFF是为了在非GTID环境恢复时避免gtid报错。如果库实在太大mysqldump会慢建议用mydumper做并行备份但最小可行方案还是mysqldump加定时任务。对表结构做分区时ALTER TABLE PARTITION BY RANGE会重建全表对几十GB的表耗时长且有锁。如果业务不能忍受可以分成两步先做一个新分区表再INSERT INTO SELECT导数据最后改表名。但Zabbix监控数据中断几分钟影响不大只要把窗口放在凌晨直接用ALTER TABLE也能接受。我这次就是凌晨执行的提前停了外部报表任务保证没有长事务持有锁。4.2 自动创建和删除分区的存储过程与事件调度器分区表建好后管理要自动化。我维护一个存储过程每天凌晨处理三件事创建未来2天的分区删除超过保留期90天的分区并记录每次操作日志。示例过程DELIMITER $$ CREATE PROCEDURE manage_zabbix_partitions() BEGIN DECLARE expire_part_name VARCHAR(32); DECLARE create_date DATE; DECLARE create_part_name VARCHAR(32); DECLARE done INT DEFAULT 0; DECLARE cur CURSOR FOR SELECT PARTITION_NAME FROM information_schema.PARTITIONS WHERE TABLE_SCHEMAzabbix AND TABLE_NAMEhistory_uint AND PARTITION_NAME DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 90 DAY), p%Y%m%d); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done1; OPEN cur; del_loop: LOOP FETCH cur INTO expire_part_name; IF done1 THEN LEAVE del_loop; END IF; SET drop_sql CONCAT(ALTER TABLE zabbix.history_uint DROP PARTITION , expire_part_name); PREPARE stmt FROM drop_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE cur; SET create_date DATE_ADD(CURDATE(), INTERVAL 1 DAY); SET create_part_name CONCAT(p, DATE_FORMAT(create_date, %Y%m%d)); SET add_sql CONCAT(ALTER TABLE zabbix.history_uint ADD PARTITION (PARTITION , create_part_name, VALUES LESS THAN (UNIX_TIMESTAMP(, DATE_ADD(create_date, INTERVAL 1 DAY), )))); PREPARE stmt FROM add_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END$$ DELIMITER ;这个存储过程用游标扫描information_schema.PARTITIONS找出所有分区名小于90天前日期的分区DROP掉。然后创建次日分区保证未来分区总是存在。DROP PARTITION是立即释放空间不会像DELETE那样产生大量binlog和回滚段这是分区表在空间回收上的最大优势。创建事件调度器SET GLOBAL event_scheduler ON; CREATE EVENT ev_manage_zabbix_partitions ON SCHEDULE EVERY 1 DAY STARTS 2025-01-01 03:30:00 DO CALL manage_zabbix_partitions();event_scheduler默认可能是OFF如果不开事件不会执行这是后面常见的翻车点。确认开启状态用SHOW VARIABLES LIKE event_scheduler;。4.3 从Zabbix 6.0迁移到7.0时分区表的注意事项如果是从Zabbix 6.0升级到7.0并且老库已经做过分区升级前要检查分区边界是否覆盖到今天。7.0的schema脚本会对表结构做变更可能涉及新索引在分区表上ALTER TABLE ADD INDEX会一次性重建所有分区耗时长。所以我的建议是升级前先把旧分区清到只剩最近30天导入7.0 schema后再按新策略重建这样迁移窗口短历史趋势数据丢掉一部分也无所谓。另外Zabbix 7.0官方schema脚本里可能包含了对history表增加列的改动。分区表增加列在MySQL 8.0里有特殊限制如果新增列的默认值不是常量会报不支持。实际上7.0的变更都是常量默认值问题不大但升级过程中一旦报错要把官方给的ALTER语句改成按分区逐个处理或者先合并分区再升级。还有一个容易被忽略的点如果分区表已经运行了很久分区数量非常多升级时查询information_schema会变慢。可以先删掉一半历史分区再升级减少元数据扫描时间。5. 分区后必踩的坑查询变慢、锁表与监控断档的排查记录5.1 现象分区后history查询反而变慢分区前几千万行查询1秒分区后变成3秒这是分区裁剪失效的典型症状。原因通常是外部SQL对clock字段做了函数运算比如WHERE FROM_UNIXTIME(clock) 2025-01-01优化器无法把函数结果转换成分区范围。解决方法是不要对分区键套函数查询条件直接使用Unix时间戳值。另一个原因是分区粒度过细。我曾经把分区粒度调整为每小时一年下来分区数超过8000个慢查询不降反升。按天分区分区数量可控是性价比较高的方案。如果查询条件没有精确到分区键范围优化器会扫描所有分区再合并结果反而比普通单表更慢。5.2 现象ALTER TABLE导致主从延迟飙高在master上对几十GB的分区表执行DROP PARTITION动作本身很快但产生的DDL传入从库后从库需要长时间持有MetaData Lock复制延迟会突然飙升。解决方法是把分区删除任务放到低峰期从库配置并行复制开关比如设置slave_parallel_workers4可以缓解大半。如果延时还是压不住就把删除动作拆分一次只DROP一个分区然后sleep 5秒给从库留追数据的时间。我有一次一口气删30个历史分区结果从库延迟到了10分钟告警刷了一屏这是最惨痛的教训。5.3 现象定时删除分区失效事件调度器没开部署完存储过程第二天发现分区还在数据已经超出保留期。最直接的原因是MySQL的event_scheduler没有开启。很多人只在会话里SET GLOBAL event_schedulerONMySQL重启后又回到OFF。解决方法是把event_schedulerON写进my.cnf的[mysqld]段然后重启MySQL。另一个原因更隐蔽存储过程里用游标查询information_schema时如果applied到老库有时候分区名的字符串比较规则判断失误。比如分区名p20241231和p20250101如果过滤条件写PARTITION_NAME p20250101会正确删除旧分区但如果是带时分秒的日期格式就会匹配错误。解决方案是统一分区命名格式为p%Y%m%d用DATE_FORMAT生成。5.4 现象新分区不在预期的时间范围时区错位分区的边界用UNIX_TIMESTAMP(2025-01-02)生成如果MySQL的time_zone是UTCUNIX_TIMESTAMP会把字符串当作UTC时间换算分区边界和实际北京时间差8小时。更隐蔽的是存储过程中拼SQL时CURDATE()使用的是MySQL会话时区而PHP监控项又用系统时区两者不一致直接导致分区边界错位一天。排查方法是用SELECT UNIX_TIMESTAMP(2025-01-02 00:00:00)看结果是否等于当天的北京时间秒数。解决方法是统一MySQL时区在my.cnf里写default-time-zone 08:00同时规范所有连接串不要设置session time_zone。如果你已经被坑过会发现所有分区边界都偏移了只能手动合并删除老分区再重建。5.5 现象频繁DDL让MySQL连接堆积分区创建和删除如果集中在一个时间点会对MySQL产生大量短连接和元数据锁请求。现象是监控前端页面报数据库连接超时processlist里堆满waiting for table metadata lock。原因是分区管理任务的连接没有关闭或者与Zabbix server的采集线程形成锁等待。解决方法是给业务连接和运维连接区分账号分区管理专门用一个账号并设置lock_wait_timeout。另外事件调度器的执行计划可以加一个随机偏移量比如每天03:30:00之后等待120秒再执行错开整点的Zabbix housekeeping任务。这个坑不亲眼见到很难想到但确实会让生产环境在凌晨出现短时间卡顿。6. 用Zabbix 7.0内置监控验证分区成效数据库层面看分区真实收益6.1 用慢查询日志和performance_schema验证分区干净分区后到底快多少不能凭感觉。先开启MySQL慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;运行前端几个折线图等几分钟后查看slow.log里history相关SQL的执行时间。如果优化得当超过90%的history查询应该在2秒以内。进一步用performance_schema定位锁等待SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/1000000000 AS ms FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE %history_uint% ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;SUM_TIMER_WAIT单位默认是皮秒除以1e9转为毫秒。只有查看history_uint相关语句才能确认哪些SQL有分区裁剪失效的问题。如果某些SQL执行次数多但总耗时异常就要回到5.1的坑去处理。6.2 用Zabbix的MySQL模板监控InnoDB缓冲池和临时表Zabbix 7.0自带MySQL by Zabbix模板可以在server上配置一个监控用户然后在主机里挂载模板就能收集InnoDB缓冲池命中率、临时表数量、Table打开次数等指标。我重点盯InnoDB缓冲池命中率分区后应该稳定在99%以上。如果发现命中率掉到95%以下说明有大量全表扫描优先排查外部报表和定时任务。另外慢查询日志里如果持续出现Creating tmp table说明SQL在排序或分组时产生了临时表。分区后这类语句减少说明数据裁剪缩小了中间结果集这是分区收益的直接证明。配合Zabbix的触发器还可以在你设置的阈值上自动报警。我的习惯是分区优化上线后观察至少两周把慢查询日志和缓冲池命中率对照着看。有一次优化完缓冲池命中率没起色查了才发现很多外部脚本每小时执行一次SELECT COUNT(*)全表扫描history_uint分区再多也白搭。后来把这些脚本改成查information_schema的分区统计压力顿时消失。如果你也遇到类似情况先别急着缩短保留期先找出全表扫描的来源。分区不是银弹它只对按时间范围查询有效把不规范的SQL清理掉Zabbix 7.0 LTS才能真正跑得长。希望这份记录能帮你把部署和分区一次做顺。本文还有配套的精品资源点击获取
返回列表