Оптимизация производительности
HTTPC изначально спроектирован высокопроизводительным: переиспользование пула соединений, мультиплексирование HTTP/2, пулинг объектов, единственное выделение под объект результата. В большинстве сценариев отличный результат дают пресеты конфигурации напрямую; для дальнейшей настройки нужно понимать внутренние механизмы, чтобы действовать прицельно.
Сравнение пресетов конфигурации
HTTPC предлагает 5 пресетов конфигурации, каждый системно настроен под свой сценарий. Ниже по категориям приведены точные значения ключевых полей — для удобства выбора.
Конфигурация таймаутов
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
TimeoutConfig.Request | 180s | 15s | 60s | 180s | 180s |
TimeoutConfig.Dial | 10s | 5s | 15s | 5s | 5s |
TimeoutConfig.TLSHandshake | 10s | 5s | 15s | 5s | 5s |
TimeoutConfig.ResponseHeader | 0 (отключено) | 10s | 0 (отключено) | 0 (отключено) | 0 (отключено) |
TimeoutConfig.IdleConn | 90s | 30s | 120s | 30s | 30s |
Конфигурация соединений
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
MaxIdleConns | 50 | 20 | 100 | 10 | 10 |
MaxConnsPerHost | 10 | 5 | 20 | 5 | 2 |
EnableHTTP2 | Включён | Включён | Включён | Отключён | Включён |
EnableCookies | Отключены | Отключены | Включены | Включены | Отключены |
EnableDoH | Отключен | Отключен | Отключен | Отключен | Отключен |
Конфигурация безопасности
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
MaxResponseBodySize | 10MB | 5MB | 50MB | 10MB | 1MB |
MaxDecompressedBodySize | 100MB | 100MB | 100MB | 100MB | 100MB |
ValidateURL | Включена | Включена | Включена | Отключена | Включена |
ValidateHeaders | Включена | Включена | Включена | Отключена | Включена |
StrictContentLength | Включён | Включён | Отключён | Включён | Включён |
AllowPrivateIPs | false | false | false | true | false |
InsecureSkipVerify | false | false | false | true | false |
Конфигурация повторов
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
MaxRetries | 3 | 1 | 3 | 1 | 0 |
Delay | 1s | 2s | 500ms | 100ms | 0 |
BackoffFactor | 2.0 | 2.0 | 1.5 | 2.0 | 1.0 |
MaxRetryDelay | 30s | 30s | 30s | 30s | 30s |
EnableJitter | Включён | Включён | Включён | Отключён | Отключён |
Значения запросов по умолчанию
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
FollowRedirects | Включены | Отключены | Включены | Включены | Отключены |
MaxRedirects | 10 | 10 | 10 | 10 | 10 |
UserAgent | httpc/1.0 | httpc/1.0 | httpc/1.0 | httpc-test/1.0 | httpc/1.0 |
TestingConfig запрещён в продакшене
TestingConfig() отключает проверку URL/заголовков, верификацию TLS-сертификатов и защиту от SSRF — только локальная разработка и тесты. При вызове в нетестовом окружении выводится предупреждение безопасности. В продакшене используйте SecureConfig() или DefaultConfig().
Выбор пресета по сценарию
| Сценарий | Рекомендуемый пресет | Корректировки |
|---|---|---|
| Универсальный веб-сервис | Default | — |
| Работа с URL от пользователей | Secure | — |
| Внутренние микросервисы, высокая конкурентность | Performance | Увеличить MaxIdleConns по числу бэкендов |
| Одноразовые скрипты | Minimal | — |
| Сервис скачивания файлов | Performance | Увеличить MaxResponseBodySize |
| API для финансов/медицины | Secure + кастомизация | Добавить middleware аудита |
| Локальная разработка/юнит-тесты | Testing | Ни в коем случае не deployить в продакшен |
// Высокая пропускная способность — пресет напрямую
client, _ := httpc.New(httpc.PerformanceConfig())
// Точечная подстройка отдельных полей поверх пресета
cfg := httpc.PerformanceConfig()
cfg.Timeouts.Request = 120 * time.Second
cfg.Connection.MaxIdleConns = 200
client, _ := httpc.New(cfg)Модель конкурентности: один Client обслуживает все goroutine
Client и DomainClient HTTPC конкурентно-безопасны — любые методы можно вызывать одновременно из нескольких goroutine; в библиотеке есть специальные интеграционные тесты конкурентности (internal/concurrency), покрывающие публичный API под высокой нагрузкой. Поэтому правильный паттерн конкурентности предельно прост:
На глобальном/сервисном уровне создаётся 1 Client
│
├── goroutine 1 ──┐
├── goroutine 2 ──┼── совместно используют один пул соединений и пул объектов
└── goroutine N ──┘Связь конкурентной ёмкости и пула соединений:
| Сценарий | Поведение |
|---|---|
Конкурентность ≤ MaxConnsPerHost (HTTP/1.1) | Каждый запрос занимает своё соединение, без очередей |
Конкурентность > MaxConnsPerHost (HTTP/1.1) | Лишние запросы ждут в очереди транспортного уровня на свободное соединение (не ошибка, но задержка растёт) |
| HTTP/2 включён (по умолчанию) | Запросы к одному хосту делят одно соединение с мультиплексированием, MaxConnsPerHost редко становится узким местом |
Два способа ограничить конкурентность
- Со стороны клиента: поднять
Connection.MaxConnsPerHostдо ≥ пиковой конкурентности (для HTTP/1.1); - Со стороны вызовов: ограничить конкурентность семафором на буферизованном channel (полный пример ниже), активно защищая нижестоящий сервис. Часто их комбинируют: семафор ограничивает поток по выносливости нижестоящего сервиса, пул соединений подстраивается под лимит семафора.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"sync"
"sync/atomic"
"time"
"github.com/cybergodev/httpc"
)
func main() {
// Локальный имитационный сервер: каждый запрос стабильно занимает 50ms
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(50 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
cfg := httpc.DefaultConfig()
cfg.Security.AllowPrivateIPs = true // Разрешаем подключение к локальному тестовому серверу 127.0.0.1
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
const (
total = 20
maxInFlight = 5 // Семафор: не более 5 запросов в полёте одновременно
)
sem := make(chan struct{}, maxInFlight)
var wg sync.WaitGroup
var okCount int64
start := time.Now()
for i := 0; i < total; i++ {
wg.Add(1)
go func() {
defer wg.Done()
sem <- struct{}{} // Захват семафора
defer func() { <-sem }() // Освобождение семафора
result, err := client.Get(server.URL)
if err != nil {
return
}
if result.IsSuccess() {
atomic.AddInt64(&okCount, 1)
}
}()
}
wg.Wait()
fmt.Printf("%d/%d успешно, затрачено %v (последовательно потребовалось бы %v)\n",
okCount, total, time.Since(start), total*50*time.Millisecond)
// Пример вывода: 20/20 успешно, затрачено ~250ms (последовательно ~1s) — 5-сторонняя конкурентность даёт ~5-кратную пропускную способность
}При массовом опросе большого числа независимых URL удобен и другой паттерн — worker pool: фиксированное число worker-goroutine вычитывает задания из jobs channel, естественно ограничивая конкурентность числом worker, семафор не нужен. Полная реализация — в Продвинутых примерах.
Принципы настройки пула соединений
Пул соединений — ядро производительности HTTP-клиента. Пул HTTPC построен на http.Transport стандартной библиотеки Go, но поверх него добавлены автоматические вычисления и безопасные значения по умолчанию.
Автоматический подсчёт простаивающих соединений
MaxIdleConnsPerHost (лимит простаивающих соединений на хост) вручную задавать не нужно — HTTPC выводит его из MaxConnsPerHost:
Простаивающие соединения = MaxConnsPerHost / 2, ограничено диапазоном [2, 10]Конкретные правила (calculateIdleConnsPerHost):
| MaxConnsPerHost | Автоматический лимит простаивающих | Пояснение |
|---|---|---|
| 0 (без лимита) | 10 | Значение по умолчанию — верхняя граница |
| 1 | 1 | Сначала берётся нижняя граница 2, затем «не больше максимума соединений» опускает до 1 |
| 2 | 2 | Ровно нижняя граница |
| 5 | 2 | Половина округляется к нижней границе |
| 10 | 5 | Пресет Default |
| 20 | 10 | Пресет Performance, верхняя граница |
| 100 | 10 | Сверх верхней границы берётся 10 |
Почему именно MaxConnsPerHost / 2
Простаивающие соединения — это «кэш соединений»: уже установленные, но временно не используемые. Значение, равное половине максимума, балансирует между «переиспользованием существующего соединения» (попадание в кэш) и «созданием нового» (при промахе кэша нужно новое рукопожатие) и не даёт простаивающим соединениям избыточно занимать ресурсы серверной стороны.
TCP Keep-Alive
Пул HTTPC использует фиксированный интервал TCP keep-alive в 30 секунд (defaultKeepAlive = 30 * time.Second). После установки соединения операционная система периодически отправляет keep-alive пробы, детектируя мёртвые соединения. Таймаут IdleConn управляет временем жизни простаивающего соединения в пуле (в Default — 90s); оба механизма работают в связке.
package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
func main() {
// Микросервисы с высоким QPS: увеличиваем пул
cfg := httpc.PerformanceConfig()
cfg.Connection.MaxIdleConns = 200 // Глобальный лимит простаивающих соединений
cfg.Connection.MaxConnsPerHost = 50 // Максимум соединений на хост (простаивающие автоматически — 10)
cfg.Timeouts.IdleConn = 300 * time.Second // Простаивающие соединения живут дольше, выше переиспользование
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Запросы горячего пути напрямую переиспользуют соединения из пула
for i := 0; i < 100; i++ {
result, err := client.Get("https://api.example.com/data")
if err != nil {
log.Printf("Запрос %d не удался: %v", i, err)
continue
}
fmt.Printf("Запрос %d: %d\n", i, result.StatusCode())
}
}Преимущества HTTP/2 для производительности
HTTP/2 включён по умолчанию (EnableHTTP2 = true) и даёт три крупных улучшения:
| Возможность | HTTP/1.1 | HTTP/2 |
|---|---|---|
| Мультиплексирование | Каждое соединение — один запрос | Несколько запросов делят одно соединение |
| Сжатие заголовков | Повторная отправка открытым текстом | Сжатие заголовков HPACK |
| Переиспользование соединений | Keep-alive, последовательное | Параллельные потоки (stream) |
HTTP/2 и пул соединений
Мультиплексирование HTTP/2 позволяет одному TCP-соединению одновременно переносить несколько запросов, радикально сокращая накладные расходы на установку соединений. При высокой конкурентности обращений к одному хосту пропускная способность HTTP/2 многократно превышает HTTP/1.1. Откат к HTTP/1.1 происходит только с TestingConfig() (явно отключает HTTP/2) либо когда соединение не поддерживает согласование ALPN.
package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
func main() {
// HTTP/2 уже включён в конфигурации по умолчанию
cfg := httpc.DefaultConfig()
cfg.Connection.EnableHTTP2 = true // И так true, но явное объявление нагляднее
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Конкурентные запросы к сайту с поддержкой HTTP/2 (большинство CDN/облаков)
// Переиспользуется одно TCP-соединение, новое на каждый запрос не создаётся
start := time.Now()
for i := 0; i < 10; i++ {
result, err := client.Get("https://http2.golang.org/")
if err != nil {
log.Printf("Запрос %d не удался: %v", i, err)
continue
}
// Proto() возвращает версию протокола, например "HTTP/2.0"
fmt.Printf("Запрос %d: %s, код состояния %d\n", i, result.Proto(), result.StatusCode())
}
fmt.Printf("10 запросов заняли: %v\n", time.Since(start))
}Механизмы оптимизации памяти
HTTPC многоуровнево оптимизирует работу с памятью; основная идея — меньше выделений в куче и переиспользование объектов.
Единичное выделение resultBundle
Возвращаемый на каждый запрос *Result несёт три вложенные структуры: RequestInfo (информация запроса), ResponseInfo (информация ответа), RequestMeta (метаданные вроде длительности). Традиционный подход требует отдельных выделений под Result и три вложенные структуры — четырёх выделений в куче. HTTPC упаковывает их в один resultBundle, и одного выделения хватает на всё:
Традиционно: 4 независимых выделения (Result + RequestInfo + ResponseInfo + RequestMeta)
HTTPC: 1 выделение (resultBundle), три указателя Result смотрят в одну область памятиВызывающий получает *Result, чьи поля Request, Response, Meta (указатели) ссылаются на соответствующие структуры внутри bundle — полная прозрачность. Поскольку вызывающий может долго держать *Result, здесь не подходит пул объектов (пулирование привело бы к гонкам за данные) — утилизацию выполняет GC.
Пул объектов движка
Слой движка HTTPC широко применяет sync.Pool для переиспользования короткоживущих объектов, снижая нагрузку на GC:
| Пулируемый объект | Назначение | Описание |
|---|---|---|
engine.Response | Объект ответа | После завершения запроса возвращается в пул, переиспользуется следующим запросом |
engine.Request | Объект запроса | Аналогично |
strings.Builder | Построение строк | Сборка URL, форматирование ошибок, сериализация Config |
http.Header | Map заголовков HTTP | Обработка заголовков запроса/ответа |
bytes.Buffer | Кодирование JSON/multipart | Предвыделение по исходной ёмкости |
time.Timer | Таймеры повторов | Избегает частого создания таймеров |
| gzip/flate reader | Распаковка | Переиспользование декомпрессоров |
Разделение труда между пулом объектов и resultBundle
Внутренние объекты движка (Response/Request/Builder) живут недолго и проходят цикл borrow-return внутри запроса — им подходит пулирование. Возвращаемый вызывающему *Result имеет неопределённое время жизни — ему подходит единичное выделение и утилизация GC. Механизмы дополняют друг друга, каждый на своём месте.
Горячий путь с малым числом выделений
Помимо пула объектов, горячий путь запроса содержит набор прицельных оптимизаций против выделений:
| Точка оптимизации | Механизм |
|---|---|
| Пакетное выделение при глубоком копировании заголовков | CloneHeader сначала считает все значения, затем одним выделением создаёт общий базовый массив — «по одному выделению на заголовок (N штук)» превращается в 1 |
| Экранирование параметров запроса без выделений | Строки, не требующие экранирования, возвращаются как есть (ноль выделений); при необходимости экранирования байты пишутся в буфер из пула |
| Прямая запись числовых параметров | int/float64/bool и другие числа пишутся в построитель через strconv.Append*, без промежуточных строк |
| Перенос владения заголовками запроса | Обычный путь запроса и путь скачивания переносят владение header map движкового Response в Result, а не клонируют его |
| Инлайн-массив цепочки перенаправлений | Первые 8 перенаправлений хранятся в инлайн-массиве фиксированной длины пулируемого объекта, срез-переполнение выделяется только после 8 — подавляющее большинство запросов не выделяет ничего под цепочку |
| Переиспользование таймеров сна повторов | time.Timer для отката повторов пулируется и переиспользуется; при частых повторах таймеры не создаются заново |
| Защита ёмкости пула | Объекты сверх порога не возвращаются в пул (например, header map > 64 записей, ёмкость query builder > 4096) — большие объекты не залёживаются в пуле и не раздувают память |
Внутренние метрики и здоровье
Движок собирает пер-запросные метрики чисто атомарными операциями (без блокировок): общее число запросов, успехи/неудачи и сглаженную задержку по формуле скользящего среднего новое среднее = (старое среднее×9 + текущая задержка) / 10; ошибки ниже 10% считаются здоровым состоянием. Эти метрики обслуживают собственные решения движка о здоровье и не являются публичным API — метрики запросов на уровне приложения собирайте через MetricsMiddleware (см. middleware): он вызывает колбэк с методом/URL/кодом состояния/длительностью и напрямую стыкуется с Prometheus и другими системами мониторинга.
Об этом можно не заботиться
Все перечисленные оптимизации прозрачны для вызывающего. Просто используйте API как обычно — переиспользование соединений, пулирование объектов и единичные выделения происходят внутри автоматически:
package main
import (
"fmt"
"log"
"github.com/cybergodev/httpc"
)
func main() {
client, err := httpc.NewDefault()
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Result создаётся заново на каждый запрос, утилизируется GC, ручное освобождение не требуется
result, err := client.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
// В горячем пути предпочитайте RawBody() вместо Body()
// RawBody() возвращает исходный срез байт; Body() — заранее сохранённую строку; String() — отладочное форматирование (самое дорогое)
data := result.RawBody()
fmt.Printf("Размер ответа: %d байт\n", len(data))
fmt.Printf("Длительность запроса: %v\n", result.Meta.Duration)
}Примеры настройки под рабочие нагрузки
Бюджет таймаутов
Четыре транспортных таймаута (Dial, TLSHandshake, ResponseHeader и неявная передача тела) вместе ограничены общим бюджетом Timeouts.Request. Меняя пресет, сохраняйте иерархию «сумма частей ≤ общий бюджет» и не допускайте бессмысленных конфигураций вроде «таймаут установки соединения больше общего таймаута»:
Timeouts.Request (общий бюджет, по умолчанию 180s)
├── Timeouts.Dial Установка соединения (по умолчанию 10s)
├── Timeouts.TLSHandshake TLS-рукопожатие (по умолчанию 10s)
├── Timeouts.ResponseHeader Ожидание заголовков ответа (в Default/Performance 0 = без ограничения на транспортном уровне)
└── Передача тела ответа Доступно всё оставшееся время| Рабочая нагрузка | Request | Dial/TLS | Пояснение |
|---|---|---|---|
| Внутренние микросервисы | 5–10s | 1–2s | Быстрое падение, ошибку обрабатывают повторы/размыкатель выше по стеку |
| Публичные API | 30s | 5s | Баланс между межсетевыми задержками и редкими медленными ответами |
| AI/долгие задачи | 300s+ | 10s | Длинное тело ответа занимает весь остаток бюджета |
| Скачивание больших файлов | 0 (управление через context) | 15s | Общее время контролирует ctx в Download, отдельный запрос — WithTimeout |
Взаимодействие ResponseHeader и WithTimeout
Пресеты Default/Performance ставят ResponseHeader в 0 (не принудительно на транспортном уровне), отдавая WithTimeout() полный контроль над долгими ответами; пресет Secure ставит 10s как защиту от атак класса slowloris. Если вы вручную сужаете ResponseHeader, помните: он может обрезать медленный ответ раньше WithTimeout.
Долгие запросы к AI API
API ИИ-инференса могут отвечать минутами — таймауты нужно ослаблять:
// AI API может отвечать 5–15 минут — не обрезайте дефолтными 180s
result, err := httpc.Post("https://api.ai.example.com/v1/completions",
httpc.WithJSON(payload),
httpc.WithTimeout(900*time.Second), // 15 минут
)Почему в Default ResponseHeader равен 0
TimeoutConfig.ResponseHeader = 0 означает отсутствие принудительного таймаута заголовков на транспортном уровне — временем управляет context-уровень (TimeoutConfig.Request или WithTimeout). Это гарантирует WithTimeout() полный контроль над запросами с долгими ответами. Нужна транспортная защита от slowloris — берите SecureConfig() (там 10s).
Высокий QPS в микросервисах
Частые вызовы между внутренними микросервисами требуют большого пула соединений:
package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.PerformanceConfig()
// Пул настраиваем по числу экземпляров бэкендов
cfg.Connection.MaxIdleConns = 300 // Всего простаивающих соединений
cfg.Connection.MaxConnsPerHost = 30 // На каждый экземпляр бэкенда
// Ответы микросервисов обычно быстрые — сокращаем таймауты ради быстрого падения
cfg.Timeouts.Request = 10 * time.Second
cfg.Retry.Delay = 200 * time.Millisecond
cfg.Retry.BackoffFactor = 2.0
cfg.Retry.MaxRetries = 2
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
start := time.Now()
// Частые запросы переиспользуют пул, TCP/TLS не пересоздаются
for i := 0; i < 50; i++ {
result, err := client.Get("http://user-service:8080/api/users")
if err != nil {
log.Printf("Запрос %d не удался: %v", i, err)
continue
}
_ = result
}
fmt.Printf("50 запросов заняли: %v\n", time.Since(start))
}Скачивание больших файлов (потоковое)
Для больших файлов используйте Download(): он автоматически включает потоковый режим, тело ответа идёт из сети напрямую на диск, потребление памяти не зависит от размера файла, поддерживаются докачка и контрольная сумма:
package main
import (
"context"
"log"
"github.com/cybergodev/httpc"
)
func main() {
cfg := httpc.PerformanceConfig()
cfg.Security.MaxResponseBodySize = 500 * 1024 * 1024 // Лимит 500MB
client, err := httpc.New(cfg)
if err != nil {
log.Fatal(err)
}
defer client.Close()
dcfg := httpc.DefaultDownloadConfig()
dcfg.FilePath = "/tmp/large-file.zip"
dcfg.ResumeDownload = true // Докачка
result, err := client.Download(
context.Background(),
"https://example.com/large-file.zip",
dcfg,
)
if err != nil {
log.Fatal(err)
}
log.Printf("Скачивание завершено: %d байт", result.BytesWritten)
}Не включайте WithStreamBody для обычных методов запроса
WithStreamBody(true) имеет смысл только для путей вроде Download, напрямую потребляющих ответ движка. На Get/Post/Request и другие обычные методы он приведёт к тому, что тело ответа всё равно будет полностью прочитано в Result с закрытием нижележащего потока — у возвращённого Result тело пустое, и вызывающий поток не получит. Правильный вход для потребления больших тел ответа — Download (подробно в Загрузке и выгрузке файлов).
Скрапинг и пул прокси
Скраперы используют пул прокси для ротации IP; HTTPC автоматически поднимает число повторов, чтобы каждый прокси был опробован хотя бы раз (подробно в Повторных попытках и отказоустойчивости):
cfg := httpc.DefaultConfig()
cfg.Connection.ProxyPool = []string{
"http://proxy1:8080",
"http://proxy2:8080",
"http://proxy3:8080",
"http://proxy4:8080",
"http://proxy5:8080",
}
cfg.Connection.ProxyRotateOnStatus = []int{403} // 403 запускает смену прокси
cfg.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin
// MaxRetries автоматически поднимается до 4 (число прокси − 1), чтобы все 5 прокси были опробованыАнтипаттерны производительности
| Антипаттерн | Причина | Правильно |
|---|---|---|
| Новый Client на каждый запрос | Соединения не переиспользуются, каждый раз заново TCP/TLS-рукопожатие | Один глобальный экземпляр Client |
Слишком большой MaxResponseBodySize | Ненужное ослабление лимита памяти | Задавайте по фактическому размеру ответов |
result.String() в горячем пути | Дополнительные расходы на построение строки | result.Body() или result.RawBody() |
| Слишком маленький пул соединений | При высокой конкурентности соединений не хватает, запросы ждут в очереди | MaxConnsPerHost подстраивайте под конкурентность |
WithStreamBody для обычных запросов | У возвращённого Result тело пустое, поток не получить | Большие тела — через Download |
| Отключение HTTP/2 | Деградация до последовательных запросов HTTP/1.1 | Оставьте включённым по умолчанию |
Игнорирование Close() | Утечка соединений | defer client.Close() |
| Забыли переиспользовать глобальный клиент | Постоянное создание/уничтожение Client | Создать один раз, держать долго |
| Лимитирование числом goroutine | Ломает нижестоящий сервис, ловит 429/размыкатель | Семафор или worker pool на число запросов в полёте |
Client обязательно переиспользовать
Основа производительности HTTP — переиспользование соединений. Новый Client на каждый запрос означает TCP-рукопожатие в три шага плюс TLS-рукопожатие каждый раз: задержка взлетает с долей миллисекунды до десятков миллисекунд. В микросервисах внедряйте Client как singleton в структуру сервиса — со временем жизни сервиса.
package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
// Демонстрация антипаттерна: новый Client на каждый запрос
func main() {
start := time.Now()
for i := 0; i < 5; i++ {
// ❌ Каждый оборот цикла создаёт Client — соединения не переиспользуются
client, err := httpc.NewDefault()
if err != nil {
log.Fatal(err)
}
result, err := client.Get("https://httpbin.org/get")
client.Close() // Закрываем каждый раз, пул соединений опустошается
if err != nil {
log.Printf("Запрос %d не удался: %v", i, err)
continue
}
_ = result
}
// 5 запросов занимают куда больше времени, чем вариант с переиспользованием Client
fmt.Printf("Время антипаттерна: %v\n", time.Since(start))
// ✅ Правильно: переиспользуем Client
client, err := httpc.NewDefault()
if err != nil {
log.Fatal(err)
}
defer client.Close()
start = time.Now()
for i := 0; i < 5; i++ {
result, err := client.Get("https://httpbin.org/get")
if err != nil {
log.Printf("Запрос %d не удался: %v", i, err)
continue
}
_ = result
}
fmt.Printf("Время с переиспользованием: %v\n", time.Since(start))
}Что дальше
- Пул соединений и DNS — подробный разбор параметров пула и разрешение DoH
- Прокси и пул прокси — настройка пула прокси и стратегии ротации
- Обработка ошибок — многоуровневые таймауты и классификация ошибок
- Повторные попытки и отказоустойчивость — подробный разбор алгоритмов отката и бюджет повторов
- Обзор безопасности — баланс безопасности и производительности