如何在 AWS SDK 中实现 AWS CLI Sync 命令的性能

How to Achieve Performance of AWS CLI Sync Command in AWS SDK

CLI 中的aws s3 sync 命令可以非常快速地下载大量文件,而我无法使用AWS Go SDK 实现相同的性能。我的存储桶中有数百万个文件,所以这对我来说至关重要。我还需要使用 list pages 命令,以便我可以添加 sync CLI 命令不支持的前缀。

我曾尝试使用多个 goroutine(10 到 1000 个)向服务器发出请求,但与 CLI 相比,时间实在是太慢了。 运行 Go GetObject 函数每个文件大约需要 100 毫秒,这对于我拥有的文件数量来说是不可接受的。我知道 AWS CLI 在后端也使用了 Python SDK,那么它为什么有如此好的性能(我在 boto 和 Go 中试过我的脚本)。

我正在使用 ListObjectsV2PagesGetObject。我的区域与 S3 服务器的区域相同。

    logMtx := &sync.Mutex{}
    logBuf := bytes.NewBuffer(make([]byte, 0, 100000000))

    err = s3c.ListObjectsV2Pages(
        &s3.ListObjectsV2Input{
            Bucket:  bucket,
            Prefix:  aws.String("2019-07-21-01"),
            MaxKeys: aws.Int64(1000),
        },
        func(page *s3.ListObjectsV2Output, lastPage bool) bool {
            fmt.Println("Received", len(page.Contents), "objects in page")
            worker := make(chan bool, 10)
            for i := 0; i < cap(worker); i++ {
                worker <- true
            }
            wg := &sync.WaitGroup{}
            wg.Add(len(page.Contents))
            objIdx := 0
            objIdxMtx := sync.Mutex{}
            for {
                <-worker
                objIdxMtx.Lock()
                if objIdx == len(page.Contents) {
                    break
                }
                go func(idx int, obj *s3.Object) {
                    gs := time.Now()
                    resp, err := s3c.GetObject(&s3.GetObjectInput{
                        Bucket: bucket,
                        Key:    obj.Key,
                    })
                    check(err)
                    fmt.Println("Get: ", time.Since(gs))

                    rs := time.Now()
                    logMtx.Lock()
                    _, err = logBuf.ReadFrom(resp.Body)
                    check(err)
                    logMtx.Unlock()
                    fmt.Println("Read: ", time.Since(rs))

                    err = resp.Body.Close()
                    check(err)
                    worker <- true
                    wg.Done()
                }(objIdx, page.Contents[objIdx])
                objIdx += 1
                objIdxMtx.Unlock()
            }
            fmt.Println("ok")
            wg.Wait()
            return true
        },
    )
    check(err)

许多结果如下所示:

Get:  153.380727ms
Read:  51.562µs

您尝试过使用 https://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3manager/ 吗?

iter := new(s3manager.DownloadObjectsIterator)
var files []*os.File
defer func() {
    for _, f := range files {
        f.Close()
    }
}()

err := client.ListObjectsV2PagesWithContext(ctx, &s3.ListObjectsV2Input{
    Bucket: aws.String(bucket),
    Prefix: aws.String(prefix),
}, func(output *s3.ListObjectsV2Output, last bool) bool {
    for _, object := range output.Contents {
        nm := filepath.Join(dstdir, *object.Key)
        err := os.MkdirAll(filepath.Dir(nm), 0755)
        if err != nil {
            panic(err)
        }

        f, err := os.Create(nm)
        if err != nil {
            panic(err)
        }

        log.Println("downloading", *object.Key, "to", nm)

        iter.Objects = append(iter.Objects, s3manager.BatchDownloadObject{
            Object: &s3.GetObjectInput{
                Bucket: aws.String(bucket),
                Key:    object.Key,
            },
            Writer: f,
        })
        files = append(files, f)
    }

    return true
})
if err != nil {
    panic(err)
}

downloader := s3manager.NewDownloader(s)
err = downloader.DownloadWithIterator(ctx, iter)
if err != nil {
    panic(err)
}

我最终在最初的 post 中接受了我的剧本。我尝试了 20 个 goroutine,它们似乎运行良好。在我的笔记本电脑上,初始脚本肯定比命令行(i7 8 线程、16 GB RAM、NVME)和 CLI 慢。但是,在 EC2 实例上,差异很小,不值得我花时间进一步优化它。我在与 S3 服务器相同的区域中使用了一个 c5.xlarge 实例。