Skip to content

第 10 章:带宽树与队列 —— 不只“限速”,而是分层资源治理

Transmission 同时面对全局限速、分组限速、单 torrent 限速、Peer 优先级和上传 choke。tr_bandwidth 用树表达约束继承,Peer Manager 再用周期调度决定连接和服务对象。

一、带宽是一棵树

tr_bandwidth 的头文件直接给出设计:

实际父子关系会根据 group 与 torrent 设置调整,但原则不变:叶子是 Peer I/O,根是 Session。

二、测量与限制共用同一结构

每个节点对上下行分别记录:

  • raw bytes:协议、握手和 piece 数据全部计入;
  • piece bytes:只统计有效载荷;
  • 2 秒历史窗口,250 ms 粒度;
  • desired speed、limited、bytes left;
  • 是否 honor parent limits。

Peer 消耗字节时,notify_bandwidth_consumed() 沿 parent 递归上报,因此:

  • Peer 节点给出单连接速度;
  • Torrent 节点自然得到其所有 Peer 之和;
  • Session 根得到全局速度。

不需要另建一个统计聚合器。

三、allocate()clamp() 分工

周期开始时根节点调用 allocate(period_ms)

  1. 深度遍历树,为 limited 节点计算本周期 bytes_left
  2. 收集所有 Peer I/O,并继承路径上的有效 priority;
  3. 按 high/normal/low 分组;
  4. 给可运行 Peer 激活读写;
  5. Peer 真正 I/O 前调用 clamp(dir, wanted_bytes)

clamp() 会在当前节点限制 byte count,并在 honor_parent_limits 为真时递归询问父节点。最终允许值是整条祖先路径上最严格的剩余额度。

text
Peer 想写 64 KiB
  → Peer 节点允许 64 KiB
  → Torrent 限速只剩 20 KiB
  → Group 还剩 50 KiB
  → Session 还剩 12 KiB
最终只能写 12 KiB

四、“不限速”为什么有两种含义

通常子节点不限速仍要遵守父限制。但 torrent 的 TR_SPEEDLIMIT_UNLIMITED 语义可能是“忽略 Session 全局限速”。因此节点有 honor_parent_limits 开关,而不是把 desired speed 设成一个巨大数。

这是配置语义与数值模型分离的好例子:无限并非某个魔法带宽值,而是约束继承关系改变。

五、原始流量与 Piece 流量为何分开

用户关心的“下载速度”通常是 piece payload;网络和限速必须考虑握手、消息头、PEX、metadata 等 raw bytes。若只按 piece bytes 限速,协议开销可绕过上限;若 UI 只显示 raw,又会让用户看到超过文件增长速度的数字。

同一节点记录两套 history,分别服务控制与展示。

六、Torrent 队列控制的是“运行名额”

Session 对下载与做种分别维护:是否启用队列、最大同时任务数、stall 判定分钟数。Torrent 的 queue direction 由完成度决定:未完成占下载队列,完成后占上传/做种队列。

start(false) 发现无空槽时只设置 is_queued_。Session 的 1 秒 queue timer 检查:

  • 当前 active 数;
  • 队列上限;
  • 运行任务是否 stalled;
  • queue position;
  • torrent 是否有错误/验证中。

有空槽时按位置启动等待任务。start_now 则绕过队列,但不绕过文件存在和验证等安全条件。

七、队列、带宽、Peer 上限是三层预算

预算控制对象解决的问题
Torrent queue同时运行的任务数避免大量任务争抢资源
Peer limit全局/单 torrent 连接数控制 fd、NAT 与握手成本
Bandwidth tree每周期可传字节数控制速率与优先级

一个 torrent “已运行”也可能因没有 Peer 或被 choke 而零速;有很多 Peer 也可能被全局带宽 cap 限住。排障不能只看单一开关。

八、上传 rechoke:选择服务谁

Peer Manager 定时执行 rechoke。大意是:

  • 对下载中的 torrent,偏好向我们提供数据、表现好的 Peer;
  • 对做种 torrent,考虑上传速率、interest 和公平性;
  • 保留 optimistic unchoke,周期给未知 Peer 机会;
  • Fast Extension 的 allowed-fast 与普通 choke 状态协同;
  • 每个 swarm 有自己的 Peer 状态,但调度受全局上限影响。

Choke 不是断开连接,而是暂时不接受对方普通 REQUEST。它控制上传服务面,带宽树控制字节速率,两者不能互相替代。

九、连接维护的多个 Pulse

tr_peerMgr 用多个节拍处理不同成本的工作:

  • bandwidth pulse:分配额度、pump Peer;
  • rechoke pulse:重算上传 choke;
  • reconnect pulse:关闭低价值连接、发起新连接;
  • peer info pulse:老化候选与状态。

分开周期比每个网络事件都全局重算便宜,也避免单个复杂“大 tick”造成延迟尖峰。紧急变化可把 rechoke timer 临时调到很短间隔,而不是同步重算。

十、Alt speed 是 Session 级策略切换

“乌龟模式”保存一套上下行替代速度,可手动或按每周日程启用。tr_session_alt_speeds 负责时间窗口和 change reason;状态变化时更新 Session 顶层带宽限制,并通过回调/RPC 让 UI 同步。

它没有给每个 Peer 打补丁,说明把全局策略放在带宽树根的价值:一次切换自然传播到所有后代。

十一、分组限速为何是独立节点

Session 的 getBandwidthGroup(name) 动态创建 group bandwidth。Torrent 可把自己的 bandwidth parent 指向 group,从而实现:

  • 工作 torrent 总计 5 MB/s;
  • 影音 torrent 总计 20 MB/s;
  • 所有 group 再共同服从 Session 50 MB/s。

如果只有“全局 + 单任务”两个数,无法表达共享子预算;树结构正是这个配置需求的自然模型。

十二、设计启示

  1. 限速是层级约束问题,不是 sleep 问题。在 I/O 前 clamp,比传完后统计超速更可控。
  2. 统计与控制复用一棵树。字节沿父链上报,天然得到各层聚合值。
  3. 调度周期要按成本拆分。带宽 100ms 级、队列 1s 级、保存数分钟级,各自容忍度不同。
  4. 策略输出状态,机制执行额度。queue/rechoke 决定谁活跃,bandwidth 决定能传多少。

源码锚点

文档采用 CC BY-SA 4.0;源码片段保留 Transmission 上游许可。