数据中心网络架构十几年来几乎没变过——胖树(fat-tree)是默认选项。AWS 最近公开了一项已经落地的大改动:他们用基于准随机图理论的扁平网络架构替代了胖树,新架构叫 Resilient Network Graphs,已经成为大多数新建数据中心的默认方案。路由器数量直接砍掉 69%,吞吐量提升 33%,网络功耗降低 40%。这不是论文里的设想,是已经在跑的生产架构。
胖树的瓶颈在哪
胖树的核心思路是层级扩展:ToR 交换机连到汇聚层,汇聚层连到核心层,流量逐级向上再向下。好处是路径可预测,坏处也很明显——
- 层级越多,路由器越多:每一层都是成倍增长,一个典型三级胖树要几千台路由器。
- 核心层是单点瓶颈:所有跨机架流量都要过核心,核心一旦拥塞,整栋数据中心吞吐塌方。
- 超分比难以压缩:胖树的带宽逐级收窄,要降低超分比就得堆更多核心路由器,成本和功耗一起涨。
- 故障域大:核心层路由器宕机影响面是整栋 DC,收敛慢、影响大。
AWS 的规模让这些问题更尖锐:几十万台服务器、跨机架流量占比越来越高,胖树的层级开销已经不可接受。
ShuffleBox + 准随机图:扁平到极致
Resilient Network Graphs 的关键设计只有两步:
第一步:去掉中间层,ToR 直连 ToR。 机架顶部的交换机不再往上汇聚,而是直接和其他机架的 ToR 建立连接,形成一张扁平的 mesh。
第二步:连接模式不用手工规划,用准随机图理论生成。 不是每个 ToR 都连所有其他 ToR(那线缆数量会爆炸),而是按准随机图的度数约束,让每个 ToR 只连有限数量的邻居,但整体拓扑具备随机图的短路径和高容错特性。
物理实现靠 ShuffleBox——一种被动光学器件,把多路光纤信号做物理级混洗(shuffle),不需要电信号处理,不需要路由决策,功耗几乎为零。ShuffleBox 放在机架行之间,ToR 的光纤拉到 ShuffleBox,出来就连到了随机分配的远端 ToR。
整张网络变成一张低度数、高连通的随机图:
- 任意两个 ToR 之间通常 2-3 跳就能到达,不需要核心层。
- 单条链路或单个 ToR 宕机,流量沿随机图的冗余路径绕行,故障收敛快。
- 路由器总数大幅减少——中间层全没了。
数字背后的逻辑
| 指标 | 变化 | 原因 |
|---|---|---|
| 路由器数量 | -69% | 汇聚层和核心层被 ShuffleBox + 直连替代 |
| 吞吐量 | +33% | ToR 间直连消除了核心层超分瓶颈 |
| 网络功耗 | -40% | ShuffleBox 是被动光学器件,路由器数量也少了 |
69% 的路由器削减是最大的冲击。胖树里路由器数量大致是 O(k³)(k 是端口数),而准随机图里路由器数量只跟 ToR 数量线性相关,度数是常数。量级差异直接体现在这个百分比上。
吞吐量提升 33% 的关键不是链路变快了,而是超分比降低了。胖树核心层的超分比通常是 3:1 甚至更高,ToR 直连后跨机架带宽不再被核心层收窄,有效吞吐自然上去。
功耗下降 40% 是两个因素叠加:路由器少了(每台路由器几百瓦),ShuffleBox 几乎不耗电(被动光学混洗,没有芯片)。
用 Python 模拟:胖树 vs 准随机图的路由器数量对比
下面的脚本生成两种拓扑的路由器数量对比,你可以直接跑起来看差距。度数参数可以调整,观察随机图在不同连通度下的路由器需求。
#!/usr/bin/env python3
"""
fat_tree_vs_random_graph.py
对比胖树和准随机图拓扑的路由器数量与平均跳数。
直接运行: python3 fat_tree_vs_random_graph.py
"""
import random
import math
from collections import deque
def fat_tree_routers(k: int) -> dict:
"""
k-port 胖树: k 个核心路由器, k²/2 个汇聚路由器, k²/2 个边缘路由器。
总路由器 = k + k²/2 + k²/2 = k + k²
总机架数 = k²/2 (每个边缘路由器连一个机架)
"""
core = k
agg = k * k // 2
edge = k * k // 2
total_routers = core + agg + edge
racks = edge
return {
"topology": "fat-tree",
"k": k,
"total_routers": total_routers,
"racks": racks,
"core": core,
"aggregation": agg,
"edge": edge,
}
def random_graph_routers(num_tors: int, degree: int) -> dict:
"""
准随机图: 只有 ToR 路由器, 每个 ToR 有 degree 条直连链路到其他 ToR。
总路由器 = num_tors (没有汇聚层和核心层)
"""
return {
"topology": "resilient-random-graph",
"total_routers": num_tors,
"racks": num_tors,
"degree": degree,
"core": 0,
"aggregation": 0,
"edge": num_tors,
}
def build_random_graph(num_nodes: int, degree: int) -> dict:
"""
构建一张准随机图 (近似 regular random graph),
返回邻接表和平均最短路径跳数。
"""
if degree >= num_nodes:
degree = num_nodes - 1
if degree < 2 or num_nodes < 3:
return {"adj": {}, "avg_hops": float("inf")}
adj = {i: set() for i in range(num_nodes)}
# 简单方法: 每个节点随机选 degree 个不同邻居
for i in range(num_nodes):
candidates = [j for j in range(num_nodes) if j != i and j not in adj[i]]
need = degree - len(adj[i])
if need > 0 and candidates:
chosen = random.sample(candidates, min(need, len(candidates)))
for j in chosen:
adj[i].add(j)
adj[j].add(i)
# 计算平均最短路径 (BFS, 取样加速)
sample_size = min(200, num_nodes)
sampled = random.sample(range(num_nodes), sample_size)
total_hops = 0
pairs = 0
for src in sampled:
dist = {src: 0}
q = deque([src])
while q:
u = q.popleft()
for v in adj[u]:
if v not in dist:
dist[v] = dist[u] + 1
q.append(v)
for dst in dist:
if dst != src:
total_hops += dist[dst]
pairs += 1
avg_hops = total_hops / pairs if pairs > 0 else float("inf")
return {"adj": adj, "avg_hops": round(avg_hops, 2)}
def compare(k_values: list[int], degrees: list[int]):
"""打印对比表"""
print("=" * 72)
print(f"{'k':>4} | {'胖树路由器':>10} | {'机架':>6} | "
f"{'随机图路由器':>12} | {'度数':>4} | {'路由器削减':>10} | {'平均跳数':>8}")
print("-" * 72)
for k in k_values:
ft = fat_tree_routers(k)
for d in degrees:
rg = random_graph_routers(ft["racks"], d)
reduction = (1 - rg["total_routers"] / ft["total_routers"]) * 100
graph = build_random_graph(ft["racks"], d)
print(f"{k:>4} | {ft['total_routers']:>10} | {ft['racks']:>6} | "
f"{rg['total_routers']:>12} | {d:>4} | {reduction:>9.1f}% | "
f"{graph['avg_hops']:>8}")
print("=" * 72)
if __name__ == "__main__":
random.seed(42)
# k=48 是典型大型胖树; 度数 4-8 是准随机图常见范围
compare(k_values=[8, 16, 24, 48], degrees=[4, 6, 8])
运行结果示例:
========================================================================
k | 胖树路由器 | 机架 | 随机图路由器 | 度数 | 路由器削减 | 平均跳数
------------------------------------------------------------------------
8 | 72 | 32 | 32 | 4 | 55.6% | 2.85
8 | 72 | 32 | 32 | 6 | 55.6% | 2.43
8 | 72 | 32 | 32 | 8 | 55.6% | 2.18
16 | 272 | 128 | 128 | 4 | 52.9% | 3.42
16 | 272 | 128 | 128 | 6 | 52.9% | 2.93
16 | 272 | 128 | 128 | 8 | 52.9% | 2.61
24 | 600 | 288 | 288 | 4 | 52.0% | 3.71
24 | 600 | 288 | 288 | 6 | 52.0% | 3.15
24 | 600 | 288 | 288 | 8 | 52.0% | 2.80
48 | 2352 | 1152 | 1152 | 4 | 51.3% | 4.12
48 | 2352 | 1152 | 1152 | 6 | 51.3% | 3.48
48 | 2352 | 1152 | 1152 | 8 | 51.3% | 3.09
========================================================================
注意:这个简化模型只算了路由器数量,没有计入 ShuffleBox(被动器件,不算路由器)。AWS 宣称 69% 的削减,说明他们的实际胖树层级更深、k 值更大,或者 ShuffleBox 替代了更多中间层设备。度数越高,平均跳数越低,但线缆复杂度上升——AWS 选择的度数是在跳数和布线成本之间的平衡点。
路由怎么做:随机图上的流量工程
扁平随机图最大的运维挑战是路由。胖树的层级路由是确定性的,随机图上路径多且随机,传统 ECMP 不够用。AWS 没在摘要中披露具体路由协议细节,但基于随机图理论的网络通常采用以下策略:
- 多路径负载均衡:随机图天然提供大量等价短路径,比胖树的 ECMP 路径数更多,流量分散更均匀。
- 集中式路径计算:SDN 控制器掌握全局拓扑,按随机图结构计算最优路径分配,避免局部拥塞。
- 故障快速收敛:随机图的冗余度让单链路故障的影响面很小,控制器重算路径的代价低。
如果你想在自己的环境里实验随机图路由,可以用一个最小化的网络模拟——下面是用 NetworkX 做路径分析的示例:
#!/usr/bin/env python3
"""
random_graph_routing.py
在准随机图上模拟多路径路由和故障收敛。
依赖: pip install networkx
运行: python3 random_graph_routing.py
"""
import networkx as nx
import random
def build_quasi_random_graph(num_tors: int, degree: int) -> nx.Graph:
"""构建准随机图拓扑"""
G = nx.random_regular_graph(d=degree, n=num_tors, seed=42)
return G
def analyze_routing(G: nx.Graph, src: int, dst: int):
"""分析两个 ToR 之间的短路径数量和跳数分布"""
all_paths = list(nx.all_shortest_paths(G, src, dst))
hop_count = len(all_paths[0]) - 1 if all_paths else float("inf")
print(f" {src} -> {dst}: {len(all_paths)} 条最短路径, "
f"跳数={hop_count}")
return all_paths
def simulate_link_failure(G: nx.Graph, src: int, dst: int, fail_link: tuple):
"""模拟单链路故障后的路由收敛"""
G_failed = G.copy()
u, v = fail_link
if G_failed.has_edge(u, v):
G_failed.remove_edge(u, v)
try:
new_path = nx.shortest_path(G_failed, src, dst)
new_hop = len(new_path) - 1
print(f" 链路 {fail_link} 故障后: 新路径跳数={new_hop}, "
f"路径={new_path}")
except nx.NetworkXNoPath:
print(f" 链路 {fail_link} 故障后: {src} -> {dst} 不可达!")
if __name__ == "__main__":
NUM_TORS = 64
DEGREE = 6
G = build_quasi_random_graph(NUM_TORS, DEGREE)
print(f"拓扑: {NUM_TORS} 个 ToR, 度数={DEGREE}")
print(f"总链路数: {G.number_of_edges()}")
print(f"全网平均最短路径跳数: "
f"{nx.average_shortest_path_length(G):.2f}")
# 正常路由分析
print("\n--- 正常路由 (取样 5 对 ToR) ---")
pairs = random.sample(range(NUM_TORS), 10)
for i in range(0, len(pairs), 2):
analyze_routing(G, pairs[i], pairs[i+1])
# 故障模拟
print("\n--- 单链路故障模拟 ---")
src, dst = 0, 31
analyze_routing(G, src, dst)
# 随机断一条链路
fail_edge = random.choice(list(G.edges()))
simulate_link_failure(G, src, dst, fail_edge)
运行后你会看到:64 个 ToR、度数 6 的随机图,平均跳数大约 2.5,任意两点之间有多条最短路径,断一条链路后跳数只增加 1 或保持不变。这就是 AWS 说"resilient"的含义——随机图的容错不是靠冗余核心层,而是靠拓扑本身的路径多样性。
落地考量与边界条件
Resilient Network Graphs 不是万能替换,有几个现实约束:
布线复杂度。 ToR 直连意味着光纤要从每个机架拉到 ShuffleBox,再随机分配到远端机架。机房物理布局必须适配这种布线模式——行间距、光纤走线槽、ShuffleBox 安装位置都需要重新规划。对现有机房的改造成本可能比新建更高,这也是 AWS 只在"大多数新建数据中心"采用的原因。
路由协议成熟度。 随机图上的路由不像胖树那样有成熟的 BGP/OSPF 实践,需要 SDN 控制器或定制路由协议。AWS 有自研的网络控制平面,中小规模数据中心如果照搬架构,路由软件是最大的空白地带。
度数选择是权衡。 度数太低(比如 2-3),跳数上升、容错下降;度数太高(比如 12+),每个 ToR 的端口需求暴增,线缆和 ShuffleBox 成本上升。AWS 选的度数没有公开,但从 69% 的路由器削减反推,度数可能在 4-8 之间。
渐进式采用。 如果你想在自己的环境里尝试类似思路,不需要一步到位:
- 先在集群内部做小规模 mesh:选一个 20-30 机架的集群,用 ToR 直连 + 准随机图布线,和胖树集群并行跑,对比延迟和吞吐。
- ShuffleBox 可以用便宜替代:小规模场景不需要 AWS 级别的定制光学器件,用多路光纤配线盘 + 手工跳线也能实现类似的随机连接,运维成本高但能验证路由逻辑。
- SDN 控制器先行:在物理拓扑改造之前,先用虚拟网络模拟随机图路由,验证控制平面逻辑。
AWS 的 Resilient Network Graphs 证明了一件事:数据中心网络不一定要层级堆叠,随机图的数学特性——短路径、高容错、低节点数——在超大规模下比胖树更经济。但这项架构迁移的门槛不在数学,在物理布线和路由软件。有自研网络栈的大型云厂商可以跟进,中小团队更现实的路径是先吃透随机图的路由逻辑,再逐步改造物理层。