Оптимизация производительности
HTTPC изначально спроектирован высокопроизводительным: повторное использование пула соединений, мультиплексирование HTTP/2, пулинг объектов и единичное выделение памяти под объект результата. В большинстве сценариев достаточно использовать предустановки конфигурации для отличной производительности; для дальнейшей настройки нужно понимать низкоуровневые механизмы, чтобы действовать точечно.
Сравнение предустановок конфигурации
HTTPC предоставляет 5 предустановок конфигурации, каждая систематически настроена под конкретный сценарий. Ниже приведены точные значения ключевых полей по категориям для удобства сравнения и выбора.
Конфигурация таймаутов
| Поле | Default | Secure | Performance | Testing | Minimal |
|---|---|---|---|---|---|
Timeouts.Request | 180s | 15s | 60s | 180s | 180s |
Timeouts.Dial | 10s | 5s | 15s | 5s | 5s |
Timeouts.TLSHandshake | 10s | 5s | 15s | 5s | 5s |
Timeouts.ResponseHeader | 0 (отключено) | 10s | 0 (отключено) | 0 (отключено) | 0 (отключено) |
Timeouts.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 + пользовательская | Добавить промежуточное ПО аудита |
| Локальная разработка/модульные тесты | Testing | Не развертывать в продакшен |
// Сценарий высокой пропускной способности — используйте пресет напрямую
client, _ := httpc.New(httpc.PerformanceConfig())
// Тонкая настройка отдельных полей на основе пресета
cfg := httpc.PerformanceConfig()
cfg.Timeouts.Request = 120 * time.Second
cfg.Connection.MaxIdleConns = 200
client, _ := httpc.New(cfg)Принципы настройки пула соединений
Пул соединений — основа производительности HTTP-клиента. Пул HTTPC основан на http.Transport стандартной библиотеки Go, но добавляет автоматическую логику вычислений и безопасные значения по умолчанию.
Автоматический расчёт простаивающих соединений
MaxIdleConnsPerHost (лимит простаивающих соединений на хост) не нужно задавать вручную — HTTPC автоматически выводит его из MaxConnsPerHost:
Простаивающие соединения = MaxConnsPerHost / 2, ограничено диапазоном [2, 10]Конкретные правила (calculateIdleConnsPerHost):
| MaxConnsPerHost | Автоматическое число простаивающих | Описание |
|---|---|---|
| 0 (без лимита) | 10 | Значение по умолчанию (верхний предел) |
| 1 | 2 | Не ниже нижнего предела |
| 2 | 2 | Ровно нижний предел |
| 5 | 2 | Половина округляется к нижнему |
| 10 | 5 | Пресет Default |
| 20 | 10 | Пресет Performance, верхний предел |
| 100 | 10 | Превышает верхний предел — берётся 10 |
Почему именно MaxConnsPerHost / 2
Простаивающие соединения — это «кеш соединений»: уже установленные, но временно не используемые. Установка их в половину от максимального числа соединений обеспечивает баланс между «переиспользованием существующих» (попадание в кеш) и «созданием новых» (промах кеша требует повторного рукопожатия), предотвращая избыточное потребление ресурсов сервера простаивающими соединениями.
TCP Keep-Alive
Пул соединений HTTPC использует фиксированный 30-секундный интервал TCP keep-alive (defaultKeepAlive = 30 * time.Second). После установки соединения ОС периодически отправляет probe-пакеты 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 заголовков | Обработка заголовков запроса/ответа |
bytes.Buffer | Кодирование JSON/multipart | Предварительное выделение по начальной ёмкости |
time.Timer | Таймер повторов | Избегает частого создания таймеров |
| gzip/flate reader | Распаковка | Переиспользование декомпрессоров |
Разделение труда между пулом объектов и resultBundle
Внутренние объекты движка (Response/Request/Builder) имеют короткий жизненный цикл, выполняя цикл borrow-return внутри запроса — подходят для пулинга. Возвращаемый вызывающему *Result имеет неопределённый жизненный цикл — подходит для единичного выделения + сборки GC. Они дополняют друг друга, каждый на своём месте.
Вам не нужно об этом беспокоиться
Все эти оптимизации полностью прозрачны для вызывающего. Вы просто используете 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)
}Примеры настройки под рабочие нагрузки
AI API с длинным опросом
API AI-вывода может отвечать несколько минут — нужно ослабить ограничения таймаута:
// 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 минут
)Почему ResponseHeader в Default равен 0
Timeouts.ResponseHeader = 0 означает, что таймаут заголовка ответа не принудителен на транспортном уровне — он контролируется context-уровневым таймаутом (Timeouts.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))
}Загрузка больших файлов (потоковая)
При загрузке больших файлов используйте WithStreamBody(true), чтобы не загружать весь ответ в память, а с методом 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)
}Сканеры и пул прокси
В сценариях сканирования используется пул прокси для ротации 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 проксиАнтипаттерны производительности
| Антипаттерн | Причина | Правильный подход |
|---|---|---|
| Создание клиента на каждый запрос | Соединения не переиспользуются, каждый раз TCP/TLS рукопожатие заново | Глобальное переиспользование одного экземпляра Client |
Слишком большой MaxResponseBodySize | Необоснованно высокий лимит памяти | Устанавливать по реальному размеру ответа |
result.String() на горячем пути | Дополнительные накладные расходы построения строки | Использовать result.Body() или result.RawBody() |
| Слишком малый пул соединений | При высокой параллельности соединений не хватает, образуется очередь | Настраивать MaxConnsPerHost по уровню параллелизма |
| Отключение HTTP/2 | Деградация до последовательных HTTP/1.1 запросов | Держать включённым по умолчанию |
Игнорирование Close() | Утечка соединений | defer client.Close() |
| Глобальный доступ без переиспользования | Повторное создание/уничтожение Client | Создать один раз, удерживать длительно |
Client обязательно нужно переиспользовать
Основа производительности HTTP — повторное использование соединений. Создание нового Client на каждый запрос означает каждый раз TCP тройное рукопожатие + TLS рукопожатие, задержка возрастает от субмиллисекундной до десятков миллисекунд. В микросервисных сценариях внедряйте Client как одиночку (singleton) в структуру сервиса, живущий весь жизненный цикл сервиса.
package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
// Демонстрация антипаттерна: создание клиента на каждый запрос
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))
}Что дальше
- Пул соединений и прокси — детальный разбор параметров пула, настройка пула прокси и стратегии ротации
- Обработка ошибок — стратегия разделения таймаутов и классификация ошибок
- Повторы и отказоустойчивость — детальный разбор алгоритма отката и бюджет повторов
- Обзор безопасности — баланс безопасности и производительности