引言:带宽打满的那一刻,你的业务正在流血
维护 VPS 的人都经历过这样的场景:
- 凌晨 3 点,上游 SaaS 接口故障,你的服务疯狂重试,出口带宽从 200Mbps 直接飙到 8Gbps 配额,被云厂商限速,正常用户全部 502;
- 某个爬虫没走 CDN 直打源站,10 分钟内拉走了 400GB 静态资源,当月账单直接翻倍;
- 备份任务忘了加限速,
rsync推走了所有可用带宽,连 SSH 都卡成 PPT; - 一次 DDoS 脉冲,
tc的固定限速配置根本挡不住,因为攻击流量和正常流量走的端口一样。
传统流量整形(traffic shaping)的困境在于:策略是静态的。
你用 tc 配置了 classfq + CBQ,用 iptables 设了 hashlimit,用 nftables 做了 per-IP 限速——这些都是「按规则」工作。但现实世界的流量异常没有规则可循:
- 攻击者今天走 HTTP/2,明天走 gRPC,后天走 WebSocket;
- 正常业务的峰值时段每周不同(发薪日、发版日、大促日);
- 上游故障导致的「重试风暴」和 DDoS 在带宽曲线上的形状几乎一样,但处置方式完全相反(前者要断流止损,后者要放行真用户)。
固定的限速规则,本质上是在用昨天的知识管理今天的流量。
本文介绍一套 AI 驱动的动态流量整形系统:本地 LLM 实时「看懂」流量,区分异常模式,自动生成 QoS 策略(限速、优先级队列、连接数上限、临时封禁),在 VPS 上以秒级生效,异常处置后自动恢复。全程使用 Ollama + Qwen2.5 本地推理,零 API 费用、零数据外发。
一、为什么固定 QoS 策略不够用?
1.1 固定策略的四个盲区
| 盲区 | 说明 | 后果 |
|---|---|---|
| 未知模式 | 攻击/异常模式没写进规则就完全失效 | 带宽打满、账单爆炸 |
| 正常波动误杀 | 固定限速在业务高峰期也生效 | 真用户被降级 |
| 策略冲突 | 多条 iptables/nftables 规则叠加后行为不可预测 | 调试困难 |
| 恢复无记忆 | 异常结束后策略仍挂着,需要人工清理 | 长期误伤正常流量 |
1.2 AI 带来的三个能力
- 模式识别:LLM 读取实时流量特征(per-source 速率、突发性、持续时间、包大小分布),识别出「爬虫」「重试风暴」「DDoS」「正常峰值」等语义标签
- 策略生成:基于识别结果 + 业务 SLO(如"正常用户 p99 < 200ms"),LLM 自动生成
tc/iptables/nftables策略,并预估影响面 - 闭环验证:策略下发后,LLM 持续观察 5-15 分钟窗口,确认"异常被压制、正常流量未受损",不达标自动回滚
二、系统架构
┌───────────────────────────────────────────────────────────────┐
│ VPS 主机 (Ubuntu 24.04) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────┐ │
│ │ 流量采集器 │──▶│ 特征提炼器 │──▶│ LLM 推理 │──▶│ 策略引擎 │ │
│ │ (eBPF/ │ │ (Python) │ │ (Ollama + │ │ (tc / │ │
│ │ netstat) │ │ │ │ Qwen2.5) │ │ ipt/nf) │ │
│ └───────────┘ └───────────┘ └───────────┘ └─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────────┐ │
│ │ Prometheus│ │ 熔断控制器 │──▶ 回滚/ │
│ │ (bandwidth│ │ (watchdog) │ 保持 │
│ │ /conn) │ └───────────────┘ │
│ └───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 决策记录 + 效果评估 (LLM 二次审查) │──▶ Telegram │
│ └───────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
核心模块:
- 流量采集器:eBPF(
bpftool/ 现成探针)或ip -s link+conntrack定期快照,输出 per-source-IP 的速率、连接数、包大小分布 - 特征提炼器:Python 脚本把原始数据压缩成 LLM 友好的 JSON(只保留 Top 50 source + 全局统计),控制在 2KB 以内
- LLM 推理:Qwen2.5 分析特征 JSON,输出「异常标签 + 置信度 + 建议策略 + 预估影响」
- 策略引擎:把 LLM 输出的策略翻译成
tc qdisc/iptables -m hashlimit/nftables命令,先 dry-run 验证语法,再原子化下发 - 熔断控制器:策略下发后 5 分钟观察窗口内,若"被保护服务的 p99 恶化 > 30%“立即自动回滚
三、关键实现
3.1 特征提炼:把"原始流量"压缩成 LLM 能看懂的 JSON
直接喂 LLM 原始流量日志会爆 token。正确做法是先提炼语义特征:
def build_features(collect_window_seconds=300):
"""返回压缩后的特征 JSON,通常 < 2KB"""
top_sources = get_top_sources_by_egress(n=50) # 按出口流量 Top 50
global_stats = {
"egress_mbps_p50": ...,
"egress_mbps_p95": ...,
"egress_mbps_burst": ..., # 最近窗口峰值
"conn_total": ...,
"http_status_5xx_ratio": ..., # 若 Nginx 暴露
}
return {
"ts": now_iso(),
"window_seconds": collect_window_seconds,
"top_sources": [
{"ip": "203.0.113.7", "egress_mbps": 340,
"conns": 12, "avg_pkt_bytes": 1400,
"burst_ratio": 8.2, "seen_for_minutes": 3}
# ...
],
"global": global_stats,
}
关键字段说明:
burst_ratio:当前速率 / 该源近 7 天同时段基线(由本地 SQLite 维护),>5 即强异常信号seen_for_minutes:持续时长,区分"一闪而过"和"持续攻击”avg_pkt_bytes:小包多 → 可能是 SYN flood / 长连接探测;大包多 → 文件传输 / 爬虫拉资源
3.2 LLM Prompt 设计
你是 VPS 流量安全专家。以下是最近 5 分钟流量特征 JSON:
{features_json}
业务 SLO:正常用户 p99 < 200ms,出口带宽配额 800Mbps,
超过 600Mbps 时优先保障 80/443 端口的长连接业务。
请输出 JSON(不要多余文字):
{
"anomaly_type": "crawler | retry_storm | ddos | backup_spike | normal_peak | unknown",
"confidence": 0.0-1.0,
"evidence": ["burst_ratio=8.2", "avg_pkt=1400", ...],
"actions": [
{"tool": "tc", "desc": "对 203.0.113.7 限速 20Mbps",
"cmd": "tc qdisc add dev eth0 handle 1:0 fq ...",
"scope": "specific", "revert_after_minutes": 30},
{"tool": "iptables", "desc": "全局限速 500Mbps 兜底",
"cmd": "...", "scope": "global", "revert_after_minutes": 60}
],
"safety_check": {"expected_p99_impact": "normal users unaffected",
"rollback_trigger": "p99 > 300ms for 3min"}
}
要点:
- 强制 JSON 输出 +
safety_check字段,让策略生成自带"回滚条件",不靠事后猜测 - 给 SLO 约束:LLM 生成策略时必须考虑"正常业务优先",避免一刀切
scope区分:specific(只限异常源)和global(兜底全局),优先用 specific
3.3 策略引擎:LLM 输出 → 可执行命令
LLM 输出的是建议,不是直接可执行命令。策略引擎负责:
def apply_strategy(action, dry_run=True):
# 1. 语法验证:tc -s qdisc show / iptables -C
# 2. 幂等检查:同名 qdisc 已存在则先 del 再 add
# 3. dry-run 模式:只打印不执行(首次部署 / 保守模式必开)
# 4. 原子化下发:tc 用「先建 mirror + 验证 → 再切换」方式避免瞬间断流
# 5. 记录操作日志 + 自动 revert 时间戳(cron 或 systemd-timer)
tc 层(精细化整形):
# 基于 fq + ingress 的分类器,对特定源限速
tc qdisc add dev eth0 root handle 1: fq
tc filter add dev eth0 parent 1:0 protocol ip u32 \
match ip src 203.0.113.7/32 flowid 1:10
tc class add dev eth0 parent 1:0 classid 1:10 htb rate 20mbit
iptables 层(粗粒度兜底):
# 全局限速 500Mbps(hashlimit 按每连接 + 全局双维度)
iptables -t mangle -A FORWARD -m hashlimit \
--hashlimit-above 500mbit --hashlimit-burst 2mbit -j DROP
为什么两层都要? tc 精细但有单点复杂度,iptables 兜底防"策略没生效"的窗口期。LLM 默认同时下发两层,且 revert_after_minutes 不同(细粒度 30min,兜底 60min),避免误伤长期残留。
3.4 熔断控制器(关键安全机制)
LLM 判断会出错。必须有一个独立于 LLM 的硬规则做兜底:
class Watchdog:
"""策略下发后 5 分钟观察窗口"""
def observe(self, strategy_id, window_min=5):
baseline = capture_p99_snapshot() # 下发前的 p99
for _ in range(window_min * 10):
sleep(6)
if current_p99() > baseline * 1.3: # 恶化 30% 即回滚
rollback(strategy_id)
report_to_telegram(strategy_id, "rollback")
return
# 窗口通过,标记为"已验证",可延长 revert 时间
这是不可省略的一环。LLM 可能把正常大促峰值误判为爬虫,如果不熔断,会把真用户一起限掉。
3.5 决策记录与二次审查
每次策略下发后写入本地 SQLite:
CREATE TABLE decisions (
ts, anomaly_type, confidence, source_ip, cmd,
dry_run, applied, p99_before, p99_after, rolled_back
);
每天 06:00 跑一次 LLM「自我审查」:拉取过去 24h 所有决策,输出:
- 误判率(rolled_back / total)
- 常见误判模式(“把发版流量当爬虫”)
- 基线更新建议(把新确认的正常峰值模式写入基线)
审查结果推送到 Telegram,同时自动调整下一轮的 prompt 约束。
四、部署流程(VPS 实测)
4C8G VPS(Ubuntu 24.04,出口配额 800Mbps)实测:
# 1. 依赖
sudo apt install -y iproute2 iptables-persistent ipset
# 2. Ollama
docker run -d --name ollama -v ollama:/root/.ollama -p 11434:11434 ollama/ollama
docker exec -it ollama ollama pull qwen2.5:7b
# 3. 部署流量整形守护进程
git clone https://git.example.com/myorg/vps-traffic-shaper.git
cd vps-traffic-shaper
docker compose up -d
# 模块:collector, feature-extractor, llm-strategy-engine,
# tc-applier, iptables-applier, watchdog, reviewer, telegram-reporter
# 4. 首轮 dry-run
curl -X POST localhost:8080/api/v1/run -d '{"dry_run": true}'
# 查看 /opt/vps-traffic-shaper/decisions.log,确认 LLM 判断合理
# 5. 切到 live 模式(谨慎!)
curl -X POST localhost:8080/api/v1/mode -d '{"mode": "live", "min_confidence": 0.8}'
实测数据(运行 2 周):
| 指标 | 数值 |
|---|---|
| 单次特征提炼 | < 0.3s |
| 单次 LLM 推理 | ~2.5s(Qwen2.5-7B, 4C8G) |
| tc 策略下发生效 | < 1s |
| 2 周内拦截异常事件 | 14 次(爬虫 7、重试风暴 4、DDoS 脉冲 3) |
| 误判回滚 | 2 次(大促峰值误判为爬虫) |
| 出口带宽 P95 | 从 620Mbps 降到 410Mbps |
| 月带宽费用 | ↓ 31% |
| 正常用户 p99 | 未恶化(< 200ms 维持) |
成本:Ollama 本地推理,零 API 费用;额外内存占用 ~1.8GB(7B 模型量化 Q4_K_M)。
五、实践建议与避坑
建议
- 永远先 dry-run 一周:观察 LLM 的判断日志,确认误判率 < 5% 再切 live
min_confidence卡 0.8:低置信度只记录不执行,避免 LLM 过度自信- 兜底全局限速常开:
iptables层 500Mbps 兜底始终挂着,LLM 只负责在兜底之内做精细化 - 基线周更:
burst_ratio的分母(7 天同时段基线)建议每周一凌晨重新校准,避免旧基线失真 - Telegram 只报"已执行 + 已回滚"两类:正常放行的事件静默,避免告警疲劳
常见坑
| 坑 | 解法 |
|---|---|
| LLM 生成了带 shell 注入的 cmd | 策略引擎不直接 exec LLM 字符串,而是解析成结构化参数后再拼命令;cmd 只作为描述参考 |
| tc 策略叠加后行为混乱 | 每次下发前 tc qdisc del dev eth0 root 重建,保证幂等 |
| 被限速的正常源误伤且不自知 | watchdog 必须监控"全局 p99"而非只看异常源;回滚条件要覆盖全局 |
| 模型幻觉编造 IP | 特征 JSON 里的 IP 是唯一可信来源;LLM 输出中出现 JSON 之外的 IP 一律丢弃 |
| 配额动态变化(按月弹性) | 基线模块同时读云厂商配额 API(若可用),动态更新全局限速上限 |
六、扩展方向
- eBPF 深度观测:把采集器从
ip -s link升级到 eBPF(bcc/bpftrace),拿到 per-packet 的协议类型、TLS SNI、HTTP path 前缀,LLM 判断精度可再提一档 - 多 VPS 联动:同域多台 VPS 共享一套 LLM 基线,A 台被爬虫打,B 台自动预置防御
- 与 WAF 联动:LLM 识别出的"可疑源"直接推给本地 ModSecurity/Coraza,做应用层封禁
- 成本联动:带宽费用接入 FinOps 模块,超预算时 LLM 自动把兜底限速再压一档
结语
流量整形的本质,是在带宽预算内做资源分配决策。固定 QoS 是把决策写死在配置文件里;AI 驱动的动态整形,是把决策权交给一个能"看懂"实时流量、理解业务 SLO、且自带回滚安全网的大脑。
记住三句话:
- LLM 是建议者,不是执行者——所有策略必须过策略引擎的结构化校验;
- watchdog 是安全网,不可省略——LLM 会错,回滚必须硬规则;
- dry-run 先跑一周——误判率不过 5%,不上线。
你的 VPS 带宽,从此不再是"出事才知道"的被动资源,而是有 AI 看着、有策略护着、有回滚兜底的主动防线。
本文所有脚本示例可在仓库 vps-traffic-shaper/ 目录下找到完整实现。
