Наберите тайм-аут ввода / вывода 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.DNSError
s на самом деле
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-сервера дает мне ту же ошибку.