Zabbix 源码 90 讲 · Day 02
源码基线:Zabbix 7.0.28
Revision:86f12eab509
设想一个很常见的排障现场:一套自行编译的 Zabbix Server 上,SNMP 监控突然全部失败。值班同事执行 zabbix_server -V,看到版本确实是 7.0.28;再打开源码,也能找到完整的 SNMP Poller 和协议实现,于是把时间都花在网络、MIB 和设备权限上。
真正改变排查方向的,却是启动日志中的一行:
SNMP monitoring: NO源码存在,不等于目标二进制编入了这项能力。“同为 Zabbix 7.0”也不等于运行产物、源码快照、构建选项和数据库状态完全相同。
Day 01 建立了系统地图,告诉我们问题属于哪个组件;Day 02 要做的,是先校准坐标,证明自己分析的究竟是哪一份代码、哪一个程序。
只写“Zabbix 7.0”,为什么不够
7.0 只确定了主版本和次版本。实际环境里还可能同时存在:
不同的 7.0.x 维护版本; Server、Proxy、Agent 和 PHP 前端版本不一致; 相同源码用不同依赖和功能开关编译出的程序; 官方源码上叠加的本地补丁或厂商 backport; 尚未执行完全部升级补丁的数据库。
因此,一条可复现的源码结论至少需要四层证据。

图 1:运行产物、源码快照、构建能力和数据库状态共同限定一条源码结论的适用范围。
第一层:先认运行中的目标程序
排查哪个组件,就直接询问哪个程序,不要用 UI 页脚、安装包文件名或运维文档代替运行证据。
zabbix_server -Vzabbix_proxy -Vzabbix_agentd -Vzabbix_agent2 -V
C 组件最终调用 src/libs/zbxcommon/misc.c 中的 zbx_print_version(),输出格式包含发布版本、revision、revision date 和实际编译时间:
<组件名> (Zabbix) 7.0.28Revision 86f12eab509 6 July 2026, compilation time: <日期> <时间>
这里有两个容易混淆的时间:6 July 2026 来自源码中的 ZABBIX_REVDATE;后面的 compilation time 来自实际构建。两者不是一回事。
Go 组件通过 src/go/pkg/version/version.go 的 Display() 输出相同核心信息,并额外显示 Go runtime。正式构建还会由 src/go/Makefile.am 注入构建日期、操作系统和架构;一次没有这些参数的裸 go build,不能自动等同于正式发布构建。
前端也有自己的观测点。apiinfo.version 返回的是 PHP 代码中的 ZABBIX_API_VERSION,它可以证明前端/API 是 7.0.28,却不能证明正在运行的 Server 二进制具有相同 revision。
第二层:再认手里的源码快照
当前源码的 C 版本主锚点是 include/version.h:
#define ZABBIX_REVDATE ”6 July 2026”#define ZABBIX_VERSION_MAJOR 7#define ZABBIX_VERSION_MINOR 0#define ZABBIX_VERSION_PATCH 28#define ZABBIX_VERSION_REVISION 86f12eab509#define ZABBIX_VERSION_RC ””
configure.ac 中的 AC_INIT([Zabbix],[7.0.28]) 则定义了 Autoconf 的包版本。Go、Java 和 PHP 还分别在 src/go/pkg/version/version.go、src/zabbix_java/src/com/zabbix/gateway/GeneralInformation.java、ui/include/defines.inc.php 中保留自己的版本常量。
为什么手册始终把 86f12eab509 称为 revision,而不直接写成“当前工作区完整 Git commit”?答案在仓库根目录的 Makefile.am。制作发布包时,dist-hook 会取得短 Git 标识,并把它写入 C、Go 和 Java 的版本文件:
zabbix_revision=`git rev-parse --short HEAD`; \cat $(top_distdir)/include/version.h | \sed ”s/{ZABBIX_RC_NUM}/$$branch$$type_num$$type_count/g” | \sed ”s/{ZABBIX_REVISION}/$$zabbix_revision/g” \> $(top_distdir)/include/version.h.new;
这说明它是发布打包时嵌入的短 revision。解压后的源码目录可能没有 .git,也可能后来被人工修改;仅凭这个字符串,既不能恢复完整 SHA,也不能证明当前文件没有本地改动。
所以,准确的记录方式应当是:
源码包内嵌 revision 为 86f12eab509;本地补丁与来源另行记录。
第三层:代码存在,不等于功能已编入
回到开头的 SNMP 故障。src/zabbix_server/server.c 根据条件编译宏决定启动日志显示什么:
#ifdef HAVE_NETSNMP# define SNMP_FEATURE_STATUS ”YES”#else# define SNMP_FEATURE_STATUS ” NO”#endifzabbix_log(LOG_LEVEL_INFORMATION,”SNMP monitoring: ” SNMP_FEATURE_STATUS);
HAVE_NETSNMP 是否成立,取决于配置阶段的选项、依赖检测和生成的构建配置,而不是取决于源码目录里有没有 SNMP 文件。同样的判断还适用于 IPMI、Web monitoring、VMware、ODBC、SSH、IPv6 和 TLS。
因此,验证构建能力至少要保存三类证据:
如果启动日志已经写着 SNMP monitoring: NO,继续追网络报文没有意义。排查应返回 configure.ac、m4/、Net-SNMP 依赖和条件编译结果。
第四层:数据库有独立的 schema 坐标
软件版本是 7.0.28,不代表数据库版本就写成 7.0.28。Zabbix 使用 dbversion 表记录 schema 补丁位置:
SELECT mandatory, optional FROM dbversion;这份 7.0.28 源码中的 MySQL、PostgreSQL、Oracle 和 SQLite 全新建库脚本都写入:
mandatory = 7000000optional = 7000030
7000030 是 schema patch 编号,不是“Zabbix 7.0.30”。在 src/libs/zbxdbupgrade/dbupgrade_7000.c 中,7000000 被注册为 mandatory patch,7000001 到 7000030 是 optional patches。
src/libs/zbxdbupgrade/dbupgrade.c 会读取 mandatory 和 optional,计算程序要求的 mandatory 版本,并执行尚未完成的补丁。PHP 前端的 ui/include/defines.inc.php 则定义 ZABBIX_DB_VERSION = 7000000,前端检查的是 mandatory 边界。
这也是为什么研究升级问题时,不能只打开当前的 database//schema.sql。它描述全新安装的目标结构,不是完整升级历史;真实升级链还要继续查看 dbupgrade_.c 的补丁注册与执行逻辑。
对不上源码时,按这个顺序缩小范围

图 2:先排除版本、revision、构建能力和数据库状态差异,再进入当前源码的函数调用链。
这套顺序的价值,在于每一步都能排除一大类无效搜索:
运行版本不对,先找对应版本的源码; revision 不同,先确认本地补丁或打包来源; Enabled features 不同,先回到构建条件; 涉及表、字段或升级,再核对 dbversion 和数据库补丁; 以上边界都明确后,函数和调用链分析才有稳定起点。
建议保留一张“版本证据卡”
每次准备给出源码结论前,可以先填写下面这张记录:
目标组件:运行版本:Revision:源码来源:本地或厂商补丁:构建平台与编译时间:Enabled features:数据库 mandatory / optional:关键日志:
并非每个问题都会用到所有字段。例如纯 Agent key 问题通常与数据库无关。但凡某个坐标可能改变结论,就必须记录,或者明确写出“尚未核实”的边界。
回放开头的 SNMP 故障
现在重新看最初的现场,排查路径会变得很短:
这不是多做了一轮检查,而是用几分钟避免在错误的源码和错误的层次里排查数小时。
今天只需要记住四句话
第一,7.0 只是系列标识,不能唯一确定源码和运行行为。
第二,revision 是重要的发布快照锚点,但不能代替完整 Git commit、本地修改记录或实际二进制证据。
第三,代码存在不等于功能已编入;可选能力必须核对构建条件和启动日志。
第四,数据库 schema 有自己的 mandatory/optional 补丁坐标,不能从产品版本号直接推算。
留给读者的小练习
选择你环境中的一个 Zabbix Server、Proxy 或 Agent,填写一张版本证据卡。然后比较 -V、启动日志、对应源码版本文件,以及问题涉及数据库时的 dbversion。
如果四层坐标中有一层无法确认,就先不要急着下“源码就是这样”的结论——那一层往往正是故障边界。
下一篇预告
版本坐标确定后,下一步不是从入口文件第一行开始顺序阅读,而是先认识仓库的地形。
下一篇《十个顶层模块,拼出 Zabbix 源码全貌》,我们将按组件和语言拆开 src/、ui/、database/ 等顶层目录,建立从需求到源码目录的第一跳。
夜雨聆风