📚 第八章 应用协议
📄 一句话概括

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. 算一下:1.3.6.1.2.1.10 和 1.3.6.1.2.1.2 谁在前?
  2. 想一下:为什么 walk 的报文数是「条目数加一」?
  3. 想一下:GetBulk 改进了什么?

常见误区

  1. 以为「OID 按字符串排序」。 它逐段比数字,10 排在 2 后面。
  2. 以为「MIB 是协议」。 它只是一份「有哪些指标」的清单,协议是 SMI 和 SNMP。
  3. 以为「SNMP 很轻量」。 取几条很轻,全量 walk 会跑出几十万字节。
自测三问(点开对答案) 1. `1.3.6.1.4.1` 是什么? —— **厂商自留地**,下面按厂商编号分叉。 2. OID 的排序规则是什么? —— **逐段比数字的字典序**,不是字符串序。 3. GetBulk 解决什么问题? —— **把「一问一答」变成「一问多答」**,减少往返次数。

下一节看 RMON:把判断下放到设备上。

相关笔记