HTTP retry в Go: почему один список кодов не делает повтор безопасным

После сетевой ошибки сервис повторил POST и создал второй заказ. Решение о retry нельзя принимать только по коду ответа. Важно, является ли операция безопасной для повторения и известно ли, выполнилась ли первая попытка.

Как разобрать ситуацию

Разделите политику по методу, смыслу операции и контракту зависимости. Для чтения допустим ограниченный повтор некоторых временных отказов; изменение состояния требует отдельного механизма идемпотентности. Кроме количества попыток задайте общий deadline, задержку и обработку серверного ограничения скорости. Бесконечные повторы превращают временную перегрузку в устойчивую.

Пример и ожидаемый результат

В примере проверяется упрощённая локальная политика: повторяем GET при 502 или 503, а POST не повторяем автоматически. Это демонстрация проверки правил, а не готовая библиотека повторов. Для 429 нужна отдельная обработка указаний поставщика.

package main

import "fmt"

func retry(method string, status int) bool {
	return method == "GET" && (status == 502 || status == 503)
}
func main() { fmt.Println(retry("GET", 503), retry("POST", 503), retry("GET", 400)) }

Программа выводит true, false, false: GET/503 допускается, POST/503 и GET/400 отклоняются выбранной политикой.

Проверка перед применением

  • Смоделируйте ошибку после фактического создания заказа, а не только до отправки.
  • Проверьте, воспроизводимо ли тело запроса для второй попытки.
  • Запишите число попыток и суммарную задержку, чтобы повтор не выходил за бюджет входящего запроса.

Полезный тест надёжности — одинаковый бизнес-запрос с разными местами сбоя: до обработки, после записи и до получения ответа. Во всех случаях проверьте итоговое состояние, а не только количество успешных HTTP-ответов. Дешёвая повторная попытка может оказаться дорогой двойной операцией, если измерять лишь сетевую доступность.

Границы решения

GET не является магическим разрешением повторять любую бизнес-операцию: неверно спроектированный API может менять состояние при чтении. Полный клиент должен учитывать контракт, отмену, тело ответа и ограничения поставщика.

Если базовый Go уже знаком и хочется связать HTTP, хранение данных и обработку отказов в одну систему, посмотрите наш платный курс

Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker. Перед записью сравните программу со своей задачей и подготовкой.

Техническая справка: pkg.go.dev: документация, www.rfc-editor.org: документация.

Следующий шаг: Go backend: HTTP, конкурентность и устойчивость сервиса. По смежным вопросам: Общий счётчик в Go: почему обычный плюс не безопасен между goroutine, Отмена HTTP-запроса в Go: как убедиться, что context дошёл до клиента.

Продолжить на практике

  • Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker ↗

    От работающего Go API — к взаимодействию сервисов, которому можно доверять. Разберитесь, как проектировать контракты, сохранять согласованность данных и восстанавливать обработку после сбоев. HTTP, PostgreSQL, gRPC, Kafka и Docker — через код и наглядные разборы.