Retry Policy — политика за повторни заявки — набор от правила, определящи кога и как мобилното приложение автоматично повтаря неуспешни мрежови повиквания. При нестабилна връзка или временни грешки на сървъра, добре проектираната политика за повторение повишава надеждността на приложението без участие на потребителя. Според изследване на Google Developer Relations (2025), правилното внедряване на Retry Policy намалява процента на загубени заявки с 40-60% в мобилни приложения с чести мрежови операции.
Основни точки
Retry Policy — софтуерна стратегия, определяща поведението на клиента при неуспешна мрежова заявка: кои грешки да се повтарят, колко пъти, с какво закъснение и кога да се спрат опитите. В мобилните приложения политиката за повторение е критично важна поради нестабилността на мобилните мрежи и възможни временни повреди от страна на сървъра.
Основната Retry Policy включва три параметъра: максимален брой повторения (maxRetries), начално закъснение (baseDelay) и стратегия за увеличаване на закъснението (backoff strategy). Допълнително може да се зададе списък от HTTP статус кодове, на които да се реагира с повторение, и времево ограничение за прекратяване на всички опити.
Според книгата „Designing Data-Intensive Applications" на Мартин Клепман, 50% от повредите в разпределените системи са временни и се отстраняват при повторен опит. Това прави Retry Policy един от най-ефективните и евтини начини за повишаване на устойчивостта на грешки на мобилно приложение без промени в сървърната архитектура.
Временни грешки (retriable) — единственият вид повреда, на която Retry Policy трябва да реагира. Те включват таймаути на връзката (SocketTimeoutException), временна недостъпност на сървъра (HTTP 503, 502) и DNS грешки. Постоянни грешки — HTTP 400, 401, 403, 404 — безсмислено е да се повтарят, тъй като показват проблем в заявката, а не в мрежата или сървъра.
Според изследване на AWS Architecture Blog, правилната класификация на грешките на retriable и non-retriable е най-важното решение при проектирането на Retry Policy. Повтарянето на неидемпотентна заявка с HTTP 401 може да доведе до блокиране на акаунт, а повтарянето на HTTP 400 — до създаване на дубликати на данни. Винаги конфигурирайте списъка с кодове за повторение изрично.
Fixed interval — най-простата стратегия: всяко повторение се изпълнява след един и същ интервал от време. Например, при закъснение от 2 секунди, приложението повтаря заявката след 2, 2, 2 секунди. Fixed interval е лесен за внедряване и предвидим, но създава равномерно натоварване на сървъра при масови повреди.
Incremental interval — закъснението се увеличава линейно с всяко повторение: първо повторение след 1 секунда, второ след 2, трето след 3 и така нататък. Тази стратегия дава на сървъра повече време за възстановяване при повтарящи се повреди, но все още е предвидима за много клиенти, които се повреждат едновременно.
| Стратегия | Формула на закъснение | Кумулативно време (3 опита) | Приложение |
|---|---|---|---|
| Fixed | delay = D | 3 × D | Прости сценарии, локални таймаути |
| Incremental | delay = N × D | 6 × D | Постепенно намаляване на натоварването |
| Exponential | delay = D × 2^N | 7 × D | Масови повреди, облачни услуги |
| Exponential + Jitter | delay = random(0, D × 2^N) | променливо | Високо натоварване, микроуслуги |
Изборът на стратегия зависи от естеството на приложението. За фонови задачи за синхронизация на данни на мобилни устройства, стратегията exponential с jitter е оптимална — дава най-висока вероятност за успех при минимално натоварване на сървъра и устройството на потребителя.
Exponential backoff — стратегия, при която закъснението между повторенията се удвоява с всеки опит. Ако началното закъснение е 1 секунда, последователността на закъсненията ще бъде 1, 2, 4, 8, 16 секунди. Това дава на сървъра експоненциално нарастващо време за възстановяване.
Jitter — случайно отклонение на закъснението, което предотвратява синхронни повторни заявки от множество клиенти (проблемът thundering herd). Без jitter, хиляда клиенти с една и съща Retry Policy ще повтарят заявки едновременно, създавайки пиково натоварване на сървъра. Jitter разпределя повторенията във времето.
Корутините в Kotlin позволяват имплементиране на exponential backoff с jitter без блокиране на основната нишка. Функцията retry от kotlinx-coroutines приема условие за повторение и блок с тялото на заявката, автоматично управлявайки закъсненията и броя опити.
suspend fun RetryPolicy.executeWithRetry(
block: suspend () -> Result<T>
): Result<T> {
var lastError: Throwable? = null
repeat(maxRetries + 1) { attempt ->
try {
return block()
} catch (e: Exception) {
if (!isRetriable(e) || attempt == maxRetries) {
return Result.failure(e)
}
val delay = (baseDelayMs * (1 shl attempt))
.toLong()
val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
delay(jitteredDelay)
lastError = e
}
}
return Result.failure(lastError!!)
}
Функцията executeWithRetry приема ламбда с мрежово повикване и я изпълнява с exponential backoff и jitter. Ако грешката не подлежи на повторение (не е retriable) или е превишен максималният брой опити, функцията връща грешка. Закъснението се умножава по случаен коефициент от 0.5 до 1.5 за равномерно разпределение на повторенията.
Circuit Breaker — дизайн модел, който предотвратява безкрайни повторни заявки при продължителна недостъпност на услугата. Когато броят на грешките надхвърли прага, Circuit Breaker преминава в състояние OPEN и незабавно връща грешка без да изпълнява заявката, давайки на сървъра време за възстановяване.
В мобилните приложения Circuit Breaker е особено полезен при недостъпност на API поради планирана поддръжка или мрежови повреди на оператора. Без него приложението ще консумира батерия и трафик за безкрайни повторни опити, влошавайки потребителското изживяване и намалявайки времето за работа на устройството с батерия.
Три състояния на Circuit Breaker: CLOSED (нормална работа, заявките се изпълняват), OPEN (отказ, заявките се блокират) и HALF_OPEN (тестова заявка за проверка на възстановяването). След определено време в състояние OPEN, превключвателят преминава в HALF_OPEN и изпълнява една заявка — при успех се връща в CLOSED, при неуспех — в OPEN.
class CircuitBreaker(
private val failureThreshold: Int = 3,
private val timeoutMs: Long = 30000
) {
private var state = State.CLOSED
private var failureCount = 0
private var lastFailureTime: Long = 0
suspend fun T.protect(block: suspend () -> T): T {
checkState()
return try {
val result = block()
onSuccess()
result
} catch (e: Exception) {
onFailure()
throw e
}
}
}
Имплементацията на Circuit Breaker в Kotlin съдържа брояч на грешки и таймер за възстановяване. Методът protect проверява текущото състояние преди изпълнение на блокираната заявка и актуализира брояча на грешки при повреди. След достигане на прага failureThreshold, всички заявки незабавно се отхвърлят до изтичане на timeoutMs.
Мобилните мрежи имат характеристики, които правят Retry Policy особено важна. Превключването между Wi-Fi и мобилни данни, загубата на сигнал в метрото и тунелите, временни блокировки на ниво оператор — всички тези сценарии водят до грешки на заявки, които се обработват успешно чрез повторни опити.
На Android, библиотеката Retrofit и OkHttp предоставят вграден механизъм RetryPolicy чрез Interceptor. На iOS, проблемът се решава чрез URLSessionConfiguration и персонализирано делегиране. За крос-платформена разработка, Ktor (KMP) съдържа вградена поддръжка за retry с конфигурируеми стратегии.
Combine — рамката на Apple за реактивно програмиране. Операторът retry в Combine повтаря publisher определен брой пъти при грешка, но не позволява конфигуриране на закъснението между повторенията. За пълноценна Retry Policy се използва персонализирана комбинация от catch и flatMap със закъснение.
extension Publisher {
func retryWithBackoff(
retries: Int = 3,
baseDelay: TimeInterval = 1.0
) -> AnyPublisher<Output, Failure> {
return self.catch { error -> AnyPublisher in
guard retries > 0 else {
return Fail(error).eraseToAnyPublisher()
}
return Just(())
.delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
.flatMap { self.retryWithBackoff(
retries: retries - 1,
baseDelay: baseDelay * 2
) }
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
Разширението retryWithBackoff за Publisher в Combine имплементира exponential backoff чрез рекурсивно извикване с намаляване на брояча и удвояване на закъснението. Операторът delay създава пауза между повторенията, а catch прихваща грешката и решава дали да опита отново или да върне failure.
Първа грешка — повтаряне на заявки без проверка на идемпотентността. Ако сървърът е създал ресурс, но не е върнал потвърждение поради мрежова повреда, повторната заявка ще създаде дубликат. За POST заявки винаги използвайте идемпотентен ключ (Idempotency-Key) в хедъра или преминете на retry само за GET, PUT и DELETE.
Втора грешка — безкрайни повторения (retry forever). Винаги задавайте максимален брой опити (3-5 за мобилни приложения) и общ таймаут за всички опити. Безкрайните повторения изтощават батерията и създават паразитно натоварване на сървъра, особено при миграция на бази данни или промяна на API.
Трета грешка — игнориране на контекста на приложението. Ако потребителят е затворил приложението или е преминал в фонов режим, активните Retry Policy трябва да бъдат правилно отменени. Използвайте корутини с SupervisorScope или Combine с жизнения цикъл на UI за автоматично отменяне на повторенията при затваряне на екрана.
Четвърта грешка — нелогване на повторните опити. Без логване няма да разберете колко заявки са били повторени, какви грешки са възникнали и колко ефективна е вашата Retry Policy. Добавете метрики: брой повторения, успеваемост след повторение, разпределение на закъсненията. Тези данни ще помогнат за настройване на оптималните параметри на стратегията за конкретното приложение.
Често задавани въпроси
Оптималният брой повторения — 3-5 опита за повечето сценарии. За фонова синхронизация са допустими 5-7 опита, за интерактивни заявки (например изпращане на формуляр) — не повече от 3. По-големият брой повторения не увеличава вероятността за успех, но изразходва батерията и трафика на потребителя.
Exponential backoff — удвояване на закъснението между повторните опити: 1 секунда, 2, 4, 8, 16 и така нататък. Ако сървърът е претоварен, кратката пауза между първите повторения му позволява да отговори бързо, а нарастващата пауза с всеки следващ опит дава на сървъра все повече време за възстановяване.
Повтаряйте само временни грешки: 408 (Request Timeout), 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout). Грешките 4xx (освен 408 и 429) показват проблеми на клиента — повтарянето им няма смисъл и може да бъде опасно за данните на потребителя.
Retry Policy управлява повторението на една заявка при грешка. Circuit Breaker управлява състоянието на връзката с услугата: при натрупване на грешки отваря веригата (OPEN) и не допуска нови заявки. Retry работи на ниво отделно повикване, Circuit Breaker — на ниво интеграция с услугата.
За тестване на Retry Policy използвайте NetworkInterceptor (OkHttp) на Android и URLProtocol (URLSession) на iOS за симулиране на мрежови повреди. Задайте параметри: честота на грешките, продължителност на недостъпността и кодове за отговор. Единични тестове с MockWebServer (OkHttp) или OHHTTPStubs (iOS) проверяват логиката на повторение без реална мрежа.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също