Пул соединений и DNS
Конфигурация пула соединений
Пул соединений — ключевой фактор производительности HTTP-клиента. HTTPC управляет пулом через ConnectionConfig.
cfg := httpc.DefaultConfig()
// Параметры пула соединений
cfg.Connection.MaxIdleConns = 100 // Глобальный максимум простаивающих соединений
cfg.Connection.MaxConnsPerHost = 20 // Максимум соединений на хост
cfg.Timeouts.IdleConn = 120 * time.Second // Время удержания простаивающих соединенийОписание параметров
| Параметр | По умолчанию | Описание |
|---|---|---|
MaxIdleConns | 50 | Глобальный максимум простаивающих соединений |
MaxConnsPerHost | 10 | Максимум соединений на хост (активные + простаивающие) |
IdleConn | 90s | Таймаут простаивающего соединения, по истечении закрывается |
Dial | 10s | Таймаут установки соединения |
TLSHandshake | 10s | Таймаут TLS-рукопожатия |
ResponseHeader | 0 | Отключено (используется таймаут Request) |
MaxResponseHeaderBytes | 0 | Лимит размера заголовков ответа; 0 = стандартные для библиотеки Go 10MB |
Рекомендации по сценариям
| Сценарий | MaxIdleConns | MaxConnsPerHost | IdleConn |
|---|---|---|---|
| Высоконагруженные API | 100 | 20 | 120s |
| Обычные сервисы | 50 | 10 | 90s |
| Редкие запросы | 10 | 2 | 30s |
| Внутренние микросервисы | 50 | 10 | 60s |
TIP
MaxConnsPerHost включает активные и простаивающие соединения. Новые запросы сверх этого лимита встают в очередь и ожидают освобождения соединения.
Производные параметры и внутренние лимиты
У части параметров соединений нет отдельных полей конфигурации — движок выводит их из существующих параметров или использует фиксированные значения:
| Параметр | Значение | Источник |
|---|---|---|
MaxIdleConnsPerHost | clamp(MaxConnsPerHost/2, 2, 10); при MaxConnsPerHost=0 — 10 | Выводится из MaxConnsPerHost, отдельного поля нет |
| Общий лимит соединений на клиент | 1000 (активные + простаивающие) | Фиксированное значение; при превышении возвращается ошибка исчерпания пула соединений |
| Интервал TCP KeepAlive-проб | 30s | Фиксированное значение |
ExpectContinueTimeout | 1s | Фиксированное значение (ожидание Expect: 100-continue) |
Как проявляется общий лимит соединений
При достижении общего лимита в 1000 соединений установка нового соединения завершается ошибкой класса ClientError (ErrorTypeNetwork, Message — connection pool exhausted). При значениях по умолчанию MaxIdleConns=50 / MaxConnsPerHost=10 срабатывание практически невозможно — считайте это последней линией обороны при экстремальной конкурентности.
Управление простаивающими соединениями
IdleConn(по умолчанию 90s): простаивающие соединения автоматически закрываются транспортным уровнем по истечении таймаута. Увеличение значения повышает переиспользование соединений, уменьшение — быстрее освобождает ресурсы удалённой стороны (в сценариях частых коротких соединений следите за накоплением TIME_WAIT).- Принудительная очистка при ротации прокси: включение
ProxyRotatePerRequestили пути повторов с ротацией по статус-коду автоматически закрывают все простаивающие соединения, заставляя следующий запрос заново выбирать прокси — иначе переиспользование HTTP/2-соединений через CONNECT-туннель обходило бы выбор прокси и ломало ротацию. Конфигурация прокси и стратегии ротации подробно разобраны в Прокси и пул прокси. client.Close(): закрывает все простаивающие соединения, DoH-резолвер и внутренние ресурсы; последующие запросы возвращаютErrClientClosed.- Автоочистка статистики по хостам: движок поддерживает счётчики соединений по хостам; записи без активности в течение 30 минут и без активных соединений периодически вычищаются (не чаще раза в минуту, лимит записей о хостах — 10000), поэтому при длительной работе память не растёт бесконечно.
Особый случай: распаковка ответов
Транспортный уровень отключает автоматическую распаковку стандартной библиотеки — HTTPC обрабатывает gzip / deflate вручную на уровне обработки ответов: распаковка ограничена Security.MaxDecompressedBodySize (по умолчанию 100MB) и защищает от атак декомпрессионной бомбы. В повседневной работе об этом слое можно не думать — достаточно знать, что Result.RawBody() возвращает уже распакованные байты.
DNS-over-HTTPS
После включения DoH разрешение DNS идёт по зашифрованному HTTPS-каналу: это защищает от перехвата со стороны провайдера и отравления DNS, а также даёт встроенную отказоустойчивость между провайдерами:
cfg := httpc.DefaultConfig()
cfg.Connection.EnableDoH = true
cfg.Connection.DoHCacheTTL = 5 * time.Minute // значение 0 тоже откатывается к 5 минутамПровайдеры DoH по умолчанию (по приоритету):
| Провайдер | Адрес | Описание |
|---|---|---|
| Cloudflare | 1.1.1.1/dns-query | Самый быстрый, приоритет конфиденциальности |
dns.google/resolve | Глобальное покрытие | |
| AliDNS | dns.alidns.com/resolve | Оптимизация для региона Китай |
Механизм работы
- Параллельные запросы A + AAAA: каждое разрешение одновременно запрашивает записи IPv4 и IPv6 и объединяет результаты.
- Разбор двух форматов: по
Content-Typeответа автоматически выбирается JSON (стиль Google/AliDNS) или wire-формат RFC 1035 (стиль Cloudflare); если тип отсутствует или не распознан, сначала пробуется JSON, затем wire — это обеспечивает совместимость с нестандартно настроенными серверами. - Слияние конкурентных запросов: одновременные промахи кэша по одному хосту сливаются в один сетевой обход (singleflight), защищая провайдеров от пробоя кэша.
- Отдельный внутренний клиент: запросы DoH идут через отдельный HTTP-клиент (таймаут 5s, HTTP/2 включён) и не расходуют пул соединений и бюджет таймаутов бизнес-запросов.
Цепочка отката
Cloudflare (1.1.1.1) → Google (dns.google) → AliDNS → системный DNS-резолверУспех любого провайдера сразу возвращает результат; при отказе всех автоматически выполняется откат к системному DNS-резолверу, а ошибки обоих уровней объединяются в сообщение об ошибке. Отказ одного провайдера не приводит к неудаче запроса.
Кэш
| Пункт | Значение |
|---|---|
| TTL | DoHCacheTTL (по умолчанию 5 минут) |
| Лимит ёмкости | 1000 записей; при заполнении сначала вытесняются истёкшие записи, затем ближайшие к истечению |
| Лимит размера ответа | 64KB (защита памяти от вредоносных DNS-ответов) |
Взаимодействие с защитой от SSRF
IP-адреса, разрешённые через DoH, сначала проходят SSRF-фильтрацию (отбрасываются приватные/зарезервированные адреса, учитываются SSRFExemptCIDRs), после чего соединение устанавливается напрямую с проверенным IP — между проверкой и установкой соединения второго разрешения DNS нет, что отсекает атаки DNS rebinding у самого корня. Адреса прокси через DoH не разрешаются (прокси настраивается разработчиком явно, соединение устанавливается напрямую с хостом прокси — см. Прокси и пул прокси). Подробнее — в Защите от SSRF.
HTTP/2
HTTP/2 включён по умолчанию (требуется TLS):
cfg := httpc.DefaultConfig()
cfg.Connection.EnableHTTP2 = false // Отключить HTTP/2Возможности HTTP/2:
- Мультиплексирование: одно соединение обслуживает несколько параллельных запросов
- Сжатие заголовков: меньше повторной передачи заголовков
- Серверный пуш
При отключении транспортный уровень полностью перестаёт пытаться согласовать HTTP/2 (включая принудительные попытки в сценариях с кастомной конфигурацией TLS); HTTP/2 в открытом виде (h2c) не поддерживается. Учтите, что мультиплексирование HTTP/2 заставляет конкурентные запросы к одному хосту делить одно соединение — именно поэтому сценарию ротации прокси требуется закрывать простаивающие соединения (см. ротацию на каждый запрос в Прокси и пул прокси).
Переиспользование пула объектов
Внутри HTTPC переиспользует объекты ответов движка и построители строк через sync.Pool, снижая нагрузку на GC; Result же создаётся заново на каждый запрос и утилизируется GC автоматически.
result, err := client.Get(url)
if err != nil {
return err
}
// Result создаётся заново на каждый запрос, утилизируется GC, ручное освобождение не требуетсяВ высоконагруженных сценариях переиспользование внутреннего пула объектов заметно снижает нагрузку на GC.
Паттерны конкурентных запросов
func fetchAll(ctx context.Context, urls []string) ([]*httpc.Result, error) {
results := make([]*httpc.Result, len(urls))
errs := make([]error, len(urls))
var wg sync.WaitGroup
for i, url := range urls {
wg.Add(1)
go func(idx int, u string) {
defer wg.Done()
result, err := client.Request(ctx, "GET", u)
results[idx] = result
errs[idx] = err
}(i, url)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return nil, err
}
}
return results, nil
}Частые проблемы
| Проблема | Причина | Решение |
|---|---|---|
| Много TIME_WAIT | Таймаут простаивающих соединений слишком короткий | Увеличьте таймаут IdleConn |
| Отказ в соединении | Не хватает соединений на хост | Увеличьте MaxConnsPerHost |
| Запросы стоят в очереди | Пул соединений слишком мал | Увеличьте MaxIdleConns |
| Нужно ограничить число простаивающих соединений на хост | Отдельного поля конфигурации нет | Выводится из MaxConnsPerHost (÷2, зажимается в диапазон 2–10) |
| После включения DoH разрешение не удаётся | Все провайдеры DoH недоступны или таймаутят | Встроен откат к системному DNS; проверьте исходящую сеть или оставьте значения по умолчанию |
| Прокси не работает или часто размыкается | Взаимодействие конфигурации прокси и переиспользования соединений | См. частые вопросы в Прокси и пул прокси |
Полный разбор антипаттернов производительности и рекомендаций по оптимизации — в Оптимизации производительности.
Что дальше
- Оптимизация производительности - руководство по настройке производительности
- Прокси и пул прокси - одиночный прокси, системный прокси, ротация и размыкание пула прокси
- Справочник конфигурации - справочник полей конфигурации соединений
- Обзор безопасности - безопасность SSRF и TLS