节前发现 VPS 上运行节点,流量忽然增加,紧急增加了限速。整理下如何用 Linux tc 做限速、如何做月流量守护、如何通过 Telegram 实时接收告警。

一、问题背景

跑节点常见两个冲突目标:

  1. 节点要尽量稳定在线(避免频繁超时、掉线)。
  2. VPS 月流量有限(例如 1TB),不能无限跑。

之前运行正常而且节点流量不是很高,忽然流量突增,右节点 2 天跑了 700G,于是赶紧使用 claude code(后被封了 :()、gemini 生成脚本。

主要目标是能够限制指定端口的流量,但是也不能影响 ssh 等服务以免影响正常服务,涉及的命令也梳理了一下。


二、tc 是什么及主要参数

tc(traffic control)是 Linux 内核流量控制工具,主要参数:

  • qdisc: 调度排队
  • class: 流量分类
  • filter: 过滤器分类
  • action: 动作,drop/mirred
  • handle: 对象的标识
  • chain: 过滤器链,用于 tc filter,大配量优化用
  • monitor: 监控

三、tc 核心概念

1. qdisc(队列规则)

qdisc 是调度入口

工具名称核心定位核心特性 & 避坑指南典型实战场景
fq_codel公平+低延迟
(Scheduling)
现代标准。结合了多流公平(FQ)与主动弃包(CoDel)。
💡 抗 Bufferbloat 神器
通用默认防卡顿
(HTB 的最佳叶子节点)
Cake全能流控
(Shaping+AQM)
fq_codel 的进化版,自带整形功能。
⚠️ 对内核版本要求较高 (OpenWrt 常用)。
软路由/家庭宽带
(一步到位的低延迟)
TBF简单限速
(Shaping)
✅ 令牌桶算法,配置简单。
⚠️ 一刀切,不分流,参数不当会导致吞吐抖动。
网卡暴力限速
(如限制 eth0 总速 10M)
SFQ老牌公平
(Scheduling)
⚠️ 仅保证连接间的公平,不负责压低延迟。
💡 防止单连接霸占带宽。
简单的多用户共享
pfifo_fast旧默认
(Scheduling)
🛑 只有简单的 3 个优先级。
⚠️ 不抗 Bufferbloat,P2P 一多 SSH 必卡。
老旧系统默认
(建议替换)
Netem网络仿真
(Utility)
✅ 模拟延时、丢包、乱序、重复。
💡 开发测试专用。
弱网环境测试
(测试程序健壮性)
Ingress入站控制
(Utility)
⚠️ 入方向通常只能丢包(Policing)。
💡 若要整形需配合 IFB 设备。
限制下载速度
(配合 IFB)

tbf(Token Bucket Filter)由于其不区分流量类型,当 P2P 数据量增大拥堵时,ssh 请求包需要进行排队导致出现 head-of-line blocking,致使 ssh 延迟极高甚至无法登录。

后来选用了 htb(Hierarchical Token Bucket),它可以通过 class 对流量分类,对于节点流量和 ssh 流量进行区分,进行分类后,再通过 fq_codel,根据限流的需求进行精细化配置。

2. class(分类)

参数全称技术定义 (Definition)最佳实践策略 (Best Practice)
rateGuaranteed Rate
(保障速率)
该 Class 承诺的最低可用带宽。即使网络拥堵,HTB 也会确保该类流量能获得此速率。SSH/交互类:设为 512Kbps - 2Mbps(确保连接存活)。
业务类:设为剩余带宽的预估值。
ceilCeiling Rate
(上限速率)
该 Class 允许借用带宽后的最大速率。仅当父类有空闲带宽时,速率可从 rate 提升至 ceil硬限速:设为 ceil = rate
弹性共享:设为物理带宽上限(允许借用空闲资源)。
burstBurst
(令牌桶容量)
允许在短时间内突破 rate 限制的数据量(字节)。决定了速率的平滑程度。交互类:适当调大(如 20KB-50KB),允许瞬间响应。
大流量:默认即可,过大会导致流量整形失效。
cburstCeiling Burst
(上限桶容量)
允许在短时间内突破 ceil 限制的数据量。主要受网卡物理特性限制。通常保持默认。如果发现无法达到千兆线速,可适当调大。
prioPriority
(优先级)
争抢空闲带宽(Ceil - Rate)时的优先权。数值越小,优先级越高(0-7)。SSH/DNS:设为 01(最高)。
P2P/下载:设为 5 或更低(避免抢占关键流量)。
quantumQuantum
(配额)
在 DRR 调度算法中,每一轮服务允许发送的字节数。一般勿动。HTB 会根据 rate 自动计算。手动调整仅用于解决极低速率下的精度问题。
r2qRate to Quantum
(转换系数)
全局参数(Root)。用于根据 rate 自动计算 quantum 的系数(Quantum = Rate / r2q)。默认 10。只有在系统日志出现 “quantum is too small” 警告时才需修改。
defaultDefault Class
(默认分类)
全局参数(Root)。未匹配任何 Filter 规则的流量将进入此 Class。必须配置。建议指向低优先级的 Class(如 1:10),防止未分类的大流量挤死 SSH。

htb 时将流量划分为不同Class, 用于满足Qos需求 针对 SSH / DNS 这些高交互性协议, 为保证其低延时,需提供高优先级的Qos保障,防止维护期间出现卡顿。 对于业务流量及其他流量放到默认class对其进行流量限制, 防止其占满带宽导致拥堵。

3. filter(过滤规则)

选型/参数全称技术定义实战建议与避坑
u32Universal 32-bit Filter位匹配过滤器强烈推荐。独立于防火墙运行,实现流量控制与安全策略完全解耦。
fwFirewall Mark Filter标记匹配过滤器复杂场景选型。依赖 iptables 打标,需处理防火墙规则持久化问题。
prio / prefPriority / Preference过滤器的检测顺序管理先行。将 SSH/ICMP 设为 1,业务大流量设为 10 以上。
protocolProtocol Identifier指定链路层协议类型务必显式指定 protocol ip,防止过滤器挂载失败。
matchMatch Condition具体的匹配表达式下行限速关键。IFB 模式下通常匹配 sport (源端口)。
flowidFlow Identifier命中规则后的分类目标必须与 classid 精确对应,否则流量会进入默认队列。
mirredMirror/Redirect Action流量镜像/重定向动作下行限流核心。用于将 ingress 流量导向 ifb 设备。
0xffffHex Mask十六进制掩码匹配端口时务必带上,防止匹配范围过大误伤其他业务。

Filter 过滤器选型,配置过程中使用过 fw(mark) 模式,但该方案与 ufw/firewalld 存在持久化问题和冲突处理问题,处理相对复杂,最终使用 u32 通过端口协议类型完成流量区分,不再依赖防火墙标识区分。

使用 htb 模式,带宽分配依赖以下两个参数:

  • rate: 保底额度,这是分配该类别的基础保障带宽。即使系统极其拥堵时也会保证这部分流量分配,设置合理值是保证其不掉线关键。
  • ceil: 封顶额度,由于 htb 存在借用机制(Borrowing),在带宽空闲情况下,该类型可以申请系统剩余资源来提升速率,但是最终速率受到 ceil 的限制。

4. 配置示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
tc qdisc add dev "$IFACE" root handle 1: htb default 10

# 高优先级类(SSH + DNS)
tc class add dev "$IFACE" parent 1: classid 1:1 htb rate 2mbit ceil 100mbit prio 0
tc qdisc add dev "$IFACE" parent 1:1 handle 11: fq_codel

# 默认限速类(其余流量)
tc class add dev "$IFACE" parent 1: classid 1:10 htb rate "$TX_LIMIT" ceil "$TX_CEIL" prio 5
tc qdisc add dev "$IFACE" parent 1:10 handle 110: fq_codel

# IPv4 规则
tc filter add dev "$IFACE" protocol ip parent 1: prio 1 u32 match ip dport "$SSH_PORT" 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 1 u32 match ip sport "$SSH_PORT" 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 2 u32 match ip dport 53 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 2 u32 match ip sport 53 0xffff flowid 1:1

# IPv6 规则(系统不支持时忽略)
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 1 u32 match ip6 dport "$SSH_PORT" 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 1 u32 match ip6 sport "$SSH_PORT" 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 2 u32 match ip6 dport 53 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 2 u32 match ip6 sport 53 0xffff flowid 1:1 2>/dev/null || true

graph TD classDef flow fill:#e1f5fe,stroke:#01579b classDef limit fill:#ffebee,stroke:#c62828 A[🌊 实时流量输入] --> B{是否超过 rate?} B -- 否 --> C[✅ 保证通道: 直接发送]:::flow B -- 是 --> D{是否有闲置带宽?} D -- 无 --> E[⏳ 降速排队/丢包]:::limit D -- 有 --> F{是否达到 ceil?} F -- 否 --> G[🚀 借用模式: 加速发送]:::flow F -- 是 --> E

四、下行限速(IFB)说明

Linux 原生只方便整形 egress(上行)。下行要用 IFB:

  1. 把网卡 ingress 重定向到 ifb0
  2. ifb0 上再做 htb 分类

这个方案可行,但更容易误伤稳定性。如果跑其他业务涉及端口需要进行区分,否则会被业务流量占满带宽后造成丢包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 加载 ifb 模块 
modprobe ifb numifbs=1 2>/dev/null || true
ip link add "$IFB_DEVICE" type ifb 2>/dev/null || true
ip link set "$IFB_DEVICE" up

tc qdisc add dev "$IFACE" handle ffff: ingress
tc filter add dev "$IFACE" parent ffff: protocol ip u32 match u32 0 0 \
  action mirred egress redirect dev "$IFB_DEVICE"

tc qdisc add dev "$IFB_DEVICE" root handle 2: htb default 20

# 高优先级类(SSH + DNS)
tc class add dev "$IFB_DEVICE" parent 2: classid 2:1 htb rate 2mbit ceil 100mbit prio 0
tc qdisc add dev "$IFB_DEVICE" parent 2:1 handle 21: fq_codel

# 默认限速类
tc class add dev "$IFB_DEVICE" parent 2: classid 2:20 htb rate "$RX_LIMIT" ceil "$RX_CEIL" prio 5
tc qdisc add dev "$IFB_DEVICE" parent 2:20 handle 220: fq_codel

# IFB 方向反转,sport/dport 都加
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 1 u32 match ip sport "$SSH_PORT" 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 1 u32 match ip dport "$SSH_PORT" 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 2 u32 match ip sport 53 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 2 u32 match ip dport 53 0xffff flowid 2:1

五、常用 tc 命令速查

1. 查看规则

1
2
3
sudo tc qdisc show dev eth0
sudo tc class show dev eth0
sudo tc filter show dev eth0

2. 查看统计(是否真的在限速)

1
2
sudo tc -s class show dev eth0
sudo tc -s class show dev ifb0

看这几个字段:

  • Sent:发送字节
  • overlimits:超限被整形次数
  • dropped:丢包
  • backlog / ldelay:排队与延迟

3. 清理规则(紧急恢复)

1
2
3
sudo tc qdisc del dev eth0 root 2>/dev/null || true
sudo tc qdisc del dev eth0 ingress 2>/dev/null || true
sudo ip link del ifb0 2>/dev/null || true

八、流量监控

目标:在达到月度阈值时自动停容器,避免超额。

1. 工作原理

  1. 读取网卡累计字节:
    • /sys/class/net/<iface>/statistics/rx_bytes
    • /sys/class/net/<iface>/statistics/tx_bytes
  2. 以“基线值”计算本周期增量
  3. 增量超限后执行:docker stop <container>
  4. 记录日志并发 Telegram 告警

九、Telegram 机器人注册与告警配置

1. 创建机器人

  1. 在 Telegram 搜索 @BotFather
  2. 发送 /newbot
  3. 按提示设置机器人名称和用户名
  4. BotFather 返回 BOT_TOKEN

BOT_TOKEN 形如:

1
123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. 获取 Chat ID

  1. 在 Telegram 搜索 @IDBot
  2. 发送 /start
  3. 返回的 Id 就是你的 TELEGRAM_CHAT_ID

3. 填入脚本

1
2
TELEGRAM_BOT_TOKEN="你的BOT_TOKEN"
TELEGRAM_CHAT_ID="你的CHAT_ID"

4. 发送测试消息

1
2
3
curl -s -X POST "https://api.telegram.org/bot<BOT_TOKEN>/sendMessage" \
  -d chat_id="<CHAT_ID>" \
  -d text="Streamr monitor test"

十、其他

编写脚本后,防止重启后失效,可以配置 systemd 默认开机运行。


最后更新: 2026-02-18