自动下行流量管理工具。通过监控网卡上行流量,动态生成下载流量以维持上下行比例或满足累计流量目标。下载内容不写入磁盘,仅消耗带宽。
- 带宽模式(bandwidth) — 下行带宽 = 上行带宽 × 配置比例,自动动态调整
- 流量模式(traffic) — 时间窗口内:入网总流量 = 出网总流量 × 倍数
- 令牌桶速率限制,支持动态调整和突发控制
- 流量模式字节上限控制:按剩余目标分配单次下载配额,避免超额
- 并发下载 worker 池,自动伸缩
- 网卡流量采集(Linux
/proc/net/dev/ 跨平台gopsutil) - 状态持久化,进程崩溃或重启后恢复运行现场
- 优雅退出,信号处理 + 最终统计输出
- 仅允许 http/https 下载 URL,重定向控制,连接超时
- URL 熔断机制:连续失败 5 次自动跳过,60 秒冷却后自动恢复
从 Releases 页面下载对应平台的二进制文件。
支持平台:linux/amd64、linux/arm64、windows/amd64、darwin/amd64、darwin/arm64
git clone https://github.com/weyeahh/Fetch-Down.git
cd Fetch-Down
go build -o fetch-down .要求 Go 1.21+。
# 复制配置模板
cp config.example.yaml config.yaml
# 编辑配置后运行
./fetch-down -config config.yaml| 参数 | 默认值 | 说明 |
|---|---|---|
-config |
config.yaml |
配置文件路径 |
程序启动后持续运行,按 Ctrl+C 发送 SIGINT 信号优雅退出。
将二进制文件和配置放到标准路径:
sudo cp fetch-down /usr/local/bin/fetch-down
sudo mkdir -p /etc/fetch-down
sudo cp config.example.yaml /etc/fetch-down/config.yaml
sudo vim /etc/fetch-down/config.yaml创建 systemd 服务文件:
sudo vim /etc/systemd/system/fetch-down.service写入以下内容:
[Unit]
Description=Fetch-Down Auto Download Traffic Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/fetch-down -config /etc/fetch-down/config.yaml
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# 安全加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/etc/fetch-down
PrivateTmp=true
[Install]
WantedBy=multi-user.target启用并启动:
sudo systemctl daemon-reload
sudo systemctl enable fetch-down
sudo systemctl start fetch-down查看状态和日志:
# 查看服务状态
sudo systemctl status fetch-down
# 查看实时日志
sudo journalctl -u fetch-down -f
# 查看最近 100 行日志
sudo journalctl -u fetch-down -n 100
# 查看特定时间段日志
sudo journalctl -u fetch-down --since "2026-05-02 00:00:00" --until "2026-05-02 12:00:00"停止和重启:
# 优雅停止(发送 SIGTERM,触发状态保存和最终统计输出)
sudo systemctl stop fetch-down
# 重启
sudo systemctl restart fetch-down
# 禁止开机自启
sudo systemctl disable fetch-down注意:
Restart=on-failure确保进程异常退出后自动重启。ProtectSystem=strict将文件系统限制为只读,状态持久化文件需通过配置指定到/var/lib/fetch-down/等可写路径:state_file: "/var/lib/fetch-down/state.json"sudo mkdir -p /var/lib/fetch-down sudo chown root:root /var/lib/fetch-down
完整配置文件 config.example.yaml:
# 运行模式: "bandwidth"(带宽模式)或 "traffic"(流量模式)
mode: "bandwidth"
# --- 带宽模式 ---
# 目标下行/上行比例(如 2.0 表示下行 = 上行 × 2)
ratio: 2.0
# --- 流量模式 ---
# 目标:入网总流量 = 出网总流量 × 倍数
cumulative_multiplier: 1.0
# 时间窗口(支持 Go duration 格式:"24h"、"1h"、"30m"、"10s")
window_duration: "24h"
# --- 下载设置 ---
# 下载 URL 列表(轮询选择),仅允许 http/https
download_urls:
- "http://speedtest.tele2.net/10MB.zip"
- "http://speedtest.tele2.net/100MB.zip"
# 最大并发下载数
max_concurrent: 4
# 总下载速率限制(Mbps),0 = 不限制
bandwidth_limit_mbps: 100
# --- 流量采集 ---
# 上行监控网卡名称,留空自动检测
uplink_interface: "eth0"
# 采样间隔(秒)
uplink_sample_interval: 1
# --- 连接设置 ---
# 连接超时(秒),应用于 TCP 握手和响应头等待
connect_timeout_sec: 10
# 读取超时(秒),应用于单次下载尝试的最大时长
read_timeout_sec: 30
# 最大重试次数(指数退避 + 随机抖动)
max_retries: 3
# --- 日志 ---
# 日志级别: "debug"、"info"、"warn"、"error"
log_level: "info"
# 统计信息输出间隔(秒)
stats_interval_sec: 10
# --- 状态持久化 ---
# 状态文件路径,留空则禁用(带宽模式默认禁用,流量模式自动使用 "fetch-down-state.json")
state_file: ""
# 自动保存间隔(秒),0 = 仅退出时保存
state_save_interval: 60| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
mode |
string | 否 | "bandwidth" |
运行模式 |
ratio |
float | 否 | 2.0 |
带宽模式目标比例,必须 > 0 |
cumulative_multiplier |
float | 否 | 1.0 |
流量模式倍数,必须 > 0 |
window_duration |
string | 否 | "24h" |
流量模式时间窗口 |
download_urls |
[]string | 是 | — | 下载 URL 列表,必须为 http/https |
max_concurrent |
int | 否 | 4 |
最大并发数,必须 >= 1 |
bandwidth_limit_mbps |
float | 否 | 0 |
全局速率限制,0 = 无限制 |
uplink_interface |
string | 否 | 自动检测 | 监控的网卡名称 |
uplink_sample_interval |
int | 否 | 1 |
上行采样间隔(秒) |
connect_timeout_sec |
int | 否 | 10 |
TCP 连接超时(秒) |
read_timeout_sec |
int | 否 | 30 |
单次下载尝试超时(秒) |
max_retries |
int | 否 | 3 |
失败重试次数 |
log_level |
string | 否 | "info" |
日志级别 |
stats_interval_sec |
int | 否 | 10 |
统计输出间隔(秒) |
state_file |
string | 否 | "" |
状态持久化文件路径 |
state_save_interval |
int | 否 | 60 |
自动保存间隔(秒) |
┌─────────────┐
│ main.go │ 加载配置 → 初始化组件 → 信号处理 → 启动 Controller
└──────┬──────┘
│
┌──────▼──────┐
│ Controller │ 中央控制循环,管理所有 goroutine
│ │
│ ┌──────────────────┐
│ │ collectorLoop │ 定时采样网卡上下行流量
│ └──────────────────┘
│ ┌──────────────────┐
│ │ controlLoop │ 每秒更新下载速率统计,关闭 done 信号
│ └──────────────────┘
│ ┌──────────────────┐
│ │ bandwidthMode │ 带宽模式:根据上行带宽动态调整下载速率
│ │ 或 │
│ │ trafficMode │ 流量模式:根据窗口内上行总量计算下载目标
│ └──────────────────┘
│ ┌──────────────────┐
│ │ statsReporter │ 定时输出运行统计
│ └──────────────────┘
│ ┌──────────────────┐
│ │ downloadWorkerPool│ 管理 N 个并发下载 worker
│ └──────────────────┘
└─────────────┘
collectorLoop每uplink_sample_interval秒采样网卡,计算实时上行带宽(B/s)- 控制循环读取上行带宽,计算目标下行 = 上行 × ratio
- 对目标速率应用平滑因子(α=0.3),避免振荡
- 若上行带宽趋近于 0,平滑后速率降至阈值以下时,调用
bucket.Stop()停止所有下载 - 将平滑后的速率设为令牌桶限速,根据速率动态调整 worker 数量
- 下载 worker 通过令牌桶限速读取 HTTP 响应体,数据读取后丢弃,不写入磁盘
- 程序启动时记录窗口起点时间和上行基准值
- 控制循环每个采样周期计算:
windowUplink= 窗口内累计上行字节数targetBytes= windowUplink × cumulative_multiplierwindowDownBytes= 窗口内累计下行字节数(程序级统计)remaining= targetBytes - windowDownBytes
- 根据
remaining / 剩余时间计算所需下载速率 - 每次下载前,按
remaining / 活跃worker数计算单次下载字节上限(MaxBytes),读满即停,避免超额 - 到达窗口末尾时自动重置:清零所有计数器,重新开始新窗口
- 窗口重置后自动保存一次状态文件
- 令牌以配置速率(B/s)填充,突发容量 = 速率 × 0.5 秒
- 下载 worker 每次读取前向令牌桶申请 4KB 令牌
- 令牌不足时阻塞等待,支持 context 取消
Stop()设置停止标志后令牌桶拒绝所有请求,Wait阻塞直到 context 取消或恢复
下载 URL 列表中可能存在不可用地址。为避免在坏 URL 上浪费重试时间,控制器维护 URL 健康状态:
- 每次下载失败记录该 URL 的连续失败次数
- 连续失败达到 5 次后,该 URL 被标记为禁用,轮询时自动跳过
- 禁用 60 秒后自动冷却恢复,重新尝试
- 下载成功则重置失败计数
优先读取 Linux /proc/net/dev(零依赖),失败时回退到 gopsutil 跨平台库。
采集数据:
RxBytes/TxBytes— 累计收发字节数(系统级,自开机起)- 通过两次采样差值计算实时带宽
- 程序级累计值 = 当前系统值 - 程序启动时基准值
异常处理:
- 采样失败时标记
stale,控制循环跳过本轮决策 - 计数器回绕(网卡重置等)时自动重置基准值并记录警告
流量模式下自动启用(可通过 state_file 手动配置)。
保存内容:
- 当前模式、窗口起始时间
- 累计上下行字节数、请求成功/失败数
- 窗口内上行基准值
写入方式:先写 .tmp 临时文件,再 os.Rename 原子替换(Windows 上自动回退为先删后改)。
恢复逻辑:
- 窗口未过期:恢复窗口起点和累计值,继续在原窗口运行
- 窗口已过期:清零所有状态,开始新窗口
- 收到
SIGINT或SIGTERM - 调用
SaveAndStop():保存最终状态 → 调用cancel()取消所有 goroutine - 等待
controlLoop关闭done通道(最长 5 秒超时) - 输出最终统计(总下载量、请求数、运行时长)
- 进程自然退出
[2026-05-02 10:00:00] [INFO] Starting Fetch-Down in bandwidth mode
[2026-05-02 10:00:00] [INFO] Using network interface: eth0
[2026-05-02 10:00:00] [INFO] Bandwidth limit: 100.0 Mbps (12500000 bytes/sec)
[2026-05-02 10:00:10] [INFO] [STATS] mode=bandwidth | uptime=10s | up=5.00 Mbps down=9.80 Mbps | ratio=1.96 (target=2.00) | dl_rate=9.80 Mbps | total_down=12.25 MB total_up=6.25 MB | workers=4/4 | success=8 failed=0
[2026-05-02 10:00:15] [INFO] Download complete: http://speedtest.tele2.net/100MB.zip | 100.00 MB in 8.2s | 97.56 Mbps
Fetch-Down/
├── main.go # 入口:配置加载、组件初始化、信号处理
├── config/
│ └── config.go # YAML 配置解析与校验
├── collector/
│ └── collector.go # 网卡流量采集(/proc/net/dev + gopsutil)
├── controller/
│ └── controller.go # 中央控制:带宽/流量模式、worker 池、状态管理
├── downloader/
│ └── downloader.go # HTTP 下载:速率限制读取、字节上限、重试、连接管理
├── limiter/
│ └── limiter.go # 令牌桶速率限制器
├── logger/
│ └── logger.go # 分级日志(debug/info/warn/error)
├── stats/
│ └── stats.go # 下载统计(字节数、请求数、速率计算)
├── state/
│ └── state.go # 状态持久化(原子写入、崩溃恢复)
├── config.example.yaml # 配置文件模板
├── go.mod
└── .github/
└── workflows/
└── build.yml # GitHub Actions 多平台构建
程序为轻量级网络 I/O 密集型应用,下载内容不写入磁盘,仅消耗带宽。以下为典型场景的性能估值。
| 场景 | CPU(单核) | 内存 | 磁盘 I/O | 网络下行 |
|---|---|---|---|---|
| 空闲(上行 0) | < 0.01% | ~13 MB | 0 | 0(自动停止) |
| 低负载(上行 100 KB/s) | < 0.05% | ~13 MB | ~0.07 KB/s | ~200 KB/s |
| 中负载(上行 5 MB/s) | 0.2-0.8% | ~13 MB | ~0.07 KB/s | ~10 MB/s |
| 高负载(上行 50 MB/s) | 1-3% | ~15 MB | ~0.07 KB/s | ~100 MB/s |
以上行 5 MB/s、ratio=2.0、目标下行 10 MB/s 为例(4KB 读缓冲 ≈ 2,500 次 Read/秒):
| 操作 | 频率 | 单次耗时 | CPU 占用 |
|---|---|---|---|
| 令牌桶 Wait(mutex + refill) | ~2,500 次/秒 | ~200 ns | ~0.05% |
| resp.Body.Read(4KB) | ~2,500 次/秒 | ~1-5 μs | ~0.1-0.5% |
| 原子计数器 AddBytes | ~2,500 次/秒 | ~10 ns | 可忽略 |
| /proc/net/dev 读取 | 1 次/秒 | ~50 μs | 可忽略 |
| 日志输出(info 级别) | 1 次/10 秒 | ~10 μs | 可忽略 |
瓶颈在网络 I/O 等待(系统调用阻塞),而非计算。多核机器上 CPU 占用可进一步摊薄。
| 组件 | 大小 | 说明 |
|---|---|---|
| Go Runtime 基础开销 | ~12 MB | GC、goroutine 调度器、栈管理 |
| 下载缓冲区 | 16 KB | 4 worker × 4KB buf,固定分配,循环复用 |
| HTTP 连接池 | ~400 KB | MaxIdleConns=8,每个空闲连接 ~50KB |
| goroutine 栈 | ~80 KB | 约 10 个 goroutine,每个 2-8KB |
| 业务数据结构 | ~2 KB | TokenBucket、DownloadStats、Collector |
| 总计 | ~13 MB RSS | 常驻内存,不随流量增长 |
下载的字节直接从 socket 读入 buf 后丢弃,不累积在堆中。高带宽下内存不会增长。
| 操作 | 频率 | 大小 |
|---|---|---|
| 状态文件写入 | 1 次/60 秒(可配置) | ~2-4 KB |
| 下载内容写入 | 0 | 完全丢弃,不落盘 |
平均磁盘写入速率约 0.07 KB/s,对 SSD/HDD 无压力。
以上行 5 MB/s、下行 10 MB/s 为例:
| 方向 | 速率 | 来源 |
|---|---|---|
| 下行(下载) | 10 MB/s | HTTP 响应体,由令牌桶精确控制 |
| 上行(协议开销) | ~100-300 KB/s | TCP ACK + HTTP 请求头,由下载行为产生 |
| 上行(外部流量) | 5 MB/s | 用户自身的上行流量,非程序产生 |
上行带宽 目标下行(ratio=2) CPU 内存 Read次数/秒
───────────────────────────────────────────────────────────
0 0 (停止) ~0% 13 MB 0
100 KB/s 200 KB/s <0.05% 13 MB ~50
1 MB/s 2 MB/s <0.1% 13 MB ~500
5 MB/s 10 MB/s 0.5% 13 MB ~2,500
50 MB/s 100 MB/s 2% 15 MB ~25,000
100 MB/s 200 MB/s (受限) 3% 15 MB ~50,000
Read 次率 = 目标下行速率 / 4KB 缓冲大小。高带宽下 Read 调用频率上升,但每次调用耗时仍为微秒级。
# 本地构建
go build -o fetch-down .
# 交叉编译
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o fetch-down-linux-arm64 .
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o fetch-down-windows-amd64.exe .
# 静态分析
go vet ./...| 依赖 | 用途 |
|---|---|
github.com/shirou/gopsutil/v3 |
跨平台网卡统计(Linux 回退路径) |
gopkg.in/yaml.v3 |
YAML 配置解析 |
MIT