网络物理拓扑图如果靠手工维护,一旦机房跳线调整、设备上下电,图上的连线就会和实际脱节。用Ruby写一套自动发现与更新算法,可以定时从交换机、路由器拉取邻居与转发信息,算出当前真实拓扑,再和上一次结果合并,做到分钟级同步。

一、物理拓扑更新的核心数据结构
在Ruby里,最直观的表达方式是用邻接表。每一个网络设备抽象成一个节点,拥有唯一标识(如管理IP或桥MAC),节点之间通过物理链路构成无向边。我们可以用哈希来保存图结构,键是节点ID,值是与该节点直连的邻居集合。
为了支持差量更新,还需要维护两个独立快照:旧快照old_graph和上一次成功计算的图。每次探测完成后生成新快照new_graph,随后比对两者。下面给出基础结构定义,使用普通Ruby对象即可,不需要引入重型企业级图库。
# 拓扑图使用邻接表表达
class TopologyGraph
def initialize
# { node_id => Set.new([neighbor_id, ...]) }
@adj = Hash.new { |h, k| h[k] = Set.new }
end
def add_link(a, b)
@adj[a] << b
@adj[b] << a
end
def remove_link(a, b)
@adj[a].delete(b)
@adj[b].delete(a)
end
def neighbors(node)
@adj[node].to_a
end
def edges
seen = Set.new
result = []
@adj.each do |a, nbs|
nbs.each do |b|
key = [a, b].sort
next if seen.include?(key)
seen << key
result << [a, b]
end
end
result
end
end
二、基于SNMP的邻居信息采集
物理拓扑发现通常依赖LLDP或CDP协议。Ruby可通过snmp gem读取设备的邻居表。以LLDP为例,远程系统名、端口ID、本地端口ID都能从标准MIB中取出。采集模块应设计为可插拔:不同厂商若不支持LLDP,可回退到MAC转发表加子网网关推断。
下面的代码展示了一个简化采集器,它连接设备并取出本地端口与对端设备标识的对应关系。实际工程中要加上超时与重试,避免单台设备离线拖垮整个任务。采集结果直接调用TopologyGraph#add_link写入新图。
require 'snmp'
def collect_via_lldp(ip, community)
graph = TopologyGraph.new
SNMP::Manager.open(host: ip, community: community, version: :v2c) do |mgr|
mgr.walk(['1.0.8802.1.1.2.1.4.1.1.7', '1.0.8802.1.1.2.1.4.1.1.5']) do |local_port, remote_sys|
local = local_port.value.to_s
remote = remote_sys.value.to_s
next if remote.empty?
graph.add_link("#{ip}:#{local}", remote)
end
end
graph
end
三、差量合并算法实现
拿到新图后,更新算法的重点在于差量计算。我们遍历新图的每条边,若旧图中不存在,则说明是新增物理链路;反过来,旧图有而新图无的边,可能是线缆拔除或设备休眠。直接删除风险较高,因此引入“疑似断开”状态,连续多次探测消失才真正移除。
以下merge_graphs方法演示如何用Ruby集合运算完成合并。它返回需要入库的变更事件,供上层调用方写数据库或触发告警。这种纯函数风格也方便写单元测试。
def merge_graphs(old_g, new_g)
old_edges = old_g.edges.to_set
new_edges = new_g.edges.to_set
added = new_edges - old_edges
removed = old_edges - new_edges
events = []
added.each { |e| events << { type: :link_up, edge: e } }
removed.each { |e| events << { type: :link_down, edge: e } }
events
end
四、环路与僵尸节点处理
物理网络中常因冗余布线出现临时环路,或老旧设备下线后仍在旧图留下孤立节点。Ruby侧可用并查集快速识别连通分量,对单点连通分量且连续三次未采集到任何边的节点标记为僵尸,从展示层隐藏。环路本身不必打破,只需在画图时按生成树缩边,避免界面线条交叉混乱。
下面的片段用递归DFS检查图中是否出现预期之外的环,若出现则记录日志供运维复核,而不是直接抛异常中断更新任务。算法复杂度在节点数百级别时完全够用。
def has_unexpected_cycle?(graph, allowed_edges)
visited = Set.new
graph.edges.each do |a, b|
next if allowed_edges.include?([a, b].sort)
# 简易DFS查环
stack = [[a, nil]]
local_seen = Set.new
while !stack.empty?
node, parent = stack.pop
next if local_seen.include?(node)
local_seen << node
graph.neighbors(node).each do |nb|
next if nb == parent
if local_seen.include?(nb)
return true
else
stack << [nb, node]
end
end
end
end
false
end
五、调度与持久化建议
Ruby的sidekiq或简单loop with sleep都能驱动定时发现。建议将每次快照以JSON或MessagePack存入Redis,保留最近五份以便回滚。当差量事件写入关系库后,前端轮询接口即可拿到最新物理拓扑,无需整图重绘。
整体来看,用Ruby实现这套更新算法代码量小、可读性高,特别适合中小机房自研轻量网管。只要保证采集协议覆盖主要设备类型,拓扑图准确率就能稳定提升,减少人工核查成本。
network_topologyRubytopology_update修改时间:2026-08-10 08:12:50