Наберите тайм-аут ввода / вывода tcp при одновременных запросах

Я создаю инструмент на Go, который должен делать очень большое количество одновременных HTTP-запросов ко многим различным серверам. Мой первоначальный прототип на Python без проблем выполнял несколько сотен одновременных запросов.

Тем не менее, я обнаружил, что в Go это почти всегда приводит к Get http://www.google.com: dial tcp 216.58.205.228:80: i/o timeout для некоторых, если количество одновременных запросов превышает ~30-40.

Я тестировал на macOS, openSUSE, другом оборудовании, в разных сетях и с разными списками доменов, и изменение DNS-сервера, как описано в других ответах Stackru, также не работает.

Интересно то, что неудавшиеся запросы даже не генерируют пакет, как видно при проверке с помощью Wireshark.

Есть ли что-то, что я делаю не так или это ошибка в Go?

Минимальная воспроизводимая программа ниже:

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func main() {
    domains := []string{/* large domain list here, eg from https://moz.com/top500 */}

    limiter := make(chan string, 50) // Limits simultaneous requests

    wg := sync.WaitGroup{} // Needed to not prematurely exit before all requests have been finished

    for i, domain := range domains {
        wg.Add(1)
        limiter <- domain

        go func(i int, domain string) {
            defer func() { <-limiter }()
            defer wg.Done()

            resp, err := http.Get("http://"+domain)
            if err != nil {
                fmt.Printf("%d %s failed: %s\n", i, domain, err)
                return
            }

            fmt.Printf("%d %s: %s\n", i, domain, resp.Status)
        }(i, domain)
    }

    wg.Wait()
}

Два конкретных сообщения об ошибках происходят, net.DNSError это не имеет никакого смысла и неописуемый poll.TimeoutError:

&url.Error{Op:"Get", URL:"http://harvard.edu", Err:(*net.OpError)(0xc00022a460)}
&net.OpError{Op:"dial", Net:"tcp", Source:net.Addr(nil), Addr:net.Addr(nil), Err:(*net.DNSError)(0xc000aca200)}
&net.DNSError{Err:"no such host", Name:"harvard.edu", Server:"", IsTimeout:false, IsTemporary:false}

&url.Error{Op:"Get", URL:"http://latimes.com", Err:(*net.OpError)(0xc000d92730)}
&net.OpError{Op:"dial", Net:"tcp", Source:net.Addr(nil), Addr:net.Addr(nil), Err:(*poll.TimeoutError)(0x14779a0)}
&poll.TimeoutError{}

Обновить:

Выполнение запросов с отдельным http.Client так же как http.Transport а также net.Dialer не имеет никакого значения, как это видно при запуске кода с этой игровой площадки.

1 ответ

Я думаю многие из твоих net.DNSErrors на самом деле too many open filesзамаскированные ошибки. Вы можете убедиться в этом, запустив образец кода с netgoтег (рекомендация отсюда ) ( go run -tags netgo main.go), что приведет к появлению таких ошибок, как:

      …dial tcp: lookup buzzfeed.com on 192.168.1.1:53: dial udp 192.168.1.1:53: socket: too many open files

вместо

      …dial tcp: lookup buzzfeed.com: no such host

Убедитесь, что вы закрываете тело ответа на запрос ( resp.Body.Close()). Вы можете найти больше об этой конкретной проблеме в Как лучше всего справиться со «слишком большим количеством открытых файлов»?и как установить ulimit -n из программы golang?. (На моей машине (macOS) увеличение лимитов файлов вручную казалось полезным, но я не думаю, что это хорошее решение, поскольку оно не масштабируется, и я не уверен, сколько открытых файлов вам понадобится в целом. )


Как предположил @liam-kelly, я думаю, что i/o timeoutошибка исходит от DNS-сервера или другого механизма безопасности. Установка пользовательского (плохого) IP-адреса DNS-сервера дает мне ту же ошибку.

Другие вопросы по тегам