【问题标题】:Best Approach to Zipping up a Folder in Google Cloud Storage using Go?使用 Go 在 Google Cloud Storage 中压缩文件夹的最佳方法?
【发布时间】:2015-10-15 04:34:09
【问题描述】:

我的 Google App Engine Go 项目在 Google Cloud Storage 中的“文件夹”中创建了多个文件的 zip。在 BlobStore 中使用现已弃用和删除的 Files API 实现它时,它曾经非常快。我最近将代码转换为使用 Google Cloud Storage,现在性能非常糟糕,有时会超时。被压缩的文件大小在 1K 到 2M 之间。

我正在寻找任何建议来改进压缩文件内容。下面的代码是我为将云中的多个文件压缩为云中的新 zip 文件而编写的。执行可能需要很长时间,并且需要将每个文件的全部内容(参见下面的性能问题)加载到内存中,然后再将其写入 zip。必须有更好的方法。

// Pack a folder into zip file
func (cloud *Cloud) Pack(srcFolder string, fileName string, contentType string, metaData *map[string]string) {

    log.Infof(cloud.c, "Packing bucket %v folder %v to file %v", cloud.bucket, srcFolder, fileName) 

    srcFolder = fmt.Sprintf("%v/", srcFolder)
    query := &storage.Query{Prefix: srcFolder, Delimiter: "/"}

    objs, err := storage.ListObjects(cloud.ctx, cloud.bucket, query)
    if err != nil {
        log.Errorf(cloud.c, "Packing failed to list bucket %q: %v", cloud.bucket, err)
        return
    }

    totalFiles := len(objs.Results)
    if totalFiles == 0 {
        log.Errorf(cloud.c, "Packing failed to find objects found in folder %q: %v", cloud.bucket, srcFolder)
        return
    }

    // create storage file for writing
    log.Infof(cloud.c, "Writing new zip file to %v/%v for %v files", cloud.bucket, fileName, totalFiles)
    storageWriter := storage.NewWriter(cloud.ctx, cloud.bucket, fileName)

    // add optional content type and meta data  
    if len(contentType) > 0 { storageWriter.ContentType = contentType }
    if metaData != nil { storageWriter.Metadata = *metaData }

    // Create a buffer to write our archive to.
    buf := new(bytes.Buffer)

    // Create a new zip archive to memory buffer
    zipWriter := zip.NewWriter(buf)

    // go through each file in the folder
    for _, obj := range objs.Results {

        log.Infof(cloud.c, "Packing file %v of size %v to zip file", obj.Name, obj.Size)
        //d.dumpStats(obj)

        // read file in our source folder from storage - io.ReadCloser returned from storage
        storageReader, err := storage.NewReader(cloud.ctx, cloud.bucket, obj.Name)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to read from bucket %q file %q: %v", cloud.bucket, obj.Name, err)
            return  
        }
        defer storageReader.Close()

        // PERFORMANCE ISSUE: have to load the entire file into memory to get random access from the cloud
        slurp, err := ioutil.ReadAll(storageReader)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to read data from bucket %q file %q: %v", cloud.bucket, obj.Name, err)
            return
        }

        // grab just the filename from directory listing (don't want to store paths in zip)
        _, zipFileName := filepath.Split(obj.Name)

        newFileName := strings.ToLower(zipFileName)

        // add filename to zip
        zipFile, err := zipWriter.Create(newFileName)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to create zip file from bucket %q file %q: %v", cloud.bucket, zipFileName, err)
            return
        }

        // write entire file into zip archive
        _, err = zipFile.Write(slurp)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to write zip file from bucket %q file %q: %v", cloud.bucket, zipFileName, err)
            return
        }

        // flush that to buffer so we can write it off now
        //err = zipFile.Flush()
        //if err != nil {
        //  d.errorf("pack: unable to flush write of zip file from bucket %q, file %q: %v", cloud.bucket, zipFileName, err)
        //  //return
        //}

        // now drain all that buffered zip data to the cloud storage file   
        log.Infof(cloud.c, "Writing zip buffer of size %v to cloud storage file %v", buf.Len(), fileName)   
        _, err = buf.WriteTo(storageWriter)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to write data to bucket %q file %q: %v", cloud.bucket, fileName, err)   
            return          
        }
    }

    // Make sure to check the error on Close.
    log.Infof(cloud.c, "Closing zip writer")    
    err = zipWriter.Close()
    if err != nil {
        log.Errorf(cloud.c, "Packing failed to close zip file writer from bucket %q file %q : %v", cloud.bucket, fileName, err)
    }

    // write any leftover data
    if buf.Len() > 0 {
        // now drain all that buffered zip data to the cloud storage file   
        // log.Infof(cloud.c, "Packing zip buffer of size %v to cloud storage file %v", buf.Len(), fileName)    
        _, err := buf.WriteTo(storageWriter)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to write data to bucket %q file %q: %v", cloud.bucket, fileName, err)               
        }
    }

    // close up final write file
    //log.Infof(cloud.c, "Closing cloud storage file %v", fileName) 
    if err := storageWriter.Close(); err != nil {
        log.Errorf(cloud.c, "Packing failed to close bucket %q file %q: %v", cloud.bucket, fileName, err)
        return
    }

    // success!
    log.Infof(cloud.c, "Packed files to new cloud storage file %v successful!", fileName)   
}

【问题讨论】:

  • 我认为您没有理由将其全部加载到内存中。为什么需要从文件中随机访问?您只需要将 io.Copy 从 src 复制到 zip。我也不明白为什么需要将 zip 文件加载到内存中。您可以在构建过程中将其流式传输出来。
  • 我会再次调查,但我很确定这是我尝试的第一件事......
  • @StephenWeinberg Zip Reader 需要一个 io.ReaderAt 接口,这就是为什么您必须将整个内容加载到缓冲区以从云中随机访问(请参阅:golang.org/src/archive/zip/reader.go),因为云存储只提供你是一个 ReadCloser 接口(请参阅:godoc.org/google.golang.org/cloud/storage#example-NewReader)这就是为什么 BlobStore 对我来说要快得多。我错过了什么吗?
  • 那是为了阅读。你在写。
  • @StephenWeinberg 谢谢!这显然是我的 Unpack 例程的复制/粘贴错误。也不需要先写入缓冲区,直接写入存储文件即可。所以这是两个很大的改进。干杯

标签: google-app-engine go google-cloud-storage


【解决方案1】:

感谢 Stephen 建议在写入 zip 文件时不要将文件加载到内存缓冲区中。以下是固定代码供参考:

// Pack a folder into zip file
func (cloud *Cloud) Pack(srcFolder string, fileName string, contentType string, metaData *map[string]string) bool {

    log.Infof(cloud.c, "Packing bucket %v folder %v to file %v", cloud.bucket, srcFolder, fileName) 

    srcFolder = fmt.Sprintf("%v/", srcFolder)
    query := &storage.Query{Prefix: srcFolder, Delimiter: "/"}

    objs, err := storage.ListObjects(cloud.ctx, cloud.bucket, query)
    if err != nil {
        log.Errorf(cloud.c, "Packing failed to list bucket %q: %v", cloud.bucket, err)
        return false
    }

    totalFiles := len(objs.Results)
    if totalFiles == 0 {
        log.Errorf(cloud.c, "Packing failed to find objects found in folder %q: %v", cloud.bucket, srcFolder)
        return false
    }

    // create storage file for writing
    log.Infof(cloud.c, "Writing new zip file to %v/%v for %v files", cloud.bucket, fileName, totalFiles)
    storageWriter := storage.NewWriter(cloud.ctx, cloud.bucket, fileName)
    defer storageWriter.Close()

    // add optional content type and meta data  
    if len(contentType) > 0 { storageWriter.ContentType = contentType }
    if metaData != nil { storageWriter.Metadata = *metaData }

    // Create a new zip archive to memory buffer
    zipWriter := zip.NewWriter(storageWriter)

    // go through each file in the folder
    for _, obj := range objs.Results {

        log.Infof(cloud.c, "Packing file %v of size %v to zip file", obj.Name, obj.Size)
        //d.dumpStats(obj)

        // read file in our source folder from storage - io.ReadCloser returned from storage
        storageReader, err := storage.NewReader(cloud.ctx, cloud.bucket, obj.Name)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to read from bucket %q file %q: %v", cloud.bucket, obj.Name, err)
            return false
        }
        defer storageReader.Close()

        // grab just the filename from directory listing (don't want to store paths in zip)
        _, zipFileName := filepath.Split(obj.Name)
        newFileName := strings.ToLower(zipFileName)

        // add filename to zip
        zipFile, err := zipWriter.Create(newFileName)
        if err != nil {
            log.Errorf(cloud.c, "Packing failed to create zip file from bucket %q file %q: %v", cloud.bucket, zipFileName, err)
            return false
        }

        // copy from storage reader to zip writer   
        _, err = io.Copy(zipFile, storageReader)
        if err != nil {
            log.Errorf(cloud.c, "Failed to copy from storage reader to zip file: %v", err)
            return false
        }   
    }

    // Make sure to check the error on Close.
    log.Infof(cloud.c, "Closing zip writer")    
    err = zipWriter.Close()
    if err != nil {
        log.Errorf(cloud.c, "Packing failed to close zip file writer from bucket %q file %q : %v", cloud.bucket, fileName, err)
        return false
    }

    // success!
    log.Infof(cloud.c, "Packed files to new cloud storage file %v successful!", fileName)   
    return true
}

【讨论】:

  • 为此编写了一个版本以在 google 应用引擎之外使用,但仍在继续:gist.github.com/moorage/a7300dee720e694aa89fd152b2b2c69d
  • @tmoore 此代码是否将谷歌云存储桶中的多个文件压缩/压缩为单个 zip 文件而不在本地下载它们?因为我没有使用 Go 语言,所以我无法理解您代码中的那一部分。请澄清。
猜你喜欢
  • 1970-01-01
  • 2014-11-09
  • 2014-10-13
  • 2011-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-29
相关资源
最近更新 更多