小型网络系列:PCC 分流逻辑和故障行为
系列导航
- 小型网络系列:开篇 - 300-500 Devices 的网络为什么也会复杂
- 小型网络系列:RouterOS 和 Switch 的职责边界
- 小型网络系列:LAG、VLAN 和 Macvlan 为什么会一起出现
- 小型网络系列:PCC 分流逻辑和故障行为
- 小型网络系列:WireGuard 为什么是日常路径
- 小型网络系列:脚本、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 -> route3,1 -> route2 |
route1 暂时不参与,只用 route2 / route3 |
1:1:1 |
3 |
0 -> route3,1 -> route2,2 -> route1 |
三条线路等比例参与 |
1:2:2 |
5 |
2/3 -> route3,0/1 -> route2,4 -> route1 |
route1 权重低,route2 / route3 权重高 |
2:3:3 |
8 |
0/1/2 -> route3,3/4/5 -> route2,6/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
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:1 或 1: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:2 或 2: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 Layerpcc-routing-*是 Routing Layerboth-addresses-and-ports提供 Connection 粒度的上下行稳定- 单用户多连接下载/上传可以更好利用多 WAN 总带宽
- 切换脚本只动 Bucket Layer
- 模式切换影响新连接权重,不等于应用层智能调度
- 线路质量判断仍然需要人和监控一起参与
后面写 WireGuard 为什么是日常路径。因为办公室站点互联不是 PCC 的出口选择问题,而是另一条明确的内部网络路径。