Golang HTTP服务器大文件上传内存占用问题解决方案

Golang HTTP服务器大文件上传内存占用问题解决方案 大家好,

我在服务器端使用以下文件上传处理程序代码来上传文件…

func fileUpload(req *http.Request) {
    req.ParseMultipartForm(32 << 20)

    file, handler, err := req.FormFile("datafile")
    if err != nil {
        fmt.Println("Error Retrieving the File")
        fmt.Println(err)
        return
    }
    defer file.Close()
    fmt.Printf("Uploaded File: %+v\n", handler.Filename)
    fmt.Printf("File Size: %+v\n", handler.Size)
    fmt.Printf("MIME Header: %+v\n", handler.Header)

    f, err := os.OpenFile("./"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    io.Copy(f, file)
}

当我上传大小约为 500 MB 的文件时,上述代码会占用约 500 MB 的 RAM 内存。 我在一个设备上运行 HTTP 服务器,需要限制我的 Go 应用程序可以使用的 RAM 内存。

我认为 req.FormFile("") 正在使用与上传文件大小成线性关系的 RAM 内存。是否可以限制缓冲区大小,使得无论上传文件大小如何,RAM 使用率都保持在指定限制以下? 如果这意味着文件传输需要更长一点的时间,那也没关系。 是否有其他不同的方式来完成文件上传?

这个问题困扰我有一段时间了,非常感谢任何帮助。

谢谢,


更多关于Golang HTTP服务器大文件上传内存占用问题解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang HTTP服务器大文件上传内存占用问题解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


对于大文件上传的内存问题,解决方案是使用 multipart.Reader 进行流式处理,而不是一次性加载整个文件到内存。以下是改进后的代码:

func fileUpload(w http.ResponseWriter, req *http.Request) {
    // 设置内存限制(例如10MB)
    const maxMemory = 10 * 1024 * 1024
    
    // 解析multipart表单,限制内存使用
    err := req.ParseMultipartForm(maxMemory)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // 获取multipart reader进行流式处理
    reader, err := req.MultipartReader()
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // 遍历multipart的各个部分
    for {
        part, err := reader.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        // 检查是否是文件字段
        if part.FormName() == "datafile" {
            // 创建目标文件
            f, err := os.Create("./" + part.FileName())
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            defer f.Close()
            
            // 使用缓冲区进行流式复制
            buf := make([]byte, 32*1024) // 32KB缓冲区
            _, err = io.CopyBuffer(f, part, buf)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            
            fmt.Printf("Uploaded File: %s\n", part.FileName())
            break
        }
        
        part.Close()
    }
    
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("File uploaded successfully"))
}

更简洁的版本使用 io.Copy 直接流式传输:

func fileUploadStream(w http.ResponseWriter, req *http.Request) {
    // 直接使用MultipartReader进行流式处理
    reader, err := req.MultipartReader()
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // 查找文件部分
    for {
        part, err := reader.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        if part.FormName() == "datafile" {
            // 创建目标文件
            dst, err := os.Create("./" + part.FileName())
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            defer dst.Close()
            
            // 流式复制,内存使用恒定
            _, err = io.Copy(dst, part)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            
            fmt.Printf("Uploaded: %s\n", part.FileName())
            w.WriteHeader(http.StatusOK)
            return
        }
        part.Close()
    }
    
    http.Error(w, "No file found", http.StatusBadRequest)
}

如果需要限制上传速度,可以添加限流器:

func fileUploadWithLimit(w http.ResponseWriter, req *http.Request) {
    reader, err := req.MultipartReader()
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    for {
        part, err := reader.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        if part.FormName() == "datafile" {
            dst, err := os.Create("./" + part.FileName())
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            defer dst.Close()
            
            // 创建限流reader(例如限制为1MB/s)
            throttledReader := &throttledReader{
                reader: part,
                limit:  1024 * 1024, // 1MB per second
            }
            
            _, err = io.Copy(dst, throttledReader)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            
            w.WriteHeader(http.StatusOK)
            return
        }
        part.Close()
    }
}

type throttledReader struct {
    reader io.Reader
    limit  int64 // bytes per second
}

func (t *throttledReader) Read(p []byte) (n int, err error) {
    // 实现限流逻辑
    start := time.Now()
    n, err = t.reader.Read(p)
    elapsed := time.Since(start)
    
    if t.limit > 0 && n > 0 {
        expected := time.Duration(n) * time.Second / time.Duration(t.limit)
        if elapsed < expected {
            time.Sleep(expected - elapsed)
        }
    }
    return n, err
}

这些解决方案的关键区别:

  1. 使用 MultipartReader() 而不是 FormFile()ParseMultipartForm()
  2. 通过 io.Copy 进行流式传输,内存使用恒定(由缓冲区大小决定)
  3. 避免将整个文件加载到内存中

使用这些方法后,无论上传文件大小如何,内存占用将保持在几十KB到几MB的范围内。

回到顶部