节点、边与方向
作为图上行走的请求
请求触及的每个组件都是一个节点:客户端、DNS 解析器、CDN 边缘节点、反向代理、后端副本、数据库、缓存。
两个节点之间的每个连接都是一条有向边:请求向前流动,响应向后流动。正向边代表一个打开的 TCP 连接及其上层协议。
单个请求是穿过此图的一条路径。系统为响应该请求所做的总工作量等于每个节点上工作量的总和,加上每条边的延迟。
为什么要关注? 一旦你画出图,那些在代码中不可见的属性就会显现出来:
- 跳数(Hop count):路径中边的数量。每一跳都会增加延迟(网络往返 + 节点处理)。跳数越少 = 延迟下限越低。
- 入度(In-degree):指向某个节点的边的数量。高入度意味着该节点从许多来源接收请求,必须扩展规模或进行自我保护。
- 出度(Out-degree):从某个节点指出的边的数量。高出度意味着该节点依赖于许多下游服务,并且有许多故障点。
- 割点(Cut vertex):移除该节点会导致图断开的单个节点。没有对等节点的反向代理就是一个割点;移除它将切断对其源站的所有访问。
流量汇聚之处
扇入 = 汇聚
节点的入度 = 指向该节点的边数。在请求图中,入度 = 发送请求的上游源数量。
扇入模式:许多客户端 -> 一个 CDN;许多 CDN 边缘节点 -> 少数源站代理;许多代理 -> 更少的后端副本;许多后端 -> 单一数据库。
集中性之所以重要,是因为入度最高的节点承受着最大的聚合负载。 链条末端的数据库可能会看到来自整个系统中每个活跃请求的查询,即使单个用户产生的流量并不大。
扇出 = 依赖
节点的出度 = 从该节点指向外部的边数。高出度意味着有许多下游依赖。
一个调用数据库、两个缓存、三个外部 API 和一个队列的后端,其出度为 7。如果所有下游组件对于成功响应都是必需的,那么它的成功概率大致等于每个下游成功概率的乘积。
0.999 ^ 7 ≈ 0.993:一个拥有 7 个下游依赖的后端,即使每个下游的可靠性均为 99.9%,且自身没有 bug,其自身的可靠性也只能达到约 99.3%。
通过以下方式降低出度:缓存下游结果,使非关键下游变为可选(优雅降级),并行化可以并行的操作。
不对称性
扇入(Fan-in)集中负载;扇出(Fan-out)放大风险。一个形状良好的图会在影响最大的节点上同时最小化这两者。
数据库(最高扇入):积极使用缓存以减少负载。使用读副本将扇入分散到多个节点。
编排服务(最高扇出):针对每个依赖项设置熔断器,实现优雅降级,并采用舱壁隔离模式。
插入节点带来灵活性
间接性 = 添加中间节点
没有代理时,图结构为:client -> backend。客户端必须知道后端的地址。移动后端需要更新客户端(通过 DNS 或配置)。这是一种紧密耦合。
有了代理,图结构变为:client -> proxy -> backend。客户端只知道代理。移动后端只需更新代理的上游配置,而无需更新客户端。
图操作:在现有边上插入一个节点。新边 client -> proxy 是稳定的;新边 proxy -> backend 现在由团队负责管理。
几何解读:间接性增加了一层,将上游变更与下游变更解耦。每一层的边都可以独立重新连接。
间接层的成本
每一层都会增加:
- 一跳延迟(客户端到代理之间的边)
- 路径上多一个割点(代理本身)
- 多一个可能发生配置错误的地方
对于任何非平凡的系统,其收益(重新布线、扩展、屏蔽、终止 TLS、分发负载)通常大于成本。但存在一个极限:每个间接层都会增加另一跳 & 另一个单点故障(SPOF)候选。
民间经验法则:任何问题都可以通过增加一层间接层来解决(除了“间接层过多”这个问题本身)。
将架构视为图来阅读
综合
你现在可以将系统架构视为一张图:计算跳数(hops),识别割点(cut vertices),衡量扇入(fan-in)集中度,根据扇出(fan-out)计算可用性上限,并评估间接层(indirection)的权衡。
应用以上全部四项。
一个新服务具有如下架构:clients -> CDN -> reverse proxy (2 replicas) -> backend tier (8 replicas) -> { DB primary, cache cluster (3 nodes), external API }。
配套笔记
配套笔记
本“几何视角”课程将代理与源站主课程重新表述为有向图分析。
本课程中的下一个配套课程 geometry_of_stateless_horizontal_scaling(无状态水平扩展的几何视角),将提取自主扩展课程中的副本数学模型,并从几何角度推导排队曲线、利特尔法则(Little's Law)以及 80% 利用率拐点。
做得很好。