1Panel SFTP 上传速度异常问题解决办法

2026-10-04 分类:Linux 作者:刺猬

前言

用 1Panel 做网站备份、再通过 SFTP 上传到远端,是个很常见的方案。但我这边的备份任务一直慢得离谱:一个 439MB 的备份包,上传要 29 分钟,速度只有 0.25 MB/s 左右。

一开始以为是 VPS 带宽小,或者远端机房慢。查下来才发现:这台机器出口带宽实测 40+ MB/s,链路 RTT 只有几十毫秒、零丢包——问题出在 1Panel 自己身上。

下面记录完整的排查过程和修复方式,最后给出可以直接用的解决方案。


一、先排除网络问题

遇到”上传慢”,第一反应通常是网络。所以先把网络这一侧的证据收集齐:

检查项 结果
本机出口带宽(上传) 42 MB/s(约 336 Mbps)
到目标机 RTT 35 ~ 68 ms
丢包率 0%
传输中的 TCP 状态 bytes_retrans 0/29、send-q 0、cwnd 远未打满

这组数据说明:

  • 带宽完全够用,跑满一次备份只需要几秒;
  • 链路很干净,没有任何重传、没有排队积压;
  • 发送方的拥塞窗口(cwnd)远没打满,也就是说”数据没喂满”——瓶颈在应用层,不在网络。

这一步很关键。很多人看到”上传慢”就先去换机房、换线路,但如果是发送端喂不满,换到哪里都一样慢。

二、A/B 对照:换个客户端就快 60 倍

为了确认问题出在哪个环节,我用同一个文件、同一台目标机、同一条链路,分别用不同客户端测了一遍。

测试文件是一个 96MiB 的安装包(已经压缩过的数据,排除压缩率干扰),目标是同一台 SFTP 服务器(RTT 68ms):

客户端 协议 耗时 速率
1Panel 内置上传 SFTP 6 分 40 秒 0.25 MB/s
rclone SFTP 6 秒 16 MB/s
OpenSSH sftp SFTP 5 秒 19 MB/s
OpenSSH scp -O legacy scp 5 秒 19 MB/s

结论非常清楚:SFTP 协议没问题、目标服务器没问题、链路没问题——系统自带的 sftp 比 1Panel 快 76 倍。

那问题就只剩一个可能:1Panel 自己的 SFTP 客户端实现。

(顺便说明:1Panel 的 SFTP 通信是编译进 1panel-agent 这个 Go 二进制里的,没法”重装 sftp”或者替换成系统自带的客户端。)

三、定位到代码:io.Copy 的一个陷阱

既然是实现问题,就只能去看源码。1Panel 的上传逻辑在:

agent/utils/cloud_storage/client/sftp.go

关键代码长这样(v2.3.2):

client, err := sftp.NewClient(sshClient)
...
dstFile, err := client.Create(target)
...
if _, err := io.Copy(dstFile, srcFile); err != nil {
    return false, err
}

看起来非常普通的 io.Copy,问题恰恰出在这里。

Go 标准库的 io.Copy(dst, src) 有两条优化路径:

  1. 如果 src 实现了 io.WriterTo → 调用 src.WriteTo(dst);
  2. 否则如果 dst 实现了 io.ReaderFrom → 调用 dst.ReadFrom(src)。

它优先选择第 1 条。

而这里的 srcFile 是 *os.File——它正好实现了 io.WriterTo。于是 io.Copy 走了 os.File.WriteTo,最终变成”从本地读约 32KB → 发一个 SFTP WRITE 包 → 同步等待服务端确认 → 再发下一个”。

也就是说,吞吐被死死卡在「包大小 ÷ RTT」:

32 KB ÷ 68 ms ≈ 470 KB/s

这和我们实测的 0.25 MB/s 完全对得上。链路再快也没用,因为同一时刻只有一个包在路上。

有意思的是,pkg/sftp(1Panel 已经依赖的库)本身就有并发写的实现:File.ReadFrom() 会配合 UseConcurrentWrites 使用管线化写入,并发上限由 MaxConcurrentRequestsPerFile 控制(默认 64)。只是这里没启用,所以白白的性能被浪费掉了。

四、修复

改动非常小,一共两处:

// 1) 上传时启用并发写
client, err := sftp.NewClient(sshClient, sftp.UseConcurrentWrites(true))

// 2) 用管线化的 ReadFrom 替代 io.Copy
//    (失败时把远端文件截断到实际写入长度,避免留下空洞)
written, err := dstFile.ReadFrom(srcFile)
if err != nil {
    _ = dstFile.Truncate(written)
    ...
}

需要说明两点:

  • 只改上传路径。 下载那条路径没有动——因为下载时源对象是 *sftp.File,它的 WriteTo 默认已经启用了并发读,本来就是快的。
  • 并发写失败时要做截断。 这是 pkg/sftp 官方文档明确提醒的:并发写如果中途出错,远端文件可能留下”空洞”,所以出错时截断到实际写入的字节数。

五、验证效果

打上补丁、重新编译 1panel-agent 并重启服务后,同一个备份任务:

修复前 修复后
单站 439MB 上传 约 29 分钟 约 17 秒
三站点合计 ~950MB 约 2 小时 40 分 2 分 04 秒

监控到的出口速率峰值 29.8 MB/s。

数据完整性也要验证——并发写最怕文件损坏。把上传到远端的备份包再流式读回来做 gzip -t 校验:

rclone cat remote:/backup/website/xxx.tar.gz | gzip -t

三个备份包全部通过,没有空洞、没有损坏。

六、怎么用上这个修复

我把它整理成了一个开源仓库,三种方式任选:

仓库地址:https://github.com/4kercc/1panel-agent-sftp-fix

方式一:一键安装(推荐)

# 先预览会做什么(不修改系统)
curl -fsSL https://raw.githubusercontent.com/4kercc/1panel-agent-sftp-fix/main/install.sh | bash -s -- --dry-run

# 正式安装
curl -fsSL https://raw.githubusercontent.com/4kercc/1panel-agent-sftp-fix/main/install.sh | bash

脚本会自动:识别面板版本 → 下载对应二进制 → 校验 sha256 → 备份 → 替换 → 重启,启动失败会自动回滚。

回滚命令:

curl -fsSL https://raw.githubusercontent.com/4kercc/1panel-agent-sftp-fix/main/install.sh | bash -s -- --rollback

方式二:手动替换(适用于 1Panel v2.3.2)

# 确认版本
1pctl version        # 应输出 v2.3.2
uname -m             # 应为 x86_64

# 下载 + 校验
curl -fLO https://github.com/4kercc/1panel-agent-sftp-fix/releases/download/v2.3.2/1panel-agent
curl -fLO https://github.com/4kercc/1panel-agent-sftp-fix/releases/download/v2.3.2/1panel-agent.sha256
sha256sum -c 1panel-agent.sha256

# 备份 → 替换 → 重启
cp -a /usr/local/bin/1panel-agent /root/1panel-agent.orig
systemctl stop 1panel-agent
install -m755 1panel-agent /usr/local/bin/1panel-agent
systemctl start 1panel-agent
1pctl status

方式三:自己编译(任意版本)

./build.sh v2.3.2      # 填你的面板版本

需要 Go 1.26.6+。有个小提示:1panel-agent 是纯 Go 且不内嵌前端,编译不需要 Node 和前端构建(只有 1panel-core 才需要),所以在普通 VPS 上几分钟就能编完。

七、几个注意点

  1. 版本必须匹配。 1panel-agent 与面板版本是绑定的(启动时会做数据库结构迁移),不要跨版本替换。其他版本请用 build.sh 自行编译。
  2. 升级会覆盖。 1Panel 每次升级都会覆盖 /usr/local/bin/1panel-agent,升级后需要重新替换或编译。
  3. 建议先备份再动手,并确认自己知道回滚步骤。

八、小结

这次排查最大的收获是:遇到”网络慢”,先把网络排除干净,再去怀疑程序。

证据链其实很清晰:

  • 出口带宽 42 MB/s、RTT 68ms、零丢包 → 网络没问题;
  • TCP 层零重传、发送队列为空、cwnd 没打满 → 是发送端喂不满;
  • 换客户端后同样的链路快 76 倍 → 问题在 1Panel 的实现;
  • 看代码发现 io.Copy 因为接口优先级走了同步写的分支 → 根因确认。

而这类”一行代码导致的性能问题”,修复成本往往比绕开它更低——这次就是两行代码换来 100 倍提升。

上游 PR 已经提交:1Panel-dev/1Panel#13976,希望后续版本能直接内置这个修复。


本文提到的补丁基于 1Panel(GPL-3.0)修改,遵循同一许可证。

» 本文链接:1Panel SFTP 上传速度异常问题解决办法
» 转载请注明来源:刺客博客
» 如果文章失效或者安装失败,请留言进行反馈。
继续阅读