Zabbix 队列风暴的元凶往往不是 Server 计算能力不足,而是底层数据库 IO 瓶颈与 Proxy 离线数据猛灌。本文通过排查某次 NVPS 突发至 35k 导致的 History Syncer 打满与监控瘫痪事件,深入解析 MySQL 8.0 针对 Zabbix 写入优化的底层逻辑,并给出 Proxy 防御性配置与模板预处理过滤的实战方案。
故障现场:History Syncer 进程 100% 死亡螺旋
某次排查过程中,一套承载 20,000+ 主机、2,000,000+ 监控项的 Zabbix 6.0.22 LTS 集群突发严重告警。现象非常典型:
-
Zabbix Queue 爆炸:延迟超过 10 分钟的 item 数量瞬间飙升到 150,000 以上。
-
内部进程打满:Zabbix Server 告警
Zabbix server history syncer processes more than 100% busy。 -
前端瘫痪:Web 界面卡死,报
Zabbix server is not running: the information displayed may not be current。
查看 zabbix_server.log,满屏的慢查询和超时:
23851:20231012:142211.512 [Z3005] query failed: [1205] Lock wait timeout exceeded; try restarting transaction [insert into history_uint (itemid,clock,ns,value) values (152342,1697101321,213412,0),(...)]
23851:20231012:142215.123 server #12 active [history syncer #4]
23851:20231012:142215.123 Zabbix server history syncer processes 100% busy
很明显,数据落盘卡住了。History Syncer 负责将内存 cache 中的监控数据批量写入底层 MySQL。一旦它被阻塞,Server 内存中的 History Cache 会迅速耗尽,触发自我保护机制,拒绝接收任何新数据,最终导致 Poller 和 Trapper 进程全部雪崩。
登陆底层 MySQL 8.0.34 节点,敲下 iostat -x 1,看到数据盘的 %util 稳稳地锁死在 100%,await 高达 200ms+。
为什么 Proxy 断网恢复会导致 Zabbix Server 瞬间雪崩?
排查发现,在雪崩发生前 15 分钟,某跨机房专线发生了短暂抖动。该机房部署了 3 台 Zabbix Proxy(Active 模式),承载了约 8,000 台主机的监控抓取。
这里牵扯到 Zabbix Proxy 的底层工作机制。Proxy 默认会将采集到的数据暂存在本地的 SQLite3(或 MySQL)中。当与 Server 断开连接时,Proxy 会根据 ProxyOfflineBuffer 的配置(默认 1 小时)在本地堆积数据。
雪崩的逻辑链条:
-
专线抖动,3 台 Proxy 与 Server 失联,期间不断采集并缓存数据到本地。
-
专线恢复,Proxy 瞬间将积压的数十万条历史数据打包。
-
Proxy 根据
DataSenderFrequency=1(每秒发送)无脑向 Server 的 Trapper 进程猛灌。 -
Server 的 Trapper 进程将海量数据塞入 History Cache。
-
History Syncer 进程全速运转,向 MySQL 发起天量
INSERT INTO history...请求。 -
MySQL InnoDB Buffer Pool 的脏页刷新速率跟不上写入速率,Redo Log 爆满,触发同步刷盘,导致 IO 彻底僵死。
防御性配置:限制 Proxy 突发流量
为了防止类似情况再次发生,必须对 Proxy 的回传机制进行限流。
1. 调优 Proxy 端缓存发送频率与体积
不要让 Proxy 一次性将积压数据全吐出来。在 zabbix_proxy.conf 中调整:
# ProxyOfflineBuffer=1 # 离线缓存保留时间,不要设置太大,无意义的历史数据宁可丢弃
DataSenderFrequency=1
# 增加批量发送的限制(隐式受制于 Server 端 Trapper 进程处理能力)
注:Zabbix 6.0 引入了 Proxy 内存缓存机制,但在面对海量离线回传时,核心仍是保护 Server 的 DB IO。
2. 调优 Server 端接收与刷盘并发
在 zabbix_server.conf 中调整:
# 控制 Trapper 进程数,不要无限调大,防止压垮 History Cache
StartTrappers=50
# 增加 History Cache 大小,做大内存缓冲池,争取时间
HistoryCacheSize=2G
HistoryIndexCacheSize=512M
# 增加 Syncer 进程数,但不要超过 DB 磁盘阵列的物理 IO 并发能力上限
StartHistoryPollers=20
数据库底层调优:拯救被压垮的 MySQL 8.0
Zabbix 是一个典型的“读少写极其密集”的系统,标准的 MySQL 默认配置在这里就是灾难。针对本次 IO 瓶颈,我们在 my.cnf 中进行了以下针对性调优。
1. 禁用 Doublewrite Buffer 与放宽事务持久性
由于 Zabbix 数据并非金融级账本,丢失一两秒的监控数据完全可以接受。
[mysqld]
# 核心:将事务刷盘策略改为 2。每次提交仅写入 OS Cache,每秒刷盘一次。
# 直接将 IOPS 需求降低一个数量级。
innodb_flush_log_at_trx_commit = 2
# 针对支持原子写的存储设备(如现代 NVMe SSD 或部分企业级 SAN),关闭双写缓冲
innodb_doublewrite = 0
# 优化 Redo log 大小,防止频繁触发 Checkpoint 导致 IO 抖动
innodb_redo_log_capacity = 4G # MySQL 8.0.30+ 的新参数,替代旧的 innodb_log_file_size
2. 匹配硬件的 InnoDB IO 刷盘能力
MySQL 默认假设你的磁盘很慢(innodb_io_capacity=200),这会导致在 SSD 环境下脏页刷得太慢,最终堆积引发急剧抖动。
# 根据实际 FIO 测试结果配置
innodb_io_capacity = 3000
innodb_io_capacity_max = 6000
innodb_flush_sync = OFF # 避免 Checkpoint 时卡死用户查询
3. 表分区与废弃 Housekeeper
导致底层 IO 缓慢的另一个隐患是 Zabbix 自带的 Housekeeper 清理进程。它通过 DELETE FROM history WHERE clock < ... 清理过期数据,这在海量数据下会产生巨大的锁竞争和 Undo Log 开销。
必须彻底关闭 Housekeeper 对历史表和趋势表的清理:
Web 界面:Administration -> General -> Housekeeping,关闭 History and Trend 的 Enable internal housekeeping。
替代方案:使用 MySQL 表分区(Table Partitioning)。
每天为 history、history_uint、history_str 等表建一个新分区,清理数据时直接 ALTER TABLE ... DROP PARTITION,这是元数据操作,耗时 0.1 秒,没有任何 IO 负担。
自定义模板防作死指南:在 Proxy 侧掐断垃圾数据
数据库调优只是续命,真正的治本之策是降低 NVPS(New Values Per Second)。排查中发现,某开发团队的自定义模板中,有一个抓取应用日志错误状态的 item,类型居然是 Text,且每 5 秒抓取一次。无论状态是否改变,全量文本都在往 DB 里塞,直接打爆了 history_str 表。
过滤绝招:Discard unchanged with heartbeat
利用 Zabbix 的 Preprocessing(预处理)功能,直接在 Proxy 内存中过滤掉无用数据,根本不让它通过网络发给 Server。
配置步骤:
-
打开 Item 的
Preprocessing选项卡。 -
添加 Step:
Discard unchanged with heartbeat。 -
参数设置为
1h(或3600s)。
原理解析: 如果该 Item 的值相比上次抓取没有发生变化,Proxy 会直接将这个数据丢弃,不往 Server 发送。只有当值发生变化,或者超过设定的 heartbeat 时间(比如 1 小时没有变化),才会发送一次数据保持激活。 仅仅配置了这一项,我们的整体 NVPS 从 35,000 直接断崖式下降到 12,000,MySQL IO 负载瞬间降至 15% 以下。
常见问题
Q1:Web 界面经常报 Zabbix Server is not running,但查看进程都在,怎么回事?
通常是因为 PHP 前端通过 TCP 10051 端口请求 Server 的 StartTrappers 进程超时。大概率是因为 History Syncer 阻塞,导致 Trapper 进程全都在等待获取 Cache 锁。检查数据库负载,或适当增加 StartTrappers 数量。
Q2:StartPollers 到底设置多少合适?为什么我设了 1000 还是不够?
千万不要无脑调大 Poller 数量。Poller 过多会导致严重的上下文切换和内存消耗。查看 Zabbix server data collector processes 图表,如果 Poller 使用率长期 > 75%,首先应该考虑将监控项改为 Active 模式(让 Agent 主动推),或者把采集任务剥离给下层 Proxy。
Q3:存在大量 SNMP 监控导致 Poller 经常超时卡死,如何缓解?
SNMP 采用 UDP,极易丢包阻塞。最佳实践:1) 将 SNMP 采集全部下放给专属的 Zabbix Proxy,将故障隔离;2) 在 Host 级别勾选 Use bulk requests;3) 在 zabbix_server.conf 和 zabbix_proxy.conf 中增加 Timeout=15(默认只有 3 秒,对于老旧交换机绝对不够)。