загрузить большой файл - ошибка VirtualAlloc из x байтов не удалась с errno = 1455 фатальная ошибка: недостаточно памяти

avatar
fr33jumper
8 августа 2021 в 23:29
263
1
2

У меня есть большой файл размером 10 ГБ, который я пытаюсь загрузить с помощью multipart/form-data в Go через Postman. Поскольку я мало знаю, как работает загрузка файлов в Go, я нашел учебник на YouTube.

Загрузка файла работает нормально с файлами меньшего размера, но с файлами большего размера всегда происходит сбой с сообщением: "Время выполнения: Ошибка VirtualAlloc из 9193373696 байт с 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)
Источник
Brits
9 августа 2021 в 00:21
1

Отвечает ли это на ваш вопрос? Как я могу эффективно загрузить большой файл с помощью Go?

Brits
9 августа 2021 в 00:24
1

Поймите, что предлагаемый дубликат относится к загрузке, а не к загрузке, но основная проблема и решение одинаковы (используйте io.copy вместо ReadAll и Write, которые требуют, чтобы вы хранили файл в памяти).

Miffa Young
9 августа 2021 в 00:33
1

Если вы загружаете большой файл, вы можете разделить его на множество маленьких файлов; Загружать файлы одновременно; И объединить их на стороне сервера.

fr33jumper
9 августа 2021 в 06:14
0

@Brits Это для загрузки файла на сервер или ПК. Мне нужно было загрузить файл на сервер. Но все равно спасибо.

fr33jumper
9 августа 2021 в 06:17
0

@Brits Я пробовал много других решений и обнаружил, что загрузка файла создает копию во временном каталоге, но не знала, как получить эту копию.

fr33jumper
9 августа 2021 в 06:23
0

@MiffaYoung Я знаю это, но я не знал, как поместить это в свой код. Я знаю, что файл хранится в памяти. Поскольку у меня всего 8 ГБ ОЗУ, а размер файла 10 ГБ, мне нужно хранить фрагменты данных во временном файле на диске, потому что на моем ПК недостаточно памяти для хранения всего файла. Но я не знал, как написать это в своем коде. Спасибо за ответ.

Ответы (1)

avatar
novalagung
9 августа 2021 в 04:28
6

Использование ParseMultipartForm() потребует выделения максимального объема памяти для временного хранения загруженного файла. Если размер вашего файла велик (и он больше, чем объем выделенной вами памяти), то это плохие новости для вашего ресурса памяти.

Из документа:

ParseMultipartForm анализирует тело запроса как multipart/form-data. Все тело запроса анализируется, и в общей сложности до maxMemory байт его файловых частей сохраняется в памяти, а оставшаяся часть хранится на диске во временных файлах. ParseMultipartForm вызывает ParseForm при необходимости. После одного вызова ParseMultipartForm последующие вызовы не имеют никакого эффекта.

Исходя из вашего сообщения об ошибке, мы можем сказать, что основная причина вашей проблемы связана с тем, что загруженный файл больше, чем выделенная вами память, которая составляет 500 << 20.

Для обработки загрузки больших файлов я предлагаю вместо этого взглянуть на MultipartReader().

Из документа:

MultipartReader возвращает многокомпонентный считыватель MIME, если это составной/данные формы или составной/смешанный запрос POST, в противном случае возвращает 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

fr33jumper
9 августа 2021 в 06:12
1

Спасибо большое добрый человек. Ваш код - это то, что мне нужно, и он работает как шарм. Я хотел бы наградить вас самой высокой честью, которую я могу дать!

novalagung
9 августа 2021 в 07:17
0

Я рад помочь :)