Zabbix后端MySQL瓶颈:TiDB分布式迁移实战与踩坑记录
做监控运维的兄弟多少都见过这种画面Zabbix 的采集规模上去之后后端 MySQL 一天比一天吃力history 表像滚雪球一样膨胀查询报表点一次卡半天清理历史数据的任务跑起来整个库都在抖。我最近刚完成的一个迁移项目就是把 Zabbix 的 MySQL 数据完整导入到 TiDB 服务里运行彻底换掉 Zabbix 的后端数据库。今天这篇就把整个项目的思路、迁移步骤和踩坑过程完整记录下来。这个方案本质上等于给 Zabbix 换了一个“MySQL 协议兼容的分布式数据库”不需要改一行应用代码。Zabbix Server、Zabbix Proxy、前端 PHP 全部通过 MySQL 驱动连到 TiDB日常使用和原来的 MySQL 几乎没区别。它适合正在被监控数据量压榨的运维团队也适合想研究 TiDB 与 MySQL 生态迁移的同学参考。先说结果我这边完成了 200GB 级别历史数据的迁移Zabbix Server 切换到 TiDB 后运行稳定历史查询和大范围报表的响应比原来单机 MySQL 有明显提升。下面按“为什么换、怎么准备、怎么迁、怎么切、怎么养”五个部分展开。1. 为什么把 Zabbix 的 MySQL 后端换成 TiDB1.1 监控数据模型把 MySQL 逼到了墙角Zabbix 的数据模型其实很“吃”数据库。每天高频写入的是 history 系列表比如history、history_uint、history_str这些表负责存原始监控数据每条记录就是一个采样点。按一个中型规模估算1000 台主机、每台机器 100 个监控项、采集间隔 30 秒一年下来history_uint单表会有 1000 × 100 × 2 × 60 × 24 × 365 ≈ 10.5 亿行。这种量级放在单机 MySQL 上无论怎么优化都会撞到硬盘和内存的物理墙。更让 MySQL 难受的是 Zabbix 的清理机制。housekeeper 进程要不断删除过期历史数据而DELETE在 MySQL 里本质是写操作删除几百万行会带来主从延迟、锁竞争、undo 膨胀。删除和采集全挤在同一台实例上业务查询很容易被拖垮。除此之外容量规划也很头疼。硬盘加到几个 TB 之后备份时间、故障恢复时间都变得不可控主从复制也随时可能因为 binlog 积压断开。这些问题不是靠“再买一台更强的机器”能解决的因为垂直扩容总归有天花板。1.2 TiDB 的 MySQL 协议兼容是决定性优势选型的时候我对比过几个方案。直接换更强配置的 MySQL 属于短期止痛数据量继续增长该出的问题还会出。TimescaleDB 是 Zabbix 官方推荐的方向之一但改造量不小需要把数据库从 MySQL 换成 PostgreSQLSchema 和查询习惯都要跟着变而且对团队的新增知识栈要求也高维护上并不轻。TiDB 对我来说最诱人的一点就是“协议兼容”。它对外使用的是 MySQL 协议SQL 语法大体兼容Zabbix 侧的连接驱动、PHP 前端、查询语句基本不用动。TiDB 内部数据自动按 Region 分片存在多个 TiKV 节点上扩容就是加节点PD 会自动做负载均衡。数据库层面从“单机”变成“分布式”Zabbix 的外在体验又保持不变。1.3 是“锦上添花”还是“雪中送炭”取决于你的监控规模这里需要说句实在话不是所有 Zabbix 环境都适合上 TiDB。如果你只有几百台主机、历史数据能控制在几十 GB 以内那继续用 MySQL SSD 分区表完全够用完全没有必要引入分布式数据库的运维成本。TiDB 的收益要到 TB 级历史数据或写入吞吐明显触顶的时候才真正体现。我当时判断迁移的核心指标有三个历史表月增超过 50GB、报表查询耗时超过 10 秒、清理任务频繁拖慢采集。三个中占两个就值得认真评估 TiDB 了。方案应用改造量扩展上限运维复杂度适用阶段单机 MySQL 升级配置零受单机限制低小规模 / 初期MySQL 主从 读写分离零有限的读扩展中读多写少的过渡期Zabbix TimescaleDB大依赖单机资源中官方推荐路线之一Zabbix TiDB零横向扩展中高历史数据量大、写放大明显2. TiDB 集群部署和 Zabbix 兼容性预检动手迁移前的关键一步2.1 版本选型和集群部署方式我这次采用的组合是 Zabbix 5.0 LTS TiDB 6.5 LTS。Zabbix 5.0 是目前很多团队还在用的长期支持版TiDB 6.5 也是长期支持版本两个都是相对保守的选择。如果读者用的是 Zabbix 6.0 或 7.0方案本身没有区别数据库层面完全不涉及版本逻辑。TiDB 集群部署我用的是官方 TiUP 工具这是目前最标准的部署方式。测试环境可以用单机把 TiDB、PD、TiKV 三个组件叠在一起跑生产环境至少应该保证 3 台 TiKV、3 台 PD、2 台以上 TiDB Server。我实际项目里一共规划了 6 台机器3 台混部 PD TiDB Server3 台独立 TiKV成本和性能折中比较合适。# 安装 TiUP 组件管理器 curl --proto https --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh # 使用拓扑文件部署一个名为 zbxtidb 的集群 tiup cluster deploy zbxtidb v6.5.0 ./topology.yaml --user tidb -p # 启动集群并查看组件状态 tiup cluster start zbxtidb tiup cluster display zbxtidbtopology.yaml的写法比较简单核心就是把每台机器的角色和目录声明清楚。TiKV 的目录一定要放在 SSD 上因为 TiKV 底层对磁盘 IO 的敏感度非常高机械盘跑 TiKV 很可能成为整个集群的短板。global: user: tidb ssh_port: 22 deployment_dir: /data/tidb pd_servers: - host: 192.168.1.11 - host: 192.168.1.12 - host: 192.168.1.13 tidb_servers: - host: 192.168.1.11 - host: 192.168.1.12 - host: 192.168.1.13 tikv_servers: - host: 192.168.1.14 data_dir: /data/tikv - host: 192.168.1.15 data_dir: /data/tikv - host: 192.168.1.16 data_dir: /data/tikv2.2 迁移前的兼容性预检清单很多迁移项目翻车就翻在“以为兼容实际上有一处不兼容”。Zabbix 的数据库 Schema 不算复杂但提前做一轮兼容性预检非常值得。我推荐的方法简单粗暴在新 TiDB 集群上手动建一个空库把 Zabbix 官方提供的create.sql直接灌进去看有没有报错。一般来说 Zabbix 的 Schema 不会踩到 TiDB 的硬限制因为 TiDB 对 MySQL 的建表语句兼容性已经做得很成熟包括ENGINEInnoDB这种声明也会自动忽略。Zabbix 表之间没有外键约束不存在存储过程依赖而且官方 Schema 中确实用到了视图TiDB 也是支持的。真正需要留意的反而是字符集排序规则和自增主键这类细节。检查项预期结果说明ENGINE 声明兼容TiDB 会忽略引擎名并走自研存储外键约束无依赖Zabbix 表结构不依赖外键存储过程无依赖官方 Schema 不含存储过程视图兼容TiDB 支持 CREATE VIEW自增主键兼容小表自增没问题大表看主键类型全文索引未使用Zabbix 主要靠普通索引字符集utf8mb4建议原库和 TiDB 统一为 utf8mb4_bin2.3 小规模试跑必须做别拿生产开玩笑预检不只是验证建表还要验证典型查询。我习惯把一个月的采样数据导入试跑库然后执行 Zabbix 前端最常用的几类 SQL最新采集值查询、趋势聚合查询、主机可用性统计。这些语句能跑通迁移才有保障。试跑阶段要特别注意两个点。第一是lower_case_table_names的大小写敏感性如果原 MySQL 是 0区分大小写那 TiDB 侧也要保持一致否则前端某些代码生成 SQL 时可能因为大小写对不上表而出问题。第二是时区函数的行为差异比如FROM_UNIXTIME、DATE_FORMAT这类函数TiDB 和 MySQL 在参数和结果上大体一致但时区变量不同会引出显示偏差这个在后面切库时还会说到。3. 数据迁移全流程Dumpling 导出、Lightning 导入与一致性校验3.1 停机迁移比在线迁移更适合监控库Zabbix 的数据是无时无刻不在写入的要做全量迁移绕不开一致性问题。当时我评估了两条路一是部署 DM 同步工具做在线迁移从 MySQL 拉 binlog 持续同步到 TiDB最后再切换二是在维护窗口停掉 Zabbix Server直接全量导出导入然后切库。我最终选了停机迁移。原因是监控系统的数据链路虽然不能断太久但选在凌晨低峰期停 10 到 30 分钟完全可接受而 DM 在线迁移需要额外部署节点、处理 DDL 同步策略链路越长出问题的可能性越大。全量迁移的工具链 Dumpling Lightning 已经足够成熟整个过程可控可复现。3.2 用 Dumpling 从 MySQL 导出 Zabbix 库先做准备工作在 MySQL 上建一个用于导出的账号权限要覆盖读取全部数据同时需要有LOCK TABLES和REPLICATION CLIENTDumpling 会靠这些权限做一致性快照。CREATE USER dumper% IDENTIFIED BY xxxxxx; GRANT SELECT, RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO dumper%;导出命令使用 TiUP 的 dumpling 组件。参数里-B zabbix指定只导出 zabbix 库--threads 8控制并发度--filetype sql让导出结果保留建表语句和插入语句方便后续直接用 Lightning 识别。tiup dumpling -u dumper -p xxxxxx -h 192.168.1.10 -P 3306 -B zabbix \ -o /data/dumpling_out --threads 8 --filetype sql这个命令跑完后/data/dumpling_out下面会按表生成 schema 和 data 文件metadata 文件记录了导出时的 binlog 位置虽然全量迁移用不上但留着总没坏处。大表会被自动切成多个文件避免单个 SQL 文件过大导致导入时内存压力过高。3.3 TiDB Lightning 导入比 mysqldump source 快一个量级导入侧我强烈推荐 TiDB Lightning而不是直接拿 mysqldump 的文件去 source。Lightning 的 local backend 模式会把数据直接编码成 TiKV 存储格式跳过传统 SQL 执行引擎导入速度通常能比逐条执行 INSERT 快很多。200GB 的数据在合理配置下基本能在 1 小时内完成导入。先在 TiDB 里手动建好空库和业务账号这样做的好处是排序规则完全可控不会被导出文件里的建库语句干扰。CREATE DATABASE IF NOT EXISTS zabbix CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; CREATE USER zabbix% IDENTIFIED BY xxxxxx; GRANT ALL PRIVILEGES ON zabbix.* TO zabbix%; SET GLOBAL time_zone 08:00; SET GLOBAL tidb_mem_quota_query 4294967296;Lightning 的配置文件不长核心有四个部分日志、导入模式、数据源地址、TiDB 连接地址。[lightning] level info file tidb-lightning.log [tikv-importer] backend local sorted-kv-dir /data/sorted-kv [mydumper]>tiup tidb-lightning -c lightning.toml如果源 MySQL 里的大表已经做了分区Dumpling 导出的建表语句也会带着分区子句。为了减少导入期的不确定性我建议先把 schema 文件里的分区子句去掉按普通表导入完成后再在 TiDB 上重新分区。TiDB 6.5 支持 range 分区但分区边界尽量用整数常量避免函数表达式带来的兼容性隐患。3.4 一致性校验让数据“闭环”而不是“凭感觉”导入完成后不能急着切库。我正在用的校验方法分三层第一种是按行数对比。大表直接SELECT COUNT(*)会跑很久可以先用information_schema.tables里的TABLE_ROWS粗看再抽查几个关键表做精确COUNT(*)。第二种是 TiDB 自带的校验命令Lightning 导入完成时本身就会做一次 checksum也可以手动执行ADMIN CHECKSUM TABLE zabbix.history_uint;第三种是业务层校验。监控类数据最怕的是丢最新数据和乱时序所以我对比了history_uint表的MAX(clock)、MIN(clock)并随机抽几个监控项看最近一天的采样点在源库和 TiDB 上是否一致。只要这几层校验通过就可以进入切库环节。4. Zabbix 切库到 TiDB配置、验证与两个真实踩坑记录4.1 Server 和前端配置改动点切库的动作本身很小改两处配置然后重启服务。第一处是 Zabbix Server 的/etc/zabbix/zabbix_server.conf核心是四个参数DBHost192.168.1.11 DBPort4000 DBNamezabbix DBUserzabbix DBPasswordxxxxxx这个文件里最容易漏的是DBPort。因为 TiDB 默认服务端口是 4000不是 MySQL 的 3306。如果这行不写或者写成 3306Zabbix Server 会一直报连接失败排查半天都找不到原因。第二处是前端 PHP 的zabbix.conf.php同样要改服务器地址和端口$DB[SERVER] 192.168.1.11; $DB[PORT] 4000; $DB[DATABASE] zabbix; $DB[USER] zabbix; $DB[PASSWORD] xxxxxx;如果环境里 Zabbix Proxy 也直接写后端库Proxy 进程的配置需要同步修改否则会出现 Server 和 Proxy 分别连不同库的诡异现象。这个点比较容易忽略建议在切换清单里单独列出来。4.2 启动验证的正确顺序切库之后不要直接把 Zabbix Server 拉起来就完事。我的验证顺序是先用 mysql 客户端手动连一下 TiDB确认网络、账号、端口都没问题再启动 Zabbix Server盯日志最后才打开前端页面。mysql -h 192.168.1.11 -P 4000 -u zabbix -p zabbix -e SELECT 1 systemctl start zabbix-server tail -f /var/log/zabbix/zabbix_server.log日志里看到 “Zabbix server started” 之后回到前端页面确认状态。更关键的验证是数据链路是否真的通登录 TiDB 查history_uint表最新时钟值看clock是不是在持续增长。如果是说明采集数据已经真实写入了 TiDB。SELECT itemid, MAX(clock), MAX(value_uint) FROM zabbix.history_uint GROUP BY itemid ORDER BY MAX(clock) DESC LIMIT 5;4.3 踩坑记录一housekeeper 批量删除撞上 TiDB 事务保护切库后前几个小时的平稳不算什么真正的坑通常在第一个清理周期出现。我在第二天早上例行检查时发现历史数据没有被清理Zabbix Server 日志里反复出现transaction too large和Too many keys这样的错误。这个坑的本质是分布式事务的物理限制。单机 MySQL 执行一条影响几十万行的DELETE虽然慢但能执行TiDB 为了保证分布式事务可控对单事务的键值数量和大小的限制更严格。Zabbix 的 housekeeper 虽然默认有每秒删除行数限制但在多线程场景下一旦某批删除超过 TiDB 的单事务阈值整个事务就会被 TiDB 拒绝清理任务失败。处理分两步走。短期应急是在zabbix_server.conf里调低MaxHousekeeperDelete并合理设置HousekeepingFrequency让每次删除的行数更平缓。长期根治方案则更彻底放弃 housekeeper 对历史大表的删除把 history 系列表做成按时间 range 分区定期直接DROP PARTITION。删除分区的开销比逐行 delete 小几个数量级这也是我在生产环境里最终采用的方案。4.4 踩坑记录二大查询内存超限和时区显示偏差第二个坑来自前端。某天业务方反馈打开某个跨多月报表时页面偶发报错Lost connection to MySQL server during query。查 TiDB 日志发现是查询触发了单查询内存限制。Zabbix 前端一次拉取大规模趋势数据时结果集可能非常大TiDB 对单查询内存是有默认配额的超了会被直接终止。解决方法是调整 TiDB 的tidb_mem_quota_query参数同时引导使用侧限制查询范围。我在全局级别设置成了 4GB并且把 Zabbix 默认的趋势查询时间范围做了约束避免“一次全选半年数据”的情况。时区偏差是另一个容易忽视的小坑。TiDB 的time_zone变量如果和原 MySQL 不一致FROM_UNIXTIME等日期函数转换出的时间会出现几小时偏移。Zabbix 内部大部分时间字段用 UNIX 时间戳影响不大但某些报表会用数据库函数做时间格式化对不上就非常突兀。迁移后建议统一执行SET GLOBAL time_zone 08:00;并把应用连接串的时区参数固定住。5. TiDB 上长期运行分区清理、自身监控与扩容方式5.1 大表分区和清理脚本的最终形态在 TiDB 上把 Zabbix 养好关键还是把历史大表的生命周期管理起来。我的最终做法对history、history_uint、history_str、trends、trends_uint、events这些表按clock字段做 range 分区按月切分。边界值直接使用时间戳数字不写函数避免 TiDB 对分区表达式做函数解析时出问题。ALTER TABLE zabbix.history_uint PARTITION BY RANGE (clock) ( PARTITION p202401 VALUES LESS THAN (1706745600), PARTITION p202402 VALUES LESS THAN (1709251200), PARTITION p202403 VALUES LESS THAN (1711929600) );分区不是建完就完事还需要一个定期维护脚本。生产环境里我会用 crond 每月执行一次先为下个月创建新分区再删除保留期之前的旧分区。#!/bin/bash NEW_PART$(date -d 1 month %Y%m) TS$(date -d ${NEW_PART}01 0 month %s) TABLEShistory history_uint history_str trends trends_uint events for t in $TABLES; do mysql -h 192.168.1.11 -P 4000 -u zabbix -pxxxx zabbix \ -e ALTER TABLE ${t} ADD PARTITION (PARTITION p${NEW_PART} VALUES LESS THAN (${TS})) done执行 DDL 尽量放在业务低峰期。TiDB 的 DDL 不会阻塞 DML但批量DROP PARTITION会引起 TiKV 内部的 Region 重新调度观察集群状态没有异常后再进行下一个表的操作。5.2 账号权限、连接管理和 TiDB 自身监控很多团队在迁移后会直接用 root 连接 Zabbix这非常危险。TiDB 是分布式架构一个误操作影响的范围比单机 MySQL 大得多。最好给 Zabbix 单独建账号只授予 zabbix 库上所需的最小权限GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, CREATE VIEW ON zabbix.* TO zabbix%;TiDB 集群部署时默认会带上监控面板日常重点看 TiKV 的存储水位、写入延迟、Region 状态。此外我也在 Zabbix 本身上增加了对 TiDB 的监控检查 4000 端口监控项、TiDB Server 进程存活、PD 节点健康状态。看过系统监控工具忘了监控自己的情况太多了这点值得多提一句。大查询的排查效率也很重要。TiDB 提供了慢查询日志和information_schema.slow_query表可以用 SQL 直接捞 Top N 慢查询SELECT * FROM information_schema.slow_query ORDER BY Query_time DESC LIMIT 10;5.3 扩容实践加 TiKV 节点比加 TiDB Server 更常见Geo 情况明确后集群该扩容时就扩容。TiDB 横向扩容最核心的动作是加 TiKV 节点。当 TiKV 存储水位超过 70%就要考虑扩容了。工具链只认拓扑文件# 准备一个仅包含新节点的 YAML 文件 tiup cluster scale-out zbxtidb ./add_tikv.yaml tiup cluster display zbxtidb扩容之后 PD 会自动把部分 Region 调度到新节点上这个过程是平滑的不需要停服。TiDB Server 节点属于无状态计算层如果前端并发查询压力大也可以加但 Zabbix 前端 PHP 本来连接的是一个固定数据库地址加多个 TiDB Server 节点还得在前面配负载均衡收益相对有限真正决定容量上限的通常还是 TiKV 存储层。如果你现在也正在纠结是否把 Zabbix 迁移到 TiDB我的建议是先做小规模验证重点看分区裁剪和写入热点两个指标再决定上不上。监控系统的数据库一旦换过去回滚成本比普通业务库要高得多所以准备工作多花些时间完全值得。