pkg/sftp为什么比Linux SCP慢很多?

3

使用 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语言的等效运行速度?

2
SCP和SFTP是不同的协议,它们并不真正可比。它与OpenSSH/SFTP相比如何? - JimB
@JimB 很好的观点。我刚试用了 https://github.com/bramvdbogaerde/go-scp,但仍然存在同样的问题,需要12分钟。因此看起来瓶颈很可能是 io.Copy... - vicariouslyi
我认为瓶颈更可能是ssh包,因为协议实现很难优化吞吐量。您可以轻松地将文件包装在bufio.Reader中,但加速管道可能是所需的。 - JimB
pkg/sftp支持并发上传,你试过吗? - Oleg Neumyvakin
1个回答

0

我发现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
}

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

  • Len() int
  • Size() int64
  • Stat() (os.FileInfo, error)

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

如果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函数并产生大量小数据块,则即使使用并发,吞吐量也会相当低。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接