上传大文件 – 错误 VirtualAlloc x 字节失败,错误号为 1455 致命错误:内存不足

huangapple go评论73阅读模式
英文:

upload large file - Error VirtualAlloc of x bytes failed with errno=1455 fatal error: out of memory

问题

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

文件上传对于较小的文件可以正常工作,但对于较大的文件,总是会崩溃并显示错误消息:"runtime: VirtualAlloc分配9193373696字节内存失败,错误码为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)

请注意,这只是一个示例代码,你需要根据你的实际需求进行适当的修改。

英文:

I have 10GB large file that I'm trying to upload with multipart/form-data in Go via Postman. Since I don't know much how file upload works in Go, I found tutorial on YouTube.

File upload works fine with smaller files, but on larger files always crashing with message: "runtime: VirtualAlloc of 9193373696 bytes failed with errno=1455
fatal error: out of memory". Here's the code I'm trying to make work:

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

	file, handler, err := r.FormFile(&quot;file&quot;)

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

	fmt.Fprintln(w, handler.Filename)
	fmt.Fprintln(w, handler.Size)
	fmt.Fprintln(w, handler.Header.Get(&quot;Content-type&quot;))

	defer file.Close()

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

	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)

答案1

得分: 5

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

根据您的错误消息,我们可以确定问题的根本原因是上传的文件大于您分配的内存,即500 << 20

为了处理大文件上传,我建议您查看MultipartReader()

这是一个更快的方法,不会消耗太多资源,因为我们可以直接将请求体(流数据)使用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()) // 打印文件名
    fmt.Println(part.FormName()) // 打印表单键,对于您的情况是"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

英文:

Using ParseMultipartForm() will require you to provide max memory allocation for temporarily store the uploaded file. If your file size is big (and it's bigger than the number of memory you allocated) then it's bad news for your memory resource.

> From doc:<br/><br/>ParseMultipartForm parses a request body as multipart/form-data. The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files. ParseMultipartForm calls ParseForm if necessary. After one call to ParseMultipartForm, subsequent calls have no effect.

Based on your error message, we can tell that the root cause of your issue is due to the uploaded file is larger than the memory you allocated, which is 500 &lt;&lt; 20.

For handling big file upload, I suggest to take a look at MultipartReader() instead.

> From doc:<br/><br/>MultipartReader returns a MIME multipart reader if this is a multipart/form-data or a multipart/mixed POST request, else returns nil and an error. Use this function instead of ParseMultipartForm to process the request body as a stream.

It's way faster approach and won't consume too much resource, it's because we will have the advantage of directly store the body (which is a stream data) into the destination file using io.Copy(), instead of writing it into temporarily storage first.

A simple usage example of 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&#39;s &quot;file&quot;
    
    saveLocation := &quot;C:\\Users\\Pc\\go\\src\\github.com\\test\\uptest&quot;
    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
    }
}

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

huangapple
  • 本文由 发表于 2021年8月9日 07:29:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/68705450.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定