Skip to content

weyeahh/Fetch-Down

Repository files navigation

Fetch-Down

自动下行流量管理工具。通过监控网卡上行流量,动态生成下载流量以维持上下行比例或满足累计流量目标。下载内容不写入磁盘,仅消耗带宽。

功能

  • 带宽模式(bandwidth) — 下行带宽 = 上行带宽 × 配置比例,自动动态调整
  • 流量模式(traffic) — 时间窗口内:入网总流量 = 出网总流量 × 倍数
  • 令牌桶速率限制,支持动态调整和突发控制
  • 流量模式字节上限控制:按剩余目标分配单次下载配额,避免超额
  • 并发下载 worker 池,自动伸缩
  • 网卡流量采集(Linux /proc/net/dev / 跨平台 gopsutil
  • 状态持久化,进程崩溃或重启后恢复运行现场
  • 优雅退出,信号处理 + 最终统计输出
  • 仅允许 http/https 下载 URL,重定向控制,连接超时
  • URL 熔断机制:连续失败 5 次自动跳过,60 秒冷却后自动恢复

安装

从 Release 下载

Releases 页面下载对应平台的二进制文件。

支持平台:linux/amd64linux/arm64windows/amd64darwin/amd64darwin/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 信号优雅退出。

以 systemd 服务运行(Linux)

将二进制文件和配置放到标准路径:

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
│  └──────────────────┘
└─────────────┘

带宽模式(bandwidth)

  1. collectorLoopuplink_sample_interval 秒采样网卡,计算实时上行带宽(B/s)
  2. 控制循环读取上行带宽,计算目标下行 = 上行 × ratio
  3. 对目标速率应用平滑因子(α=0.3),避免振荡
  4. 若上行带宽趋近于 0,平滑后速率降至阈值以下时,调用 bucket.Stop() 停止所有下载
  5. 将平滑后的速率设为令牌桶限速,根据速率动态调整 worker 数量
  6. 下载 worker 通过令牌桶限速读取 HTTP 响应体,数据读取后丢弃,不写入磁盘

流量模式(traffic)

  1. 程序启动时记录窗口起点时间和上行基准值
  2. 控制循环每个采样周期计算:
    • windowUplink = 窗口内累计上行字节数
    • targetBytes = windowUplink × cumulative_multiplier
    • windowDownBytes = 窗口内累计下行字节数(程序级统计)
    • remaining = targetBytes - windowDownBytes
  3. 根据 remaining / 剩余时间 计算所需下载速率
  4. 每次下载前,按 remaining / 活跃worker数 计算单次下载字节上限(MaxBytes),读满即停,避免超额
  5. 到达窗口末尾时自动重置:清零所有计数器,重新开始新窗口
  6. 窗口重置后自动保存一次状态文件

令牌桶速率控制

  • 令牌以配置速率(B/s)填充,突发容量 = 速率 × 0.5 秒
  • 下载 worker 每次读取前向令牌桶申请 4KB 令牌
  • 令牌不足时阻塞等待,支持 context 取消
  • Stop() 设置停止标志后令牌桶拒绝所有请求,Wait 阻塞直到 context 取消或恢复

URL 熔断机制

下载 URL 列表中可能存在不可用地址。为避免在坏 URL 上浪费重试时间,控制器维护 URL 健康状态:

  • 每次下载失败记录该 URL 的连续失败次数
  • 连续失败达到 5 次后,该 URL 被标记为禁用,轮询时自动跳过
  • 禁用 60 秒后自动冷却恢复,重新尝试
  • 下载成功则重置失败计数

流量采集

优先读取 Linux /proc/net/dev(零依赖),失败时回退到 gopsutil 跨平台库。

采集数据:

  • RxBytes / TxBytes — 累计收发字节数(系统级,自开机起)
  • 通过两次采样差值计算实时带宽
  • 程序级累计值 = 当前系统值 - 程序启动时基准值

异常处理:

  • 采样失败时标记 stale,控制循环跳过本轮决策
  • 计数器回绕(网卡重置等)时自动重置基准值并记录警告

状态持久化

流量模式下自动启用(可通过 state_file 手动配置)。

保存内容:

  • 当前模式、窗口起始时间
  • 累计上下行字节数、请求成功/失败数
  • 窗口内上行基准值

写入方式:先写 .tmp 临时文件,再 os.Rename 原子替换(Windows 上自动回退为先删后改)。

恢复逻辑:

  • 窗口未过期:恢复窗口起点和累计值,继续在原窗口运行
  • 窗口已过期:清零所有状态,开始新窗口

退出流程

  1. 收到 SIGINTSIGTERM
  2. 调用 SaveAndStop():保存最终状态 → 调用 cancel() 取消所有 goroutine
  3. 等待 controlLoop 关闭 done 通道(最长 5 秒超时)
  4. 输出最终统计(总下载量、请求数、运行时长)
  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

CPU 消耗明细

上行 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 后丢弃,不累积在堆中。高带宽下内存不会增长。

磁盘 I/O

操作 频率 大小
状态文件写入 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 配置解析

License

MIT

About

一个按固定上下行流量比例刷取宿主机下行流量的程序,适用于FRP等一比一流量模型的转发服务,在一定情况下打破一比一流量模型。

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages