Как улучшить загрузчик файлов, реализованный в golang? - PullRequest
0 голосов
/ 27 марта 2020

Я пытаюсь улучшить производительность загрузчика, который я реализовал в golang. Я думаю, что у меня проблемы с использованием памяти, потому что программа застряла, когда я пытаюсь загрузить большой файл, например, 1 ГБ или больше. Я использовал его, чтобы загрузить файлы около 100 МБ и 300 МБ, и все было в порядке. Загрузчик используется на сервере, который представляет заголовок Accept-Ranges. Ниже я покажу вам реализацию и часть основного, но сначала позвольте мне объяснить вам.

Accept-Range: bytes

В этой реализации я создал http.Client для установки диапазона заголовка с той частью файла, которую я запрашиваю, после этого я сделал запрос. Чтобы сохранить ответ на этот запрос, я создал временный файл и скопировал ответ непосредственно в этот файл. Идея состоит в том, чтобы избежать копирования всего ответа в памяти. Это реализация:

func DownloadPart(wg *sync.WaitGroup, tempName string, url string, part string) {
    //setting up the client to make the request
    client := http.Client{}
    request, err := http.NewRequest("GET", url, nil)

    //setting up the requests
    request.Header.Set("Range", part)
    response, err := client.Do(request)
    checkError(err, "fatal")
    defer response.Body.Close()

    //creating the temporary file and copying
    // the response to it
    file, err := os.Create(tempName)
    checkError(err, "panic")
    defer file.Close()

    _, err = io.Copy(file, response.Body)
    checkError(err, "fatal")

    defer wg.Done()
}

Эта функция вызывается в различных процедурах, поэтому я использовал WaitGroup для уменьшения счетчика, когда заканчивается gorputine, чтобы загрузить часть файла. После завершения всех этих подпрограмм я объединяю временные файлы в один файл. Это реализация функции соединения

func joinFiles(name string) {
    finalFile, err := os.OpenFile(name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        log.Panicln(err.Error())
    }
    defer finalFile.Close()

    files, err := ioutil.ReadDir(".")

    for _, f := range files {
        tempData, err := ioutil.ReadFile(f.Name())
        if err != nil {
            log.Panicln(err.Error())
        }

        if f.Name() != finalFile.Name() {
            finalFile.Write(tempData)
            os.Remove(f.Name())
        }
    }
}

Теперь я покажу вам часть основной функции, которая использует эти функции

//start, end and rest are used to set the Range header in the requests 
//threads are the number of goroutines to used in the download
var wg sync.WaitGroup
wg.Add(threads)
//initializing the goroutines
for i := 0; i < threads; i++ {
    part := fmt.Sprintf("bytes=%d-%d", start, end)
    start = end + 1
    if i == threads-1 {
        end = end + step + rest
    } else {
        end = end + step
    }
    go tools.DownloadPart(&wg, fmt.Sprintf("%d.temp", i), url, part)
}
wg.Wait()
log.Println("Joining files...")
joinFiles(name) 

Если есть способ улучшить эту реализацию

1 Ответ

1 голос
/ 30 марта 2020

Я думаю, что самая большая проблема здесь - как вы соединяете файлы. Вызов ioutil.ReadAll считывает содержимое всего файла в память, и, поскольку вы делаете это для всех частей, вы, скорее всего, попадете с содержимым всего файла в память (G C может работать посередине и освобождать часть из него. ) Гораздо лучше было бы использовать io.Copy для файла (после открытия его с помощью os.Open), чтобы скопировать его в окончательный файл. Таким образом, вам не нужно будет хранить содержимое в памяти.

...