近期处理了一起极为惨烈的监控系统雪崩事故。故障表现为 Zabbix Server 队列疯狂飙升,延迟监控项(Queue over 10 minutes)达到数十万,所有告警延迟触发甚至丢失,DB 节点的 Load Average 飙到 80+,底层 NVMe 磁盘 %util 锁死在 100%。最终排查结论:业务研发在未经评审的情况下,引入了一个存在严重缺陷的自定义 LLD(Low-Level Discovery)模板,将大量高频更新的容器环境变量作为 Text 类型全量上报。这直接撑爆了 history_text 表,触发了 Zabbix 内部 Housekeeper 的疯狂 DELETE 清理操作,导致 MySQL InnoDB 产生海量随机 IO 与 Undo Log 积压,最终拖垮 History Syncer 进程,引发监控全局假死。
把关系型数据库当成时序库甚至日志库来用,是监控系统运维中最不可原谅的低级错误。自定义监控脚本返回几百 KB 的冗余字符串,还要每隔 30 秒上报一次,这种粗暴的做法不仅毫无数据价值,更是对底层存储 IO 的公开处刑。
排查过程的 Dashboard 是一片刺眼的红。首先看 Zabbix Server 的自身监控,核心指标 Zabbix server history syncer processes more than 75% busy 和 Zabbix server history cache, % used 均已触顶 100%。
这说明 Zabbix Server 接收到的数据已经塞满了内存中的 History Cache,但负责将 Cache 刷入数据库的 History Syncer 进程被严重阻塞,无法写入。
顺藤摸瓜,直接登录 Zabbix 的 MySQL 后端节点,抓取现场指标:
# 查看磁盘 IO 情况
$ iostat -x 1
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 0.00 0.00 120.50 8500.20 1928.00 136003.2 31.97 145.20 16.80 25.50 16.60 0.12 100.00
# 查看 MySQL 进程状态
$ mysql -e "SHOW FULL PROCESSLIST;" | grep -i "history_text"
不出所料,SHOW PROCESSLIST 中堆积了大量处于 updating 状态的 SQL,其中最致命的是这条:
DELETE FROM history_text WHERE itemid=105432 AND clock<1690000000
此时再看 InnoDB 引擎状态:
mysql> SHOW ENGINE INNODB STATUS\G
---TRANSACTION 4598234, ACTIVE 45 sec fetching rows
mysql tables in use 1, locked 1
100345 lock struct(s), heap size 8345120, 5345020 row lock(s), undo log entries 3450920
Undo log entries 达到 300 万级别,History List Length 严重超标。到这里,整个雪崩的逻辑链条已经非常清晰了:
-
毒模板泛滥:业务层添加了自定义的 LLD 规则,自动发现并生成了近 50 万个
Text类型的 Item。这些 Item 每 30 秒抓取一次,数据载荷大。 -
写放大与表膨胀:
history_text表在短时间内急剧膨胀。对于 MySQL InnoDB 而言,大批量变长字符串的写入会引发严重的页分裂(Page Split)。 -
Housekeeper 绞肉机:Zabbix 默认启用了内部的 Housekeeper 来清理过期历史数据。当它试图通过
DELETE语句清理数亿条历史记录时,由于clock字段的索引效率在庞大的表体量前急剧下降,操作退化为缓慢的范围扫描。 -
IO 饱和与雪崩:巨量
DELETE产生了惊人的 Undo Log 和 Redo Log,将 NVMe 的写带宽榨干。History Syncer 进程在执行INSERT时等不到锁和 IO 资源,集体挂起。History Cache 随之满载,Poller 无法继续收集数据,监控队列彻底爆掉。
解决这种系统级阻塞,第一步必须是止血,而不是优雅地等待 SQL 执行完毕。
第一步:暴力阻断清理任务并清理毒模板
直接在 DB 端 Kill 掉所有 Housekeeper 相关的 DELETE 线程,同时在 Zabbix Server 配置中紧急禁用内部清理:
# /etc/zabbix/zabbix_server.conf
HousekeepingFrequency=0
重启 zabbix-server 服务。随后在 Web 端找出那个罪魁祸首的 LLD 模板,强制 Unlink and clear,并批量禁用相关主机上的遗留 Item。
第二步:DB 层面的历史表彻底改造(MySQL Partitioning)
Zabbix 原生 Housekeeper 依赖 DELETE,这在千万级以上的监控规模中是绝对的性能毒药。防御性运维的最佳实践是:永远不要用 DELETE 清理 Zabbix 历史,全部改用表分区(Table Partitioning)并通过 DROP Partition 来回收空间。
对 history、history_uint、history_str、history_text 以及 trends 系列表实施按天/月分区:
-- 以 history_uint 为例,按天分区
ALTER TABLE history_uint PARTITION BY RANGE (clock) (
PARTITION p20231001 VALUES LESS THAN (UNIX_TIMESTAMP('2023-10-02 00:00:00')),
PARTITION p20231002 VALUES LESS THAN (UNIX_TIMESTAMP('2023-10-03 00:00:00')),
...
);
配合 Crontab 每天执行存储过程,自动 ALTER TABLE history_uint DROP PARTITION p_old,耗时仅需几十毫秒,彻底根除 IO 灾难。
第三步:Zabbix Server 核心参数调优 为了防止未来再出现单点突发流量打挂整个 Server 的情况,必须对 Cache 和 Syncer 进行硬性隔离和扩容:
# 提高历史缓存,给 DB 抖动留出缓冲池 (根据 RAM 大小调整)
HistoryCacheSize=2G
HistoryIndexCacheSize=256M
ValueCacheSize=1G
# 增加 Syncer 进程数,但不要超过 CPU 核心数的一半
StartDBSyncers=16
# 限制自定义脚本超时时间,防止 Poller 假死
Timeout=10
经过上述改造,截断庞大的 history_text,重构分区表后,Zabbix Server 的 Queue 在 5 分钟内迅速清零,IO 恢复到个位数,系统平稳落地。
同类问题排查清单(Zabbix 性能速查)
-
观察 History Cache 使用率 若
Zabbix server history cache, % used持续走高,说明后端 DB 已经成为瓶颈。切勿盲目增加 Zabbix Poller 数量,这只会加重 DB 负担,应优先排查 MySQL IO 及慢查询。 -
检查 Housekeeper 状态 查看 Zabbix Server 日志中
housekeeper每次执行的耗时。如果耗时超过几十分钟甚至小时级,必须立即停用内部 Housekeeper,改用 MySQL 数据库表分区(Partitioning)策略。 -
排查 Unsupported Items 与高频 LLD 使用
zabbix_get测试自定义脚本耗时。严格限制 LLD 的发现频率(通常建议 1 小时以上),并慎用Text和Log类型监控项。对于单纯的日志采集,请移步 ELK 或 Loki,Zabbix 不是日志垃圾桶。 -
MySQL InnoDB 参数防御 确保
innodb_buffer_pool_size分配了机器物理内存的 60%-70%;innodb_flush_log_at_trx_commit在极致性能要求且允许少量数据丢失时可设为2;innodb_io_capacity需根据底层 SSD/NVMe 性能调高(如设为 10000+)。