MIB 是「这台设备有哪些指标可查」的清单,写成了一棵树。 walk 的做法是「给我下一个」,靠字典序一格格走,报文数等于条目数加一。
这一节把一棵小 MIB 真的排一遍序,看 OID 的「逐段比数字」怎么工作。 再算一次全量 walk 的代价。
一、OID 是树上的一条路径
每个节点用一串数字标识,就是 OID。
1.3.6.1.2.1.1.5.0 这一串读下来,就是「从根走到这个节点」的路径。
这棵树的形状是约定好的:
1.3.6.1.2.1.1 是 system(设备身份),1.3.6.1.2.1.2 是 interfaces(网卡与流量),
1.3.6.1.2.1.4 是 ip(地址表、路由表)。
1.3.6.1.4.1 是「厂商自留地」:下面按厂商编号分叉,华为是 2011、思科是 9。
谁想加私有指标,就去自己的号下面种一棵子树,永远不会和别人撞。
这套「分号段」的办法,和 AS 号、私有 IP 段是同一个思路。
二、排序是逐段比数字
OID 的排序规则是「逐段比数字」,不是比字符串。
所以 1.3.6.1.2.1.2.1.0 排在 1.3.6.1.2.1.1.5.0 后面——
因为前六段完全相同,比到第七段时 2 大于 1。
如果按字符串比,结果会完全不同:1.3.6.1.2.1.10 会排在 1.3.6.1.2.1.2 前面,
因为字符 1 小于 2。
这就是「数字排序」和「字典排序」的分水岭,也是很多「按 OID 排序」的脚本写错的地方。
这个顺序叫「字典序」,SNMP 的 walk 就是靠它一格格往下走。
walk 的做法很笨但很有效:先 GET 一个 OID,然后说「给我下一个」,一直问到没有下一个为止。
三、全量 walk 的代价
一次 walk 的报文数等于条目数加一(最后那次「没有了」也算一次)。 5000 条要 5001 个来回、90 万字节。 如果每 5 分钟 walk 一次,一天 288 次,两百多兆。 这在 1990 年代的 10 Mbps 网络上是不能忍的,所以当时的做法是「只取关心的几个 OID」, 或者用 SNMPv2 的 GetBulk(一次取一批)。 GetBulk 是 v2 最重要的改进:它在一次请求里说「给我接下来 N 条」, 把「一问一答」变成「一问多答」。 这个思路和 HTTP 的「批量请求」、 数据库的「批量取」是同一个: 减少往返次数,比提高单次速度更有效。
四、动手 Demo:MIB 是一棵树,OID 是树上的一条路径
造一棵七条目的 MIB,按 OID 排序,看「逐段比数字」的效果。 再算一次全量 walk 的报文数和字节数。
mib = {
'1.3.6.1.2.1.1.1.0': ('sysDescr.0', 'MikroTik RouterOS 7.1'),
'1.3.6.1.2.1.1.3.0': ('sysUpTime.0', '123456789'),
'1.3.6.1.2.1.1.5.0': ('sysName.0', 'core-sw-01'),
'1.3.6.1.2.1.2.1.0': ('ifNumber.0', '24'),
'1.3.6.1.2.1.2.2.1.10.1': ('ifInOctets.1', '987654321'),
'1.3.6.1.2.1.2.2.1.16.1': ('ifOutOctets.1', '123456789'),
'1.3.6.1.2.1.4.20.1.1.1': ('ipAdEntAddr.1', '192.168.3.1'),
}
key = lambda o: [int(x) for x in o.split('.')]
for o in sorted(mib, key=key):
print(o, mib[o][0], mib[o][1])
for n in (7, 100, 1000, 5000):
req = 60 * (n + 1)
resp = 120 * n
print(n, n + 1, resp, req + resp)
实跑输出:
--------------------------------------------------------------------
MIB 是一棵树,OID 是树上的一条路径
--------------------------------------------------------------------
**MIB 就是「这台设备有哪些指标可查」的清单**,**写成了一棵树。**
**每个节点用一串数字标识,就是 OID。**
OID 名字 值
-----------------------------------------------------------------------
`1.3.6.1.2.1.1.1.0` `sysDescr.0` MikroTik RouterOS 7.1
`1.3.6.1.2.1.1.3.0` `sysUpTime.0` 123456789
`1.3.6.1.2.1.1.5.0` `sysName.0` core-sw-01
`1.3.6.1.2.1.2.1.0` `ifNumber.0` 24
`1.3.6.1.2.1.2.2.1.10.1` `ifInOctets.1` 987654321
`1.3.6.1.2.1.2.2.1.16.1` `ifOutOctets.1` 123456789
`1.3.6.1.2.1.4.20.1.1.1` `ipAdEntAddr.1` 192.168.3.1
**排序规则是「逐段比数字」**,不是比字符串。
**所以 `1.3.6.1.2.1.2.1.0` 排在 `1.3.6.1.2.1.1.5.0` 后面**——
**因为前六段完全相同,比到第七段时 2 大于 1。**
**这个顺序叫「字典序」,SNMP 的 walk 就是靠它一格格往下走。**
**walk 的做法很笨但很有效**:**先 GET 一个 OID,然后说「给我下一个」,一直问到没有下一个为止。**
**所以一次 walk 的报文数等于条目数加一。**
MIB 条目数 报文来回数 响应字节 合计字节
----------------------------------------------------
7 8 840 1,320
100 101 12,000 18,060
1,000 1,001 120,000 180,060
5,000 5,001 600,000 900,060
**最后一行是关键**:**5000 条要 5001 个来回、90 万字节。**
**如果每 5 分钟 walk 一次,一天就是 288 次,两百多兆。**
**这就是为什么真实运维不用「全量 walk」,而是「只取关心的几个 OID」。**
**这棵树的形状也是约定好的**:
OID 前缀 名字 管什么
----------------------------------------------------------------------
`1.3.6.1.2.1.1` system **设备身份:名字、位置、运行时长**
`1.3.6.1.2.1.2` interfaces **网卡与流量计数**
`1.3.6.1.2.1.4` ip **IP 层:地址表、路由表**
`1.3.6.1.4.1` private **厂商自留地,每家一个号**
**第四行是同一种机制被反复使用的例子**:
**`1.3.6.1.4.1` 下面按厂商编号分叉,华为是 2011、思科是 9。**
**谁想加私有指标,就去自己的号下面种一棵子树**,**永远不会和别人撞。**
**这套「分号段」的办法,和 AS 号、私有 IP 段是同一个思路。**
排序结果里 1.3.6.1.2.1.2.1.0 排在 1.3.6.1.2.1.1.5.0 后面,
尽管它的字符串更长、看着更「小」。
因为比到第七段时 2 大于 1。
5000 条要 5001 个来回、90 万字节。
注意「来回数」是「条目数加一」而不是「条目数」——
因为最后一次请求会返回「没有下一个了」,那也是一次往返。
响应字节那一列是「每条约 120 字节」估的:
里面有 OID(十几字节)、值(十几个字节)、还包着好几层 BER 头。
所以「SNMP 很轻」这句话只在「取几条」的时候成立,全量 walk 一点都不轻。
30 秒实验
- 算一下:
1.3.6.1.2.1.10和1.3.6.1.2.1.2谁在前? - 想一下:为什么 walk 的报文数是「条目数加一」?
- 想一下:GetBulk 改进了什么?
常见误区
- 以为「OID 按字符串排序」。 它逐段比数字,
10排在2后面。 - 以为「MIB 是协议」。 它只是一份「有哪些指标」的清单,协议是 SMI 和 SNMP。
- 以为「SNMP 很轻量」。 取几条很轻,全量 walk 会跑出几十万字节。
自测三问(点开对答案)
1. `1.3.6.1.4.1` 是什么? —— **厂商自留地**,下面按厂商编号分叉。 2. OID 的排序规则是什么? —— **逐段比数字的字典序**,不是字符串序。 3. GetBulk 解决什么问题? —— **把「一问一答」变成「一问多答」**,减少往返次数。下一节看 RMON:把判断下放到设备上。
- ☐ (本节完,下一节 8.6.3 RMON)