Ответ
Правильная настройка таймаутов критически важна для создания надежных и отказоустойчивых систем. Она предотвращает "зависание" запросов и каскадные сбои.
Стратегия выбора значения
Ориентируйтесь на метрики:
- Соберите статистику времени ответа для вызываемого сервиса.
- Установите таймаут на основе 95-го или 99-го перцентиля времени ответа, добавив небольшой запас (например, +10-20%). Это покроет большинство нормальных запросов, но отсечет аномально долгие.
Учитывайте бизнес-требования (SLA/SLO):
- Таймаут не должен превышать требования к времени ответа вашего собственного сервиса. Если ваш сервис должен отвечать за 500 мс, нет смысла ставить таймаут на зависимость в 10 секунд.
Цепочка вызовов:
- При вызове сервиса
A -> B -> C
, таймаут уA
должен быть больше, чем уB
, а уB
— больше, чем уC
. Это позволяет ошибке таймаута корректно "всплыть" наверх.
- При вызове сервиса
Реализация в Go
Идиоматичный способ управления таймаутами и отменой в Go — это использование пакета context
.
1. Общий таймаут на http.Client
:
Хорошая практика для установки базовых ограничений, но негибкая для отдельных запросов.
client := &http.Client{
Timeout: 15 * time.Second, // Общий таймаут на весь запрос
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second, // Таймаут на установку TCP-соединения
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second, // Таймаут на TLS-рукопожатие
},
}
2. Таймаут на конкретный запрос (предпочтительный способ):
Использование context.WithTimeout
дает гранулярный контроль и обеспечивает отмену запроса на всех уровнях (включая http.Client
).
// Создаем контекст с таймаутом в 5 секунд
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // Важно вызывать cancel для освобождения ресурсов
req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil)
if err != nil {
// обработка ошибки
}
resp, err := client.Do(req)
if err != nil {
// Если произошел таймаут, ошибка будет содержать context.DeadlineExceeded
if errors.Is(err, context.DeadlineExceeded) {
log.Println("Request timed out")
}
}
Лучшие практики
- Всегда используйте
context
для исходящих запросов. - Настраивайте и клиентские, и серверные таймауты. Сервер тоже должен иметь таймаут на чтение/запись, чтобы защититься от медленных клиентов.
- Используйте Retry с Exponential Backoff: Для временных сетевых ошибок реализуйте повторные попытки с увеличивающейся задержкой (и джиттером), чтобы не "забить" сервис запросами.