近期处理了一起极具破坏性的监控系统瘫痪事故。现象非常经典:Zabbix Server 内部队列(Queue)在十分钟内飙升至 10 万以上,海量 nodata 触发器误报风暴瞬间淹没了告警通道,Zabbix Web 前端直接 502。最终结论很简单:业务团队在 Zabbix 中挂载了一个低效的自定义模板,每 30 秒通过被动模式(Passive Check)拉取一次包含海量无用信息的 500KB JSON 报文,并直接存入 history_text 表。这种把关系型数据库当日志库用的粗暴操作,直接打满了 Zabbix Proxy 的同步通道,并引发了后端 MySQL InnoDB 的脏页刷新风暴(Page Flush Storm)。
解决这类问题,单纯调大 Zabbix 的 HistoryCacheSize 或升级数据库磁盘只是扬汤止沸。真正的解法在于架构级的防御设计:在边缘端(Proxy/Agent)前置预处理(Preprocessing),剥离无效载荷,配合 MySQL 历史表分区(Table Partitioning)与 InnoDB 落盘策略调优,才能彻底稳住底盘。
案发现场与深层诊断
排查过程中,第一眼看到的是 Zabbix Server 自身的监控指标全盘崩溃。 提取 Zabbix Server 日志,满屏的告警触目惊心:
Zabbix server history syncer processes more than 75% busy
Zabbix server poller processes more than 75% busy
[Z3005] query failed: [1205] Lock wait timeout exceeded; try restarting transaction
直接通过运行时控制命令导出诊断信息:
zabbix_server -R diaginfo="historycache"
查看输出,History Cache 和 History Index Cache 的剩余空间几乎为零。这说明 Zabbix Server 的数据接收层已经被塞满,而负责将数据落盘的 History Syncer 进程被卡死。
此时切到后端 MySQL 节点,跑一下 iostat -x 1,发现数据盘的 %util 长期死锁在 100%,写入延时(w_await)飙升到 200ms 以上。
登入 MySQL 查看引擎状态:
SHOW ENGINE INNODB STATUS\G
发现 Modified db pages 指标极高,Buffer Pool 的脏页比例突破了 innodb_max_dirty_pages_pct 的阈值。Page Cleaner 线程疯狂刷盘,引发了严重的 I/O 拥塞,导致事务提交时写 Redo Log 发生等待。
究竟是哪个业务指标在疯狂灌水?直接查 Zabbix 数据库的 items 表:
SELECT h.host, i.key_, i.delay, i.value_type
FROM items i
JOIN hosts h ON i.hostid = h.hostid
WHERE i.status = 0 AND i.value_type IN (1, 4) -- 1: character, 4: text
ORDER BY i.delay ASC LIMIT 10;
真相大白:某个新上线的微服务,为了图省事,写了一个自定义脚本获取应用状态,返回的是一个完整的、未经任何裁剪的巨型 JSON 结构。更致命的是,配置为 Text 类型,每 30 秒采集一次,且通过被动模式(Zabbix Server/Proxy 主动建立 TCP 连接去拉取)。
为什么这个操作不可原谅?
在运维架构中,监控系统是生命线,对监控系统本身进行 DoS 攻击是极其低级的错误。
-
被动模式阻塞 Poller 进程:被动模式下,Zabbix Poller 需要等待 Agent 执行脚本并返回结果。当脚本执行耗时或报文过大时,TCP 会话长期占用 Poller 进程。默认几百个 Poller 瞬间耗尽,其他正常主机的 CPU、内存采集自然被挂起,引发大面积
nodata误报。 -
关系型数据库的文本噩梦:Zabbix 依赖的 MySQL InnoDB 对高频、大字段(Blob/Text)的插入极度不友好。海量的无序大文本写入不仅无法有效压缩,还会导致 Redo Log 极速轮转,触发 Checkpoint 强行刷脏,打爆磁盘 IOPS。把 Zabbix 当作 ELK 或 Prometheus(TSDB)来用,是典型的架构选型错位。
止血与根治方案
应对这种监控血崩,必须分级处理,先止血,再重构。
第一步:紧急熔断,恢复全局监控 直接在数据库层面禁用该危险模板的所有监控项,并清理历史缓存,重启 Server:
UPDATE items SET status = 1 WHERE key_ LIKE 'custom.app.json_dump%';
systemctl restart zabbix-server
第二步:利用 Zabbix Preprocessing 进行边缘降维 如果业务确实需要从该 JSON 中提取状态码,绝对不应将原始 JSON 透传给 Server。 利用 Zabbix 4.0 引入的 Preprocessing(预处理)功能,在 Agent 或 Proxy 端完成数据解析:
-
监控项类型改为
Numeric (unsigned)。 -
在 Item 的 Preprocessing 标签页添加:
JSONPath->$.data.status_code。 这样,Zabbix Proxy 拉取到 500KB 的 JSON 后,仅提取出数字状态码(如200或500),再由 Proxy 将这几个字节的整型数据上报给 Server。网络带宽占用降低了 99.9%,数据库写入压力直接消失。
第三步:主动模式改造
将 Item 类型从 Zabbix agent 更改为 Zabbix agent (active)。把连接发起方从 Server/Proxy 转移到 Agent 端,彻底释放 Server 端宝贵的 Poller 进程池。
第四步:MySQL 数据库底层防御性调优 针对 Zabbix 这种典型的“重写轻读”场景,必须对 InnoDB 进行专项调优:
# my.cnf
# 牺牲极端的 ACID 保证,换取成倍的写入吞吐量。Zabbix 丢失最近 1 秒的数据完全可接受。
innodb_flush_log_at_trx_commit = 2
# 提升 Buffer Pool 命中率,通常设为物理内存的 60%-70%
innodb_buffer_pool_size = 32G
# 匹配 SSD 的真实 IOPS 能力,避免 Page Cleaner 罢工
innodb_io_capacity = 3000
innodb_io_capacity_max = 6000
# 优化大事务日志
innodb_log_file_size = 2G
同时,强烈建议对 Zabbix 的 history 系列表(尤其是 history_uint 和 history)实施按天分区(Table Partitioning)。Zabbix 内部的 Housekeeper 线程执行 DELETE 语句清理旧数据时会产生海量碎片和全表锁;改成分区表后,清理历史数据仅需一条 ALTER TABLE DROP PARTITION,瞬间完成且零磁盘 I/O。
同类问题排查清单(Troubleshooting Checklist)
-
Server 性能自证:检查 Zabbix 内部监控指标
zabbix[process,history syncer,avg,busy]和zabbix[process,poller,avg,busy],若持续 >75%,说明落盘或采集被阻塞。 -
大对象排查:在 Zabbix DB 执行
SELECT key_, delay FROM items WHERE value_type=4 AND status=0;揪出文本类高频轮询项。 -
Proxy 积压检查:通过
zabbix[proxy,{$PROXY_NAME},lastaccess]监控 Proxy 心跳延迟,若延迟激增,多为 Proxy 本地 SQLite/MySQL I/O 瓶颈或 Server 拒收。 -
数据库 I/O 饱和度确认:使用
iostat -dmx 1观察w/s与%util,结合 MySQLSHOW ENGINE INNODB STATUS中的Log sequence number推进速度,判断是否遭遇 InnoDB 脏页刷新瓶颈。