小型网络系列:PCC 分流逻辑和故障行为

系列导航

  1. 小型网络系列:开篇 - 300-500 Devices 的网络为什么也会复杂
  2. 小型网络系列:RouterOS 和 Switch 的职责边界
  3. 小型网络系列:LAG、VLAN 和 Macvlan 为什么会一起出现
  4. 小型网络系列:PCC 分流逻辑和故障行为
  5. 小型网络系列:WireGuard 为什么是日常路径
  6. 小型网络系列:脚本、Scheduler 和可恢复性

背景

前一篇讲的是多条 WAN 怎么通过 LAG、VLAN、Macvlan 送到 RouterOS。

这一篇讲 RouterOS 拿到这些路径之后,怎么决定办公流量走哪条 WAN。

Office-JT 用的是 PCC。这里的 PCC 不是“把总带宽揉成一条大管子”,也不是按用户部门做复杂策略。它解决的是连接级分流:新连接按 both-addresses-and-ports 算 Bucket,被打上不同 Connection Mark,再进入不同 Routing Mark。

这件事在小型网络里很实用。

因为线路质量和用途并不总是固定的。有时候 route1 可以参与,有时候只想用 route2 / route3;有时候三条线等比例,有时候 route1 权重要低一些。手工一条条改 Mangle 规则很容易出错,所以最后变成了几组可切换脚本。

先分清两层规则

PCC 这部分最重要的边界,是 Bucket Layer 和 Routing Layer 分开。

Office-JT 当前命名大概是这样:

规则层 命名 职责
Bucket Layer pcc-mark-<denominator>/<bucket>-route<route> 新连接按 PCC Bucket 打 Connection Mark
Routing Layer pcc-routing-route<route> 根据 Connection Mark 打 Routing Mark

切换 PCC 模式时,脚本只应该动 Bucket Layer。

也就是说:

  • 先禁用所有 ^pcc-mark- Bucket Rules
  • 再启用当前模式对应的 ^pcc-mark-N/
  • 不碰 pcc-routing-*

这个边界看起来像命名洁癖,实际是在避免事故。

如果切换脚本把 Routing Layer 也误伤了,后续所有已经按 route1 / route2 / route3 设计的路径都会变得不可预测。PCC 模式切换应该只改变“新连接如何分桶”,不应该重写整套路由语义。

当前几种模式

当前脚本里有四种模式:

模式 Denominator Bucket 分配 使用场景
0:1:1 2 0 -> route31 -> route2 route1 暂时不参与,只用 route2 / route3
1:1:1 3 0 -> route31 -> route22 -> route1 三条线路等比例参与
1:2:2 5 2/3 -> route30/1 -> route24 -> route1 route1 权重低,route2 / route3 权重高
2:3:3 8 0/1/2 -> route33/4/5 -> route26/7 -> route1 三条都用,但 route2 / route3 更重

这里的数字不是带宽精确承诺,而是新连接 Bucket 的比例。

例如 1:2:2 不是说总带宽一定会变成这个比例,而是说新连接会按 5 个 Bucket 分配:route1 占 1 个 Bucket,route2 / route3 各占 2 个 Bucket。

实际效果还会受到连接大小、客户端数量、业务类型、单连接时长影响。单连接下载、并发下载、并发上传、短连接 API、SMB 访问、浏览器请求,它们对 PCC 的体感都不一样。

为什么用 both-addresses-and-ports

脚本里实际使用的是 RouterOS 的:

per-connection-classifier=both-addresses-and-ports:N/Bucket

也就是说,PCC Bucket 不是只看源地址,也不是只看目标地址,而是把两端地址和端口都纳入 Hash。

这个选择的重点是 Connection 粒度稳定。

一条连接被打上 pcc1 / pcc2 / pcc3 之后,后续上下行都应该沿着同一个 Connection Mark / Routing Mark 走。对普通用户来说,这比“每个包都可能换一条线”重要得多。尤其是银行、支付、后台系统这类对公网出口变化比较敏感的业务,如果访问过程中频繁跳公网 IP,很容易触发验证码、风控或重新登录。

所以这里追求的不是“越分散越好”,而是“新连接可以按比例分配,已经建立的连接不要乱跳”。

但它也不是只为了稳定。

对单个用户来说,如果下载器、浏览器、同步工具或上传任务本身会产生多条并发连接,这些连接就有机会被 PCC 分到不同 WAN Path。这样单个用户在并发下载、上传大文件时,确实可以更接近把多条线路榨干的效果。

这里的平衡点在于:单条连接保持路径稳定,多条连接允许分散利用带宽。

这也是为什么没有简单选择“只按源地址分流”。只按源地址会让某个客户端更稳定地落在一条 WAN 上,但单个用户的并发传输也更难吃到多 WAN 的总带宽。both-addresses-and-ports 的取舍,是在连接稳定性和带宽利用率之间找一个更实用的中间点。

不过这也不是应用层会话粘滞。

浏览器访问一个网站时,背后可能有多条 TCP / QUIC 连接。PCC 只能保证连接层面的标记和路由稳定,不能理解“这是同一个银行页面的完整业务会话”。如果某类业务确实对公网出口特别敏感,仍然应该通过例外规则、DNS / 目标地址归类,或者临时 PCC 模式选择来减少跳出口的概率,而不是指望 PCC 自动识别业务语义。

切换脚本怎么工作

PCC 模式切换脚本的核心流程很简单:

sequenceDiagram
  participant Operator as Operator
  participant Script as RouterOS PCC Script
  participant Bucket as pcc-mark-* Bucket Rules
  participant Routing as pcc-routing-* Rules
  participant Traffic as New Connections

  Operator->>Script: Run PCC Mode Script
  Script->>Bucket: Ensure both-addresses-and-ports Bucket Rules Exist
  Script->>Bucket: Disable All ^pcc-mark-
  Script->>Bucket: Enable Target ^pcc-mark-N/
  Note over Script,Routing: Do Not Touch pcc-routing-*
  Traffic->>Bucket: New Connections Hash by Addresses + Ports
  Bucket->>Routing: Connection Mark route1 / route2 / route3
  Routing-->>Traffic: Routing Mark Decides WAN Path
sequenceDiagram
  participant Operator as Operator
  participant Script as RouterOS PCC Script
  participant Bucket as pcc-mark-* Bucket Rules
  participant Routing as pcc-routing-* Rules
  participant Traffic as New Connections

  Operator->>Script: Run PCC Mode Script
  Script->>Bucket: Ensure both-addresses-and-ports Bucket Rules Exist
  Script->>Bucket: Disable All ^pcc-mark-
  Script->>Bucket: Enable Target ^pcc-mark-N/
  Note over Script,Routing: Do Not Touch pcc-routing-*
  Traffic->>Bucket: New Connections Hash by Addresses + Ports
  Bucket->>Routing: Connection Mark route1 / route2 / route3
  Routing-->>Traffic: Routing Mark Decides WAN Path
sequenceDiagram
  participant Operator as Operator
  participant Script as RouterOS PCC Script
  participant Bucket as pcc-mark-* Bucket Rules
  participant Routing as pcc-routing-* Rules
  participant Traffic as New Connections

  Operator->>Script: Run PCC Mode Script
  Script->>Bucket: Ensure both-addresses-and-ports Bucket Rules Exist
  Script->>Bucket: Disable All ^pcc-mark-
  Script->>Bucket: Enable Target ^pcc-mark-N/
  Note over Script,Routing: Do Not Touch pcc-routing-*
  Traffic->>Bucket: New Connections Hash by Addresses + Ports
  Bucket->>Routing: Connection Mark route1 / route2 / route3
  Routing-->>Traffic: Routing Mark Decides WAN Path

它有几个关键细节:

  • 目标 Bucket Rule 不存在时,脚本会先补出来
  • 补规则需要找到一个 Anchor,避免规则插错位置
  • 所有 pcc-mark-* 会先统一禁用
  • 只有当前 Denominator 的 pcc-mark-N/ 会启用
  • pcc-routing-* 保持不动

这让切换动作变得可重复。

不是“我记得上次手动启用了这几条”,而是“当前模式脚本每次都把 Bucket Layer 收敛到同一个状态”。

PCC 解决不了什么

PCC 很容易被误解成万能负载均衡。

它不是。

它解决的是新连接分桶,不解决这些问题:

  • 单条连接突破单条 WAN 的带宽上限
  • 某条线路只是变慢但没有断时的自动质量判断
  • 对某个业务协议做应用层感知
  • 把一个网站的多连接业务会话绝对固定在同一个公网出口
  • 用户级公平调度
  • 部门级策略隔离
  • 所有回程路径自动正确

比如某条 WAN 没断,只是质量变差。RouterOS 不一定知道这条线应该立刻降权。这个时候把 PCC 模式从 1:1:1 切到 0:1:11:2:2,其实是一种人为策略判断。

但反过来也要承认:如果一个用户正在做多连接下载、并发上传、云盘同步、开发镜像拉取这类任务,PCC 是有预期内优化效果的。它不能把一条 TCP 连接变成三条 WAN 的总和,但它可以让多条连接分散到 route1 / route2 / route3,从而提高总带宽利用率。

再比如 SMB 这类跨站访问,不应该被错误地当成普通公网出口流量。站点互联应该优先走 WireGuard LAN 路径,而不是被 PCC 当成外网流量分掉。

所以 PCC 的前提是:哪些目标属于 LAN / WireGuard,哪些属于 WAN,边界要先清楚。

故障行为

PCC 的故障行为要分几类看。

第一类是物理或 VLAN 承载故障。

如果某个 WAN Source 在 Switch 侧接错、PVID 错了、LAG 有问题,PCC 脚本本身救不了。它只能在 RouterOS 看到的接口语义上做分流,不能修 Switch 侧的物理承载。

第二类是线路不可用。

如果 route1 临时不适合参与,可以切到 0:1:1。这样新连接只在 route2 / route3 之间分。老连接是否立刻变化,要看既有 Connection Mark 和连接状态,不应该期待所有流量瞬间重新分配。

第三类是线路质量不均。

如果 route1 能用但不想承担太多,可以切到 1:2:22:3:3。这不是自动 QoS,而是把新连接权重向 route2 / route3 倾斜。

第四类是规则命名或迁移问题。

这也是为什么当前脚本统一使用 pcc-mark-*pcc-routing-*。旧命名混在一起时,脚本容易误匹配。把 Bucket Layer 和 Routing Layer 命名拆开,是为了让自动化切换更可控。

怎么观察

PCC 不应该只靠“用户感觉快不快”判断。

我更倾向于看几类证据:

  • 当前启用的是哪组 pcc-mark-N/
  • 活跃 Bucket Rule 是否仍然使用 both-addresses-and-ports
  • pcc-routing-* 是否仍然存在且未被误禁用
  • route1 / route2 / route3 的连接数是否符合预期
  • 高连接数客户端是否异常
  • WireGuard / LAN 目标有没有被错分到 WAN PCC

Office-JT 里还保留了一个 print-per-ip-counts 脚本。它会统计 DHCP Lease 里绑定的客户端,在连接表里找连接数超过阈值的地址。

这个脚本很土,但很有用。

当某台机器连接数异常高时,PCC 的体感可能会被它影响。先把高连接客户端找出来,比盲目改权重更靠谱。

最后的结论

PCC 在这套小型网络里的位置,是 Multi-WAN 新连接分流。

它依赖前一篇的 LAG / VLAN / Macvlan 把 route1 / route2 / route3 清楚送到 RouterOS,也依赖 WireGuard / LAN 目标先从普通 WAN 流量里排除出来。

真正值得保留的边界是:

  • pcc-mark-* 是 Bucket Layer
  • pcc-routing-* 是 Routing Layer
  • both-addresses-and-ports 提供 Connection 粒度的上下行稳定
  • 单用户多连接下载/上传可以更好利用多 WAN 总带宽
  • 切换脚本只动 Bucket Layer
  • 模式切换影响新连接权重,不等于应用层智能调度
  • 线路质量判断仍然需要人和监控一起参与

后面写 WireGuard 为什么是日常路径。因为办公室站点互联不是 PCC 的出口选择问题,而是另一条明确的内部网络路径。