【问题标题】:upload large file - Error VirtualAlloc of x bytes failed with errno=1455 fatal error: out of memory上传大文件 - 错误 VirtualAlloc of x bytes failed with errno=1455 致命错误:内存不足
【发布时间】:2021-08-13 18:45:49
【问题描述】:

我有 10GB 的大文件,我正在尝试通过 Postman 在 Go 中使用 multipart/form-data 上传。由于我不太了解 Go 中的文件上传是如何工作的,所以我在 YouTube 上找到了教程。

文件上传适用于较小的文件,但在较大的文件上总是崩溃并显示消息:“runtime: VirtualAlloc of 9193373696 bytes failed with errno=1455 致命错误:内存不足”。这是我正在尝试的代码:

    err := r.ParseMultipartForm(500 << 20)
    if err != nil {
        fmt.Fprintln(w, err)
    }

    file, handler, err := r.FormFile("file")

    if err != nil {
        fmt.Fprintln(w, err)
    }

    fmt.Fprintln(w, handler.Filename)
    fmt.Fprintln(w, handler.Size)
    fmt.Fprintln(w, handler.Header.Get("Content-type"))

    defer file.Close()

    saveLocation := "C:\\Users\\Pc\\go\\src\\github.com\\test\\uptest"
    tempFile, err := ioutil.TempFile(saveLocation, "upload")

    if err != nil {
        fmt.Fprintln(w, err)
    }

    defer tempFile.Close()

    fileBytes, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Fprintln(w, err)
    }
    tempFile.Write(fileBytes)

【问题讨论】:

  • 意识到建议的副本与下载而不是上传有关,但基本问题和解决方案是相同的(使用 io.copy 而不是使用 ReadAllWrite,这需要您持有内存中的文件)。
  • 如果你上传一个大文件,你可以把它分成很多小文件;同时上传文件;并在服务器端合并它们。
  • @Brits 用于将文件下载到服务器或 PC。我需要将文件上传到服务器。不过还是谢谢。
  • @Brits 我尝试了许多其他解决方案,发现上传文件会在临时目录中复制,但不知道如何获取该副本。

标签: go


【解决方案1】:

使用ParseMultipartForm() 将要求您为临时存储上传的文件提供最大内存分配。如果您的文件很大(并且大于您分配的内存数量),那么这对您的内存资源来说是个坏消息。

来自文档:

ParseMultipartForm 将请求正文解析为 multipart/form-data。整个请求正文被解析,其文件部分的最多 maxMemory 字节存储在内存中,其余部分存储在磁盘中的临时文件中。 ParseMultipartForm 在必要时调用 ParseForm。一次调用 ParseMultipartForm 后,后续调用无效。

根据您的错误信息,我们可以判断您的问题的根本原因是由于上传的文件大于您分配的内存,即500 &lt;&lt; 20

对于处理大文件上传,我建议看看MultipartReader()

来自 doc:

如果这是一个 multipart/form-data 或 multipart/mixed POST 请求,MultipartReader 返回一个 MIME 多部分阅读器,否则返回 nil 和错误。使用此函数而不是 ParseMultipartForm 将请求正文作为流处理。

这是一种更快的方法并且不会消耗太多资源,这是因为我们将具有使用io.Copy()直接将主体(这是一个流数据)存储到目标文件中的优势,而不是暂时写入它存储优先。

MultipartReader()的简单使用示例:

reader, err := r.MultipartReader()
if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

for {
    part, err := reader.NextPart()
    if err == io.EOF {
        break
    }

    fmt.Println(part.FileName()) // prints file name
    fmt.Println(part.FormName()) // prints form key, in yor case it's "file"
    
    saveLocation := "C:\\Users\\Pc\\go\\src\\github.com\\test\\uptest"
    dst, err := os.Create(saveLocation)
    if dst != nil {
        defer dst.Close()
    }
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    if _, err := io.Copy(dst, part); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}

参考:https://pkg.go.dev/net/http#Request.ParseMultipartForm

【讨论】:

  • 非常感谢您。您的代码是我所需要的,并且像魅力一样工作。我想用我能给予的最高荣誉来奖励你!
  • 我很乐意提供帮助 :)
猜你喜欢
  • 2020-12-04
  • 1970-01-01
  • 2013-05-08
  • 2014-06-01
  • 2014-01-25
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多