1. Zabbix 中文乱码到底卡在哪一层
Zabbix Web 界面里中文变成方块、问号、或者干脆一片空白,是运维圈里出现频率极高的老问题。它不像服务宕机那样有明确报错,而是「页面能打开、监控数据也在跑,就是中文看着像外星文」。很多人第一反应是去改字体,改完发现图表里的中文好了,但主机名、触发器描述还是乱码;也有人直接去动数据库编码,结果把历史数据搞坏。根本原因在于:Zabbix 的中文显示要同时经过PHP 字符集处理 → MySQL 存储编码 → 前端字体渲染三条链路,任何一条断了都会乱码,而不同环节的乱码表现还不一样。
这篇内容适合正在维护 Zabbix 5.x / 6.x / 7.x 的运维和 SRE,也适合刚接手监控系统、被中文方块折磨到怀疑人生的同学。我会按「先定位、再分层修」的思路,把三种配置方案讲清楚,同时给出用 TaoToken 统一 Key/API 通道做配置管理的可复制骨架——因为排查乱码时经常要反复改配置文件、对比不同环境的参数,把模型调用和配置生成收敛到一个入口会省很多事。下面从最常见的现象开始拆。
2. 先分清三种乱码现象,别一上来就改字体
乱码不是一种病,是三种病长得像。定位错了,改半天也没用。
第一种是图表里的中文变方块。Zabbix 用 GD 库的imagettftext()渲染图表文字,如果指定的字体文件里没有中文字形,就会画出一堆空心方块。这种乱码只出现在图形(Graph)上,页面文字正常。
第二种是页面文字变问号或乱码。主机名、模板名、触发器名称显示成???或测试这种,说明数据在 MySQL 里存的时候编码就不对,或者 PHP 连接数据库时字符集没对齐。这种乱码在列表页、详情页到处都是。
第三种是新写入的中文正常、老数据乱码。典型是数据库从 latin1 迁到 utf8 时只改了表结构没转数据,或者反过来。这种最坑,因为你会以为「已经修好了」,结果翻历史记录又炸。
我一般按这个顺序排查:先看乱码出现在图表还是页面文字,再看是全部中文乱还是部分乱,最后查数据库里实际存的是什么。用一条 SQL 就能验证:
-- 连到 zabbix 库,看实际存储的字节 SELECT host, HEX(host) FROM hosts WHERE host LIKE '%测试%' LIMIT 5;如果HEX()出来的字节和 UTF-8 编码对不上(比如中文「测」的 UTF-8 是E6B58B),那问题就在存储层,改字体是白费功夫。
3. TaoToken 前置:把配置生成和排查收敛到一个通道
排查乱码要反复改defines.inc.php、zabbix_server.conf、数据库连接参数,还要在不同环境之间对比。如果每次都用聊天窗口零散地问、复制粘贴,配置很容易改乱。我的做法是把模型调用统一走 TaoToken 的 API 通道,用一个 Key 管住所有配置生成和排障问答。
TaoToken 在这里的角色是「统一的模型/API 入口」:你不需要在多个平台之间切换 Key,也不用担心某个通道突然不可用。对于 Zabbix 这种要长期维护、经常要生成配置片段和排查脚本的场景,把调用收敛到一个入口,配置的可复现性会好很多。
先拿到 Key。打开控制台创建:
# 控制台地址(创建 API Key) https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite创建后把 Key 存到环境变量,别硬编码进脚本:
export TAOTOKEN_API_KEY="sk-你的key"API 基地址统一用:
https://taotoken.net/api如果你要长期跑编码类任务、或者让 Agent 自动生成排障脚本,可以看 Coding Plan:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite接入文档在这里,参数细节以文档为准:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite4. 可复制配置:config.toml 与 settings.json 骨架
下面给两份配置骨架。config.toml用于命令行工具或服务端脚本,settings.json用于编辑器/客户端类工具。两份都指向同一个 TaoToken 通道,Key 从环境变量读,避免泄露。
先看config.toml:
# ~/.config/taotoken/config.toml # Zabbix 排障脚本统一走这个通道 [api] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" timeout_seconds = 60 [model] # 生成配置片段、排查 SQL、解释报错用 default = "claude-sonnet" # 需要长上下文分析日志时切换 long_context = "claude-opus" [project] name = "zabbix-charset-fix" # 把排查过程用到的文件路径记下来,方便复现 workdir = "/opt/zabbix-debug"再看settings.json:
{ "provider": "taotoken", "api": { "baseUrl": "https://taotoken.net/api", "apiKeyEnv": "TAOTOKEN_API_KEY", "timeout": 60000 }, "models": { "default": "claude-sonnet", "fallback": "claude-haiku" }, "context": { "project": "zabbix-charset-fix", "includePaths": [ "/etc/zabbix/zabbix_server.conf", "/usr/share/zabbix/include/defines.inc.php" ] } }两份配置的关键点是一样的:base_url指向https://taotoken.net/api,Key 走环境变量,模型名按需切换。这样你在排查乱码时,无论是让模型生成 SQL、解释 PHP 报错,还是对比不同环境的配置差异,都走同一条通道,不会出现「这个工具能用那个工具报 401」的情况。
5. 三种配置方案逐层修:字体、数据库、PHP
5.1 方案一:替换前端字体(治图表方块)
图表中文变方块,九成是字体问题。Zabbix 默认用DejaVuSans,这个字体不含中文字形。改法是在defines.inc.php里把字体名换成一个含中文的字体。
先确认字体文件存在:
# 找系统里的中文字体 fc-list :lang=zh | head -20常见的有simkai(楷体)、wqy-zenhei(文泉驿正黑)、Noto Sans CJK。假设你用文泉驿:
# 把字体复制到 Zabbix 字体目录 cp /usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc \ /usr/share/zabbix/assets/fonts/然后改defines.inc.php:
// /usr/share/zabbix/include/defines.inc.php // 找到这两行,把 DejaVuSans 换成 wqy-zenhei define('ZBX_FONT_NAME', 'wqy-zenhei'); define('ZBX_GRAPH_FONT_NAME', 'wqy-zenhei');注意:Zabbix 6.0 之后字体配置移到了zabbix.conf.php或 Web 界面的「管理 → 常规 → 图形」里,改文件的方式在新版本可能不生效。改完清一下缓存:
# 清 Web 缓存(路径按实际部署调整) rm -rf /var/lib/zabbix/web/cache/* systemctl reload nginx刷新页面看图表,方块应该变成正常中文了。如果还是方块,说明字体文件路径不对或字体本身不含中文,用fc-list再确认一遍。
5.2 方案二:修正 MySQL 字符集(治页面问号)
页面文字乱码,根子在数据库。先查当前编码:
-- 看数据库、表、列的字符集 SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'zabbix'; SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'zabbix' LIMIT 10;如果看到latin1或utf8mb3,就要转成utf8mb4。先备份,再操作:
# 备份,别省这一步 mysqldump -uzabbix -p --default-character-set=latin1 \ --skip-set-charset zabbix > /tmp/zabbix_backup.sql转换时用iconv把 latin1 字节转成 UTF-8,再导入:
# 把备份文件从 latin1 转 utf8 iconv -f latin1 -t utf8 /tmp/zabbix_backup.sql > /tmp/zabbix_utf8.sql # 建新库,指定 utf8mb4 mysql -uroot -p -e "CREATE DATABASE zabbix_new \ CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;" # 导入 mysql -uroot -p zabbix_new < /tmp/zabbix_utf8.sql导入后改 Zabbix 的数据库连接配置,指向新库,并确保连接串里指定字符集:
// /etc/zabbix/web/zabbix.conf.php $DB['TYPE'] = 'MYSQL'; $DB['DATABASE'] = 'zabbix_new'; $DB['ENCODING'] = 'UTF8'; // 关键,别漏重启服务后刷新页面。如果部分老数据还是乱码,说明备份时字符集判断错了,得回到原始库重新确认实际存储编码,别硬转。
5.3 方案三:改 PHP 渲染逻辑(治顽固乱码)
前两种都试过还乱,问题可能在 PHP 的 GD 渲染层。Zabbix 用imagettftext()画图,如果 PHP 编译时带了--enable-gd-jis-conv,会把非 ASCII 字符按 JIS 处理,中文就废了。
先查 PHP 是否带了这个选项:
php -i | grep -i "jis"如果有输出,说明编译时开了。最彻底的办法是重新编译 PHP 去掉这个选项,但代价大。轻量做法是在graphs-inc.php里加一个转实体函数,把多字节字符转成 HTML 实体再交给 GD:
// /usr/share/zabbix/include/graphs-inc.php // 在 imagettftext() 调用前定义 function to_entities($string) { $len = strlen($string); $buf = ""; for ($i = 0; $i < $len; $i++) { if (ord($string[$i]) <= 127) { $buf .= $string[$i]; } else if (ord($string[$i]) < 192) { $buf .= "?"; } else if (ord($string[$i]) < 224) { $buf .= sprintf("&#%d;", ((ord($string[$i]) & 31) << 6) + (ord($string[$i + 1]) & 63)); $i += 1; } else if (ord($string[$i]) < 240) { $buf .= sprintf("&#%d;", ((ord($string[$i]) & 15) << 12) + ((ord($string[$i + 1]) & 63) << 6) + (ord($string[$i + 2]) & 63)); $i += 2; } else { $buf .= sprintf("&#%d;", ((ord($string[$i]) & 7) << 18) + ((ord($string[$i + 1]) & 63) << 12) + ((ord($string[$i + 2]) & 63) << 6) + (ord($string[$i + 3]) & 63)); $i += 3; } } return $buf; }然后把该文件里imagettftext()的最后一个$string参数改成to_entities($string)。改完不用重启,刷新图表就能看到中文正常了。这个方法的原理是把 UTF-8 多字节序列转成&#数字;实体,绕开 GD 的 JIS 转换逻辑。
6. 验证请求:确认乱码真的消除了
改完别急着收工,按下面几步逐项验证。
第一步,验证数据库存储:
SELECT host, HEX(host) FROM hosts WHERE host LIKE '%测试%' LIMIT 3;中文「测」的 UTF-8 是E6B58B,如果HEX()输出里能看到这个序列,说明存储层对了。
第二步,验证 PHP 连接字符集:
# 写个临时脚本查连接字符集 php -r ' $m = new mysqli("localhost","zabbix","密码","zabbix"); $r = $m->query("SHOW VARIABLES LIKE \"character_set_client\""); print_r($r->fetch_assoc()); 'character_set_client应该是utf8mb4。
第三步,验证图表渲染。打开一个含中文主机名的图表,看坐标轴和图例。如果还是方块,回到方案一确认字体路径。
第四步,用 TaoToken 通道跑一次配置对比,确认不同环境的参数一致:
curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet", "max_tokens": 512, "messages": [{ "role": "user", "content": "对比以下两个 Zabbix 配置的字符集设置差异,指出可能导致中文乱码的项:\nA: DB ENCODING=UTF8, PHP default_charset=UTF-8\nB: DB ENCODING=latin1, PHP default_charset=ISO-8859-1" }] }'返回正常说明通道可用。想直接在对话里验证模型输出,用模型对话入口:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite7. 本篇常见错排查
改了 defines.inc.php 没生效。Zabbix 6.0+ 字体配置在数据库或zabbix.conf.php里,改文件被覆盖。去「管理 → 常规 → 图形」改,或直接更新config表的default_font字段。
数据库转了 utf8mb4 但页面还是问号。检查zabbix.conf.php里的$DB['ENCODING'],很多人只改库不改连接配置。另外确认 PHP 的default_charset是UTF-8。
图表中文好了,主机名还是乱。这是两个独立问题:图表走字体,主机名走数据库。分别按方案一和方案二处理,别指望一个方案通吃。
iconv 转换后数据更乱了。说明原始编码判断错了。先用file -i或hexdump确认备份文件的实际编码,再决定iconv的-f参数。latin1 和 gbk 转错了会不可逆。
PHP 加了 to_entities 后图表报错。检查函数是否定义在imagettftext()调用之前,PHP 函数要先定义后使用。另外确认graphs-inc.php的路径和 Zabbix 版本匹配,不同版本文件名可能不同。
TaoToken 请求返回 401。确认TAOTOKEN_API_KEY环境变量已导出,且base_url是https://taotoken.net/api(不带 UTM)。Key 在控制台重新生成后旧 Key 会失效。
8. 把排查流程固化成可复用的配置
乱码修一次不难,难的是下次换环境又得从头来。我的做法是把三种方案的判断逻辑写成一个脚本,用 TaoToken 通道生成对应的修复命令,存到config.toml的workdir里。这样新环境部署时,先跑诊断脚本确认乱码层级,再按输出执行对应方案,不用凭记忆翻文档。
如果你要长期维护多套 Zabbix,建议把 API Key 和接入配置统一管起来,接入文档里有完整的参数说明:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite需要批量生成排障脚本或让 Agent 自动处理,走 Coding Plan 更顺:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite最后提醒一句:动数据库之前一定备份,iconv转错方向的数据基本救不回来。字体方案最安全,先试它;数据库方案影响面大,放到最后。