TGViewer
Библиотека джависта | Java, Spring, Maven, Hibernate Библиотека джависта | Java, Spring, Maven, Hibernate @javaproglib · 22K subscribers
Post #7494 2.4K
🔧 Spring Retry: повторные попытки с backoff без ручных циклов

Retry-логика руками — источник багов. Неправильный backoff создаёт thundering herd. Spring Retry решает это декларативно.

🔹 Решение

▪️ Зависимость

<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>


▪️ Включение и базовое использование

@Configuration
@EnableRetry
public class RetryConfig { }

@Service
public class ExternalApiService {

@Retryable(
retryFor = {IOException.class, HttpServerErrorException.class},
noRetryFor = {HttpClientErrorException.class}, // 4xx не ретраим
maxAttempts = 4,
backoff = @Backoff(
delay = 1000,
multiplier = 2,
maxDelay = 10000,
random = true // jitter для предотвращения thundering herd
)
)
public ApiResponse callExternalApi(ApiRequest request) {
return restClient.post()
.uri("/api/endpoint")
.body(request)
.retrieve()
.body(ApiResponse.class);
}

@Recover
public ApiResponse recover(IOException e, ApiRequest request) {
log.error("All retry attempts exhausted for request: {}", request.getId(), e);
// fallback логика или выброс бизнес-исключения
throw new ExternalServiceUnavailableException(request.getId(), e);
}
}


▪️ Программная конфигурация для сложных случаев

@Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.maxAttempts(5)
.exponentialBackoff(1000, 2, 30000)
.retryOn(IOException.class)
.withListener(new RetryListenerSupport() {
@Override
public <T, E extends Throwable> void onError(RetryContext ctx,
RetryCallback<T, E> cb,
Throwable t) {
log.warn("Retry attempt {} failed: {}",
ctx.getRetryCount(), t.getMessage());
}
})
.build();
}


▪️ Интеграция с Circuit Breaker


@CircuitBreaker(name = "externalApi", fallbackMethod = "circuitFallback")
@Retryable(retryFor = IOException.class, maxAttempts = 3)
public ApiResponse callWithProtection(ApiRequest request) {
return callExternalApi(request);
}


Retry отрабатывает до Circuit Breaker. Ошибки после всех попыток засчитываются как один сбой в Circuit Breaker — корректное поведение.

random = true в @Backoff добавляет jitter — случайное смещение задержки. Без jitter все инстансы приложения, получившие ошибку одновременно, будут ретраить синхронно, создавая пики нагрузки на восстанавливающийся сервис. Jitter разбивает пики.

⚠️ Ретраить нужно только идемпотентные операции. POST с созданием ресурса без идемпотентного ключа при ретрае создаст дубликаты. Либо добавить Idempotency-Key заголовок, либо не ретраить.

══════ Навигация ══════
Вакансии • Задачи • Собесы

🐸 Библиотека джависта

#Enterprise
  • ❤ 7
  • 👍 6
  • 🔥 1
More from @javaproglib
  1. Oct 17, 2026☕️ Скользящие окна в Stream без ручного буфера Нужно сравнить соседние измерения или обраб…
  2. Oct 16, 2026🧑‍💻 Какие навыки помогают вам в разработке в 2026 году? Что вы чаще используете в работе…
  3. Oct 16, 2026🧩 Неизменяемый список всё ещё может измениться Collections.unmodifiableList() запрещает м…
  4. Oct 15, 2026💡 Logpoints: логи без правки кода и нового деплоя Чтобы увидеть значение переменной, необ…
  5. Oct 14, 2026Post #7897
  6. Oct 14, 2026Почему разработчик планирует отдавать больше задач Claude Винсент Шмальбах описал план пер…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →