go - pkg/sftp 比 Linux SCP 慢得多,为什么?

标签 go scp

在 Ubuntu 18.04 上使用标准 scp(版本:1:7.6p1-4ubuntu0.3),将 110GB 的文件传输到另一台主机大约需要 6-8 分钟。

当使用 Go 的 pkg/sftp 时,它需要两倍的时间:

例子:

package main

import (
    "fmt"
    "github.com/melbahja/goph"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
    "os"
    "time"
)

func main() {
    auth, err := goph.Key("your/key", "")
    if err != nil {
        log.Fatalln(err)
    }

    client, err := goph.NewConn(&goph.Config{
        User:     "someUser",
        Addr:     "someHostname",
        Port:     22,
        Auth:     auth,
        //Timeout:  time.Duration(timeout) * time.Second,
        Callback: ssh.InsecureIgnoreHostKey(),
    })

    if err != nil {
        log.Fatalln(err)
    }

    file := "/some/file"
    start := time.Now()

    local, err := os.Open(file)
    if err != nil {
        return
    }
    defer local.Close()

    ftp, err := client.NewSftp()
    // VARIATION 1 => ftp, err := client.NewSftp(sftp.MaxPacketUnchecked(1 << 16))
    if err != nil {
        return
    }
    defer ftp.Close()

    remote, err := ftp.Create(file)
    if err != nil {
        return
    }
    defer remote.Close()

    /*
    VARIATION 1 => buffer := make([]byte, 1 << 16)
    VARIATION 1 => _, err = io.CopyBuffer(remote, local, buffer)
     */

    _, err = io.Copy(remote, local)

    if err != nil {
        log.Fatalln(err)
    }

    duration := time.Since(start)
    fmt.Println(duration)
}

注意:我什至尝试使用注释掉的行(参见 VARIATION 1)增加读取缓冲区的大小(和 tcp 最大数据包大小),但没有任何区别。

关于为什么以及如何加速 Go 等价物的任何想法?

最佳答案

我发现pkg/sftp支持并发上传,并发上传速度和linux的sftp命令很像。

这里是客户端初始化:

sftpConn, err := sftp.NewClient(sshConn,
        sftp.UseConcurrentReads(true),
        sftp.UseConcurrentWrites(true),
        sftp.MaxConcurrentRequestsPerFile(64),
        // Big max packet size can improve throughput.
        sftp.MaxPacketUnchecked(128*agentio.KiB),
        // Beware of customizing max packet size for download! 
        // On download, big max packet size can cause "connection lost" error.
    )

使用方法如下,注意ReadFrom:

func sftpUpload(c *sftp.Client, r io.Reader, path string) error {
    fp, err := c.Create(path)
    if err != nil {
        return fmt.Errorf("create destination file: %w", err)
    }
    defer fp.Close()

    _, err = fp.ReadFrom(r)

    return err
}

要使用并发上传,reader 的下划线结构必须是 bytes.Reader、io.LimitedReader 或满足以下接口(interface)之一:

  • Len() 整数
  • 大小() int64
  • 统计()(os.FileInfo,错误)

需要确定要上传的字节数。

如果 ReadFrom 无法确定阅读器的大小,它将回退到单线程上传。

查看ReadFrom源码:

func (f *File) ReadFrom(r io.Reader) (int64, error) {
    f.mu.Lock()
    defer f.mu.Unlock()

    if f.c.useConcurrentWrites {
        var remain int64
        switch r := r.(type) {
        case interface{ Len() int }:
            remain = int64(r.Len())

        case interface{ Size() int64 }:
            remain = r.Size()

        case *io.LimitedReader:
            remain = r.N

        case interface{ Stat() (os.FileInfo, error) }:
            info, err := r.Stat()
            if err == nil {
                remain = info.Size()
            }
        }

此外,如果您将读取器传递给 ReadFrom,这会产生大量小数据,即使并发时吞吐量也会非常低。

关于go - pkg/sftp 比 Linux SCP 慢得多,为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65726482/

相关文章:

go - 在Golang中附加文件并通过SMTP发送时没有正文的电子邮件

linux - 使用从 PowerShell 执行的 pscp 调试文件传输

scp - 通过 scp 定期将文件备份到另一台服务器

ant - SCP/SSHEXEC - 连接尝试次数

function - 尝试取消引用一个接口(interface),该接口(interface)是指向后端结构对象的指针,因此我可以按值传递给函数

go - 无法使用 GOLANG 和 POLYMER 在服务器上保存上传的文件 http : no such file

windows - 在 list 列表条目中获取 : no matching manifest for windows/amd64 10. 0.18362

session - Gorilla session 包混淆

ssh - ESXi 中的 SCP 不起作用

scp - SCP 中的默认文件传输模式