MPLS TE快速重路由(FRR)是流量工程中用于快速故障保护的核心机制,其核心思想是在故障发生前就预先计算并建立好备份路径,一旦链路或节点出现故障,流量可以在本地立即切换,不需要等待全局路由收敛。整个过程的关键在于备份路径的计算,也就是在排除被保护资源之后,找到一条仍然可用的路径。本文用Ruby来实现一个简化版的备份路径计算模块,帮助读者从代码层面理解FRR的工作原理。

一、MPLS TE快速重路由的基本原理
在传统的MPLS TE网络中,一条TE隧道对应一条从入节点到出节点的LSP,路径由CSPF(约束最短路径优先)计算得出。当LSP上某条链路故障时,虽然IGP会重新收敛,头节点也会重新发起路径计算,但这个过程的耗时通常在秒级,对于语音、金融交易等对时延敏感的业务来说难以接受。
快速重路由的思路是防患于未然。在建立主LSP的同时,为每个可能故障的链路或节点预先计算一条备份路径,并提前建立备份LSP的转发状态。当故障发生时,检测到故障的本地修复节点(PLR)立即将流量切换到备份路径,切换时间可以控制在50毫秒以内。故障后的流量先走备份路径绕过故障点,再回到主路径的下游节点继续转发,这个过程对头节点来说是透明的。
要实现这个机制,备份路径计算必须满足两个约束:第一,备份路径不能经过被保护的那条链路或节点,否则故障发生后备份路径也跟着失效;第二,备份路径要有足够的带宽资源,否则切换过去之后会造成拥塞。此外,如果网络配置了SRLG(共享风险链路组),备份路径还应避免与主路径共享同一组物理资源,比如同一条光缆。
二、用Ruby建模网络拓扑与约束条件
实现备份路径计算的第一步是把网络拓扑抽象成数据结构。在Ruby中,用哈希表存储邻接关系是非常自然的选择:外层哈希的键是节点名,值是一个数组,数组中每个元素描述一条出方向的链路,包含对端节点、链路IGP度量值和剩余带宽。
下面是拓扑定义的代码示例,模拟一个由六个路由器组成的小型MPLS域:
class Topology
attr_reader :links
def initialize
# @links 结构:{ "R1" => [ {to: "R2", metric: 10, bw: 100}, ... ] }
@links = Hash.new { |h, k| h[k] = [] }
# 记录每条链路所属的SRLG组
@srlg = {}
end
def add_link(from, to, metric:, bw:, srlg: nil)
@links[from] << { to: to, metric: metric, bw: bw }
@links[to] << { to: from, metric: metric, bw: bw } # 链路视为双向
key = link_key(from, to)
@srlg[key] = srlg if srlg
end
def link_key(a, b)
[a, b].sort.join("-")
end
def link_srlg(a, b)
@srlg[link_key(a, b)]
end
end
除了邻接关系,备份路径计算还需要知道被保护的对象。在链路保护场景下,被保护对象是一条链路,计算备份路径时要把这条链路从拓扑中剔除;在节点保护场景下,则要把整个被保护节点及其所有邻接链路剔除。这个“剔除”操作在代码上体现为构造一个过滤后的视图,而不是真的修改原始拓扑。
三、实现带约束的备份路径计算算法
备份路径计算本质上是一个受限最短路径问题。在排除了被保护资源之后,用Dijkstra算法从PLR出发,计算到达汇聚点(Merge Point)的最短路径。汇聚点是主路径上紧邻被保护链路或节点的下游节点,备份路径最终要在这里重新汇入主路径。
实现时需要注意一个细节:链路保护要求备份路径在到达汇聚点之前不能再次进入主路径中间的节点,否则可能出现环路。简化处理时,可以先把主路径上位于汇聚点之前的中间节点一并排除。下面是核心计算代码:
require "set"
class BackupPathCalculator
def initialize(topology)
@topo = topology
end
# protected_link: 被保护的链路,如 ["R1", "R2"]
# main_path: 主LSP经过的节点序列
# min_bw: 备份路径所需的最小带宽
def calculate(protected_link, main_path, min_bw: 0)
plr = protected_link.first
merge_point = protected_link.last
# 排除主路径上汇聚点之前的中间节点,防止备份路径绕回主路径
banned_nodes = Set.new(main_path[0...main_path.index(merge_point)])
banned_nodes.delete(plr) # PLR自身作为起点当然不能排除
dijkstra(plr, merge_point, banned_nodes, protected_link, min_bw)
end
private
def dijkstra(source, target, banned_nodes, protected_link, min_bw)
dist = { source => 0 }
prev = {}
visited = Set.new
dist.default = Float::INFINITY
until visited.include?(target)
# 选取距离最小且未访问的节点
node = dist.reject { |k, _| visited.include?(k) }
.min_by { |_, d| d }
.try(:first)
return nil if node.nil? || dist[node] == Float::INFINITY
visited << node
@topo.links[node].each do |link|
nxt = link[:to]
next if visited.include?(nxt)
next if banned_nodes.include?(nxt)
next if link[:bw] < min_bw # 带宽不足的链路直接剪枝
# 排除被保护的链路本身
next if is_protected?(node, nxt, protected_link)
new_dist = dist[node] + link[:metric]
if new_dist < dist[nxt]
dist[nxt] = new_dist
prev[nxt] = node
end
end
end
trace_path(prev, source, target)
end
def is_protected?(a, b, protected_link)
[a, b].sort == protected_link.sort
end
def trace_path(prev, source, target)
path = [target]
path.unshift(prev[path.first]) while path.first != source
path
end
end
这段代码中,dijkstra方法是核心。它在标准Dijkstra的基础上增加了三重过滤:过滤被保护链路、过滤禁止进入的中间节点、过滤带宽不足的链路。如果最终无法到达汇聚点,方法返回nil,表示这条链路找不到满足约束的备份路径,上层应用需要考虑绕行更远的保护方式或者提示配置问题。
四、验证计算结果与两种保护方式的对比
有了拓扑和计算器,写一段脚本来验证效果。构造一个典型的六节点拓扑,主路径为R1到R6,保护R1与R2之间的链路:
topo = Topology.new
topo.add_link("R1", "R2", metric: 10, bw: 100)
topo.add_link("R2", "R3", metric: 10, bw: 100)
topo.add_link("R3", "R6", metric: 10, bw: 100)
topo.add_link("R1", "R4", metric: 20, bw: 80)
topo.add_link("R4", "R5", metric: 20, bw: 60)
topo.add_link("R5", "R3", metric: 10, bw: 50)
topo.add_link("R4", "R3", metric: 40, bw: 40)
calc = BackupPathCalculator.new(topo)
main_path = ["R1", "R2", "R3", "R6"]
backup = calc.calculate(["R1", "R2"], main_path, min_bw: 50)
puts "备份路径: #{backup && backup.join(' -> ')}"
# 输出: 备份路径: R1 -> R4 -> R5 -> R3
计算结果符合预期:备份路径从R1经R4、R5绕行到R3,正好在汇聚点重新汇入主路径。如果把min_bw提高到60,则R5到R3的链路(带宽50)会被剪枝,算法会改走R4到R3的直达链路,代价是度量值更高但带宽充足,这正是约束条件发挥作用的表现。
最后谈谈两种保护方式的差异。facility bypass(设施旁路)是本文代码模拟的方式,一条备份隧道可以同时保护多条经过同一链路的LSP,扩展性好,资源开销小,但备份路径的粒度较粗,可能不是对每条LSP都最优。one-to-one backup(一对一备份)则为每条LSP单独计算备份路径,路径更贴合业务需求,代价是状态数量随LSP数量线性增长。在真实网络规划中,运营商通常对核心链路采用facility bypass,对少数高价值业务采用one-to-one backup,两者可以结合使用。理解了备份路径的计算逻辑,再去阅读RFC 4090中关于Detour和 bypass tunnel的细节,就会顺畅许多。